redisObject
Redis存儲的數據都使用redisObject來封裝,包括string,hash,list,set,zset在內的全部數據類型。
簡單來講,就是將key-value封裝成對象,key是一個對象,value也是一個對象java
typedef struct redisObject{ // 對象的類型 unsigned type 4:; // 對象的編碼格式 unsigned encoding:4; // 指向底層實現數據結構的指針 void * ptr; //LRU計時時鐘 lru:REDIS_LRU_BITS //引用計數器 int refcount; }robj;
String
Redis 的字符串是動態字符串,是能夠修改的字符串redis
SDS數組
struct sdshdr{ //記錄buf數組中已使用字節的數量 //等於 SDS 保存字符串的長度 int len; //記錄 buf 數組中未使用字節的數量 int free; //字節數組,用於保存字符串 char buf[]; }
常數複雜度獲取字符串長度
對於C語言求字符串長度,須要遍歷字符串,對於sds已經記錄了當前字符串長度,時間複雜度爲O(1)服務器
杜絕緩衝區溢出
C 語言中使用 strcat 函數來進行兩個字符串的拼接,一旦沒有分配足夠長度的內存空間,就會形成緩衝區溢出。對於 SDS 數據類型,在進行字符修改的時候,會首先根據記錄的 len 屬性檢查內存空間是否知足需求,若是不知足,會進行相應的空間擴展,而後在進行修改操做,因此不會出現緩衝區溢出。數據結構
減小修改字符串的內存從新分配次數
空間預分配:
對字符串進行空間擴展的時候,擴展的內存比實際須要的多,這樣能夠減小連續執行字符串增加操做所需的內存重分配次數。函數
惰性空間釋放:
對字符串進行縮短操做時,程序不當即使用內存從新分配來回收縮短後多餘的字節,而是使用 free 屬性將這些字節的數量記錄下來,等待後續使用,至關於打個標記ui
編碼
- int 編碼:保存的是能夠用 long 類型表示的整數值。
- raw 編碼:保存長度大於44字節的字符串(redis3.2版本以前是39字節,以後是44字節)。
- embstr 編碼:保存長度小於44字節的字符串(redis3.2版本以前是39字節,以後是44字節)
embstr與raw都使用redisObject和sds保存數據,區別在於,embstr的使用只分配一次內存空間(所以redisObject和sds是連續的),而raw須要分配兩次內存空間(分別爲redisObject和sds分配空間)。所以與raw相比,embstr的好處在於建立時少分配一次空間,刪除時少釋放一次空間,以及對象的全部數據連在一塊兒,尋找方便。而embstr的壞處也很明顯,若是字符串的長度增長鬚要從新分配內存時,整個redisObject和sds都須要從新分配空間,所以redis中的embstr實現爲只讀編碼
Redis中對於浮點數類型也是做爲字符串保存的,在須要的時候再將其轉換成浮點數類型spa
字符串在長度小於 1M 以前,擴容空間採用加倍策略,也就是保留 100% 的冗餘空間。當長度超過 1M 以後,爲了不加倍後的冗餘空間過大而致使浪費,每次擴容只會多分配 1M 大小的冗餘空間指針
zipList
壓縮列表(ziplist)是Redis爲了節約內存而開發的,是由一系列的特殊編碼的連續內存塊組成的順序性數據結構
- previous_entry_ength 記錄壓縮列表前一個字節的長度
- encoding 節點的content的內容類型以及長度
- content:保存節點的內容
壓縮列表從表尾節點倒序遍歷,首先指針經過zltail偏移量指向表尾節點,而後經過指向節點記錄的前一個節點的長度依次向前遍歷訪問整個壓縮列表,有效地減小了內存的浪費
skipList
跳躍表(skiplist)是一種有序數據結構,它經過在每一個節點中維持多個指向其它節點的指針,從而達到快速訪問節點的目的。具備以下性質:
一、由不少層結構組成;
二、每一層都是一個有序的鏈表,排列順序爲由高層到底層,都至少包含兩個鏈表節點,分別是前面的head節點和後面的nil節點;
三、最底層的鏈表包含了全部的元素;
四、若是一個元素出如今某一層的鏈表中,那麼在該層之下的鏈表也全都會出現(上一層的元素是當前層的元素的子集);
五、鏈表中的每一個節點都包含兩個指針,一個指向同一層的下一個鏈表節點,另外一個指向下一層的同一個鏈表節點;

①、搜索:從最高層的鏈表節點開始,若是比當前節點要大和比當前層的下一個節點要小,那麼則往下找,也就是和當前層的下一層的節點的下一個節點進行比較,以此類推,一直找到最底層的最後一個節點,若是找到則返回,反之則返回空。
②、插入:首先肯定插入的層數,有一種方法是假設拋一枚硬幣,若是是正面就累加,直到碰見反面爲止,最後記錄正面的次數做爲插入的層數。當肯定插入的層數k後,則須要將新元素插入到從底層到k層。
③、刪除:在各個層中找到包含指定值的節點,而後將節點從鏈表中刪除便可,若是刪除之後只剩下頭尾兩個節點,則刪除這一層。
List
雙端:鏈表具備前置節點和後置節點的引用,獲取這兩個節點時間複雜度都爲O(1)。
無環:表頭節點的 prev 指針和表尾節點的 next 指針都指向 NULL,對鏈表的訪問都是以 NULL 結束。
帶鏈表長度計數器:經過 len 屬性獲取鏈表長度的時間複雜度爲 O(1)。
多態:鏈表節點使用 void* 指針來保存節點值,能夠保存各類不一樣類型的值
編碼:
- 在列表元素較少的狀況下會使用一塊連續的內存存儲,造成的結構是
ziplist
,也便是壓縮列表 - 列表保存元素個數小於512個,每一個元素長度小於64字節
- 列表元素較多的時候,會將各個zipList串起來,造成quickList
- 上面兩個條件能夠在redis.conf 配置文件中的 list-max-ziplist-value選項和 list-max-ziplist-entries 選項進行配置
Hash
字典,用於保存鍵值對的抽象數據結構
編碼
哈希對象的編碼能夠是 ziplist 或者 hashtable
ziplist:

hashtable:

- 列表保存元素個數小於512個
- 每一個元素長度小於64字節
不能知足這兩個條件的時候使用 hashtable 編碼。第一個條件能夠經過配置文件中的 set-max-intset-entries 進行修改
Redis 的字典使用哈希表做爲底層實現,有兩個哈希表:
- ht[0]:用於存放真實的key-vlaue數據
- ht[1]:用於擴容(rehash)
解決哈希衝突:方法是鏈地址法,經過字典裏面的 *next 指針指向下一個具備相同索引值的哈希表節點
觸發擴容的條件
- 服務器目前沒有執行 BGSAVE 命令或者 BGREWRITEAOF 命令,而且負載因子大於等於1。
- 服務器目前正在執行 BGSAVE 命令或者 BGREWRITEAOF 命令,而且負載因子大於等於5。
- 負載因子 = 哈希表已保存節點數量 / 哈希表大小
漸近式 rehash
擴容和收縮操做不是一次性、集中式完成的,而是分屢次、漸進式完成的。在進行漸進式rehash期間,字典的刪除查找更新等操做可能會在兩個哈希表上進行,第一個哈希表沒有找到,就會去第二個哈希表上進行查找。可是進行 增長操做,必定是在新的哈希表上進行的
intset
Redis用於保存整數值的集合抽象數據類型,不會重複且有序(從小到大)
保存類型爲int16_t、int32_t ,int64_t 的整數值
升級操做:將int16_t升級到32,或者64,節省內存
不支持降級操做
Set
String 類型的無序鍵值對,特殊的字典,值爲null,值不能重複
集合對象編碼是intset,hashtable
當集合同時知足如下兩個條件時,使用 intset 編碼:
一、集合對象中全部元素都是整數
二、集合對象全部元素數量不超過512
不能知足這兩個條件的就使用 hashtable 編碼。第二個條件能夠經過配置文件的 set-max-intset-entries 進行配置
zset
有序集合
編碼:
當有序集合對象同時知足如下兩個條件時,對象使用 ziplist 編碼:
- 保存的元素數量小於128字節;
- 保存的全部元素長度都小於64字節。
不能知足上面兩個條件的使用 skiplist 編碼。以上兩個條件也能夠經過Redis配置文件zset-max-ziplist-entries 選項和 zset-max-ziplist-value 進行修改