在C語言中,字符串是以字符數組的形式體現的(以’\0’爲結束符),html
Redis中的字符串定義以下:node
/* 字符串結構體(字符串就是字符數組) */ struct sdshdr { // 字符串當前長度 unsigned int len; // 剩餘可用長度 unsigned int free; // 字符數組(具體存放字符串的地方) char buf[]; };
typedef struct list { // 表頭節點 listNode *head; // 表尾節點 listNode *tail; // 鏈表所包含的節點數量 unsigned long len; // 節點值複製函數 void *(*dup)(void *ptr); // 節點值釋放函數 void (*free)(void *ptr); // 節點值對比函數 int (*match)(void *ptr, void *key); } list;
哈希表redis
typedef struct dictht { // 哈希表數組 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩碼,用於計算索引值 // 老是等於 size - 1 unsigned long sizemask; // 該哈希表已有節點的數量 unsigned long used; } dictht;
哈希表節點 算法
typedef struct dictEntry { // 鍵 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 指向下個哈希表節點,造成鏈表 struct dictEntry *next; } dictEntry;
字典數據庫
typedef struct dict { // 類型特定函數 dictType *type; // 私有數據 void *privdata; // 哈希表 dictht ht[2]; // rehash 索引 // 當 rehash 不在進行時,值爲 -1 int rehashidx; /* rehashing not in progress if rehashidx == -1 */ } dict;
dictType數組
typedef struct dictType { // 計算哈希值的函數 unsigned int (*hashFunction)(const void *key); // 複製鍵的函數 void *(*keyDup)(void *privdata, const void *key); // 複製值的函數 void *(*valDup)(void *privdata, const void *obj); // 對比鍵的函數 int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 銷燬鍵的函數 void (*keyDestructor)(void *privdata, void *key); // 銷燬值的函數 void (*valDestructor)(void *privdata, void *obj); } dictType;
當如下條件中的任意一個被知足時, 程序會自動開始對哈希表執行擴展操做:安全
# 負載因子 = 哈希表已保存節點數量 / 哈希表大小 load_factor = ht[0].used / ht[0].size
1
;5
;0.1
時, 程序自動開始對哈希表執行收縮操做。重點回顧¶服務器
跳躍表節點的實現由 redis.h/zskiplistNode
結構定義:數據結構
typedef struct zskiplistNode { // 後退指針 struct zskiplistNode *backward; // 分值 double score; // 成員對象 robj *obj; // 層 struct zskiplistLevel { // 前進指針 struct zskiplistNode *forward; // 跨度 unsigned int span; } level[]; } zskiplistNode;
zskiplist
結構的定義以下:dom
typedef struct zskiplist { // 表頭節點和表尾節點 struct zskiplistNode *header, *tail; // 表中節點的數量 unsigned long length; // 表中層數最大的節點的層數 int level; } zskiplist;
header
和 tail
指針分別指向跳躍表的表頭和表尾節點, 經過這兩個指針, 程序定位表頭節點和表尾節點的複雜度爲 O(1) 。
經過使用 length
屬性來記錄節點的數量, 程序能夠在 O(1) 複雜度內返回跳躍表的長度。
level
屬性則用於在 O(1) 複雜度內獲取跳躍表中層高最大的那個節點的層數量, 注意表頭節點的層高並不計算在內。
重點回顧¶
zskiplist
和 zskiplistNode
兩個結構組成, 其中 zskiplist
用於保存跳躍表信息(好比表頭節點、表尾節點、長度), 而 zskiplistNode
則用於表示跳躍表節點。1
至 32
之間的隨機數。整數集合(intset)是 Redis 用於保存整數值的集合抽象數據結構, 它能夠保存類型爲 int16_t
、 int32_t
或者 int64_t
的整數值, 而且保證集合中不會出現重複元素
typedef struct intset { // 編碼方式 uint32_t encoding; // 集合包含的元素數量 uint32_t length; // 保存元素的數組 int8_t contents[]; } intset;
重點回顧¶
壓縮列表各個組成部分的詳細說明
屬性 | 類型 | 長度 | 用途 |
---|---|---|---|
zlbytes |
uint32_t |
4 字節 |
記錄整個壓縮列表佔用的內存字節數:在對壓縮列表進行內存重分配, 或者計算 zlend 的位置時使用。 |
zltail |
uint32_t |
4 字節 |
記錄壓縮列表表尾節點距離壓縮列表的起始地址有多少字節: 經過這個偏移量,程序無須遍歷整個壓縮列表就能夠肯定表尾節點的地址。 |
zllen |
uint16_t |
2 字節 |
記錄了壓縮列表包含的節點數量: 當這個屬性的值小於 UINT16_MAX (65535 )時, 這個屬性的值就是壓縮列表包含節點的數量; 當這個值等於 UINT16_MAX 時, 節點的真實數量須要遍歷整個壓縮列表才能計算得出。 |
entryX |
列表節點 | 不定 | 壓縮列表包含的各個節點,節點的長度由節點保存的內容決定。 |
zlend |
uint8_t |
1 字節 |
特殊值 0xFF (十進制 255 ),用於標記壓縮列表的末端。 |
每一個壓縮列表節點都由 previous_entry_length
、 encoding
、 content
三個部分組成, 如圖 :
每一個節點的 previous_entry_length
屬性都記錄了前一個節點的長度:
254
字節, 那麼 previous_entry_length
屬性須要用 1
字節長的空間來保存這個長度值。254
字節, 那麼 previous_entry_length
屬性須要用 5
字節長的空間來保存這個長度值。重點回顧¶
readobject
typedef struct redisObject { // 類型 unsigned type:4; // 編碼 unsigned encoding:4; // 指向底層實現數據結構的指針 void *ptr; // ... } robj;
對象的編碼
編碼常量 | 編碼所對應的底層數據結構 |
---|---|
REDIS_ENCODING_INT |
long 類型的整數 |
REDIS_ENCODING_EMBSTR |
embstr 編碼的簡單動態字符串 |
REDIS_ENCODING_RAW |
簡單動態字符串 |
REDIS_ENCODING_HT |
字典 |
REDIS_ENCODING_LINKEDLIST |
雙端鏈表 |
REDIS_ENCODING_ZIPLIST |
壓縮列表 |
REDIS_ENCODING_INTSET |
整數集合 |
REDIS_ENCODING_SKIPLIST |
跳躍表和字典 |
不一樣類型和編碼的對象
類型 | 編碼 | 對象 |
---|---|---|
REDIS_STRING |
REDIS_ENCODING_INT |
使用整數值實現的字符串對象。 |
REDIS_STRING |
REDIS_ENCODING_EMBSTR |
使用 embstr 編碼的簡單動態字符串實現的字符串對象。 |
REDIS_STRING |
REDIS_ENCODING_RAW |
使用簡單動態字符串實現的字符串對象。 |
REDIS_LIST |
REDIS_ENCODING_ZIPLIST |
使用壓縮列表實現的列表對象。 |
REDIS_LIST |
REDIS_ENCODING_LINKEDLIST |
使用雙端鏈表實現的列表對象。 |
REDIS_HASH |
REDIS_ENCODING_ZIPLIST |
使用壓縮列表實現的哈希對象。 |
REDIS_HASH |
REDIS_ENCODING_HT |
使用字典實現的哈希對象。 |
REDIS_SET |
REDIS_ENCODING_INTSET |
使用整數集合實現的集合對象。 |
REDIS_SET |
REDIS_ENCODING_HT |
使用字典實現的集合對象。 |
REDIS_ZSET |
REDIS_ENCODING_ZIPLIST |
使用壓縮列表實現的有序集合對象。 |
REDIS_ZSET |
REDIS_ENCODING_SKIPLIST |
使用跳躍表和字典實現的有序集合對象。 |
OBJECT ENCODING 對不一樣編碼的輸出
對象所使用的底層數據結構 | 編碼常量 | OBJECT ENCODING 命令輸出 |
---|---|---|
整數 | REDIS_ENCODING_INT |
"int" |
embstr 編碼的簡單動態字符串(SDS) |
REDIS_ENCODING_EMBSTR |
"embstr" |
簡單動態字符串 | REDIS_ENCODING_RAW |
"raw" |
字典 | REDIS_ENCODING_HT |
"hashtable" |
雙端鏈表 | REDIS_ENCODING_LINKEDLIST |
"linkedlist" |
壓縮列表 | REDIS_ENCODING_ZIPLIST |
"ziplist" |
整數集合 | REDIS_ENCODING_INTSET |
"intset" |
跳躍表和字典 | REDIS_ENCODING_SKIPLIST |
"skiplist" |
字符串對象的編碼能夠是 int
、 raw
或者 embstr
(特殊,短string,不能修改)
命令 | int 編碼的實現方法 |
embstr 編碼的實現方法 |
raw 編碼的實現方法 |
---|---|---|---|
SET | 使用 int 編碼保存值。 |
使用 embstr 編碼保存值。 |
使用 raw 編碼保存值。 |
GET | 拷貝對象所保存的整數值, 將這個拷貝轉換成字符串值, 而後向客戶端返回這個字符串值。 | 直接向客戶端返回字符串值。 | 直接向客戶端返回字符串值。 |
APPEND | 將對象轉換成 raw 編碼, 而後按 raw 編碼的方式執行此操做。 |
將對象轉換成 raw 編碼, 而後按 raw 編碼的方式執行此操做。 |
調用 sdscatlen 函數, 將給定字符串追加到現有字符串的末尾。 |
INCRBYFLOAT | 取出整數值並將其轉換成 longdouble 類型的浮點數, 對這個浮點數進行加法計算, 而後將得出的浮點數結果保存起來。 |
取出字符串值並嘗試將其轉換成long double 類型的浮點數, 對這個浮點數進行加法計算, 而後將得出的浮點數結果保存起來。 若是字符串值不能被轉換成浮點數, 那麼向客戶端返回一個錯誤。 |
取出字符串值並嘗試將其轉換成 longdouble 類型的浮點數, 對這個浮點數進行加法計算, 而後將得出的浮點數結果保存起來。 若是字符串值不能被轉換成浮點數, 那麼向客戶端返回一個錯誤。 |
INCRBY | 對整數值進行加法計算, 得出的計算結果會做爲整數被保存起來。 | embstr 編碼不能執行此命令, 向客戶端返回一個錯誤。 |
raw 編碼不能執行此命令, 向客戶端返回一個錯誤。 |
DECRBY | 對整數值進行減法計算, 得出的計算結果會做爲整數被保存起來。 | embstr 編碼不能執行此命令, 向客戶端返回一個錯誤。 |
raw 編碼不能執行此命令, 向客戶端返回一個錯誤。 |
STRLEN | 拷貝對象所保存的整數值, 將這個拷貝轉換成字符串值, 計算並返回這個字符串值的長度。 | 調用 sdslen 函數, 返回字符串的長度。 |
調用 sdslen 函數, 返回字符串的長度。 |
SETRANGE | 將對象轉換成 raw 編碼, 而後按 raw 編碼的方式執行此命令。 |
將對象轉換成 raw 編碼, 而後按 raw 編碼的方式執行此命令。 |
將字符串特定索引上的值設置爲給定的字符。 |
GETRANGE | 拷貝對象所保存的整數值, 將這個拷貝轉換成字符串值, 而後取出並返回字符串指定索引上的字符。 | 直接取出並返回字符串指定索引上的字符。 | 直接取出並返回字符串指定索引上的字符。 |
列表對象的編碼能夠是 ziplist
或者 linkedlis
列表命令的實現
命令 | ziplist 編碼的實現方法 |
linkedlist 編碼的實現方法 |
---|---|---|
LPUSH | 調用 ziplistPush 函數, 將新元素推入到壓縮列表的表頭。 |
調用 listAddNodeHead 函數, 將新元素推入到雙端鏈表的表頭。 |
RPUSH | 調用 ziplistPush 函數, 將新元素推入到壓縮列表的表尾。 |
調用 listAddNodeTail 函數, 將新元素推入到雙端鏈表的表尾。 |
LPOP | 調用 ziplistIndex 函數定位壓縮列表的表頭節點, 在向用戶返回節點所保存的元素以後, 調用 ziplistDelete 函數刪除表頭節點。 |
調用 listFirst 函數定位雙端鏈表的表頭節點, 在向用戶返回節點所保存的元素以後, 調用 listDelNode 函數刪除表頭節點。 |
RPOP | 調用 ziplistIndex 函數定位壓縮列表的表尾節點, 在向用戶返回節點所保存的元素以後, 調用 ziplistDelete 函數刪除表尾節點。 |
調用 listLast 函數定位雙端鏈表的表尾節點, 在向用戶返回節點所保存的元素以後, 調用 listDelNode 函數刪除表尾節點。 |
LINDEX | 調用 ziplistIndex 函數定位壓縮列表中的指定節點, 而後返回節點所保存的元素。 |
調用 listIndex 函數定位雙端鏈表中的指定節點, 而後返回節點所保存的元素。 |
LLEN | 調用 ziplistLen 函數返回壓縮列表的長度。 |
調用 listLength 函數返回雙端鏈表的長度。 |
LINSERT | 插入新節點到壓縮列表的表頭或者表尾時, 使用 ziplistPush 函數; 插入新節點到壓縮列表的其餘位置時, 使用 ziplistInsert 函數。 |
調用 listInsertNode 函數, 將新節點插入到雙端鏈表的指定位置。 |
LREM | 遍歷壓縮列表節點, 並調用 ziplistDelete 函數刪除包含了給定元素的節點。 |
遍歷雙端鏈表節點, 並調用 listDelNode 函數刪除包含了給定元素的節點。 |
LTRIM | 調用 ziplistDeleteRange 函數, 刪除壓縮列表中全部不在指定索引範圍內的節點。 |
遍歷雙端鏈表節點, 並調用 listDelNode 函數刪除鏈表中全部不在指定索引範圍內的節點。 |
LSET | 調用 ziplistDelete 函數, 先刪除壓縮列表指定索引上的現有節點, 而後調用 ziplistInsert 函數, 將一個包含給定元素的新節點插入到相同索引上面。 |
調用 listIndex 函數, 定位到雙端鏈表指定索引上的節點, 而後經過賦值操做更新節點的值 |
哈希命令的實現
命令 | ziplist 編碼實現方法 |
hashtable 編碼的實現方法 |
---|---|---|
HSET | 首先調用 ziplistPush 函數, 將鍵推入到壓縮列表的表尾, 而後再次調用 ziplistPush 函數, 將值推入到壓縮列表的表尾。 |
調用 dictAdd 函數, 將新節點添加到字典裏面。 |
HGET | 首先調用 ziplistFind 函數, 在壓縮列表中查找指定鍵所對應的節點, 而後調用 ziplistNext 函數, 將指針移動到鍵節點旁邊的值節點, 最後返回值節點。 |
調用 dictFind 函數, 在字典中查找給定鍵, 而後調用 dictGetVal 函數, 返回該鍵所對應的值。 |
HEXISTS | 調用 ziplistFind 函數, 在壓縮列表中查找指定鍵所對應的節點, 若是找到的話說明鍵值對存在, 沒找到的話就說明鍵值對不存在。 |
調用 dictFind 函數, 在字典中查找給定鍵, 若是找到的話說明鍵值對存在, 沒找到的話就說明鍵值對不存在。 |
HDEL | 調用 ziplistFind 函數, 在壓縮列表中查找指定鍵所對應的節點, 而後將相應的鍵節點、 以及鍵節點旁邊的值節點都刪除掉。 |
調用 dictDelete 函數, 將指定鍵所對應的鍵值對從字典中刪除掉。 |
HLEN | 調用 ziplistLen 函數, 取得壓縮列表包含節點的總數量, 將這個數量除以 2 , 得出的結果就是壓縮列表保存的鍵值對的數量。 |
調用 dictSize 函數, 返回字典包含的鍵值對數量, 這個數量就是哈希對象包含的鍵值對數量。 |
HGETALL | 遍歷整個壓縮列表, 用 ziplistGet 函數返回全部鍵和值(都是節點)。 |
遍歷整個字典, 用 dictGetKey 函數返回字典的鍵, 用 dictGetVal 函數返回字典的值。 |
集合命令的實現方法
命令 | intset 編碼的實現方法 |
hashtable 編碼的實現方法 |
---|---|---|
SADD | 調用 intsetAdd 函數, 將全部新元素添加到整數集合裏面。 |
調用 dictAdd , 以新元素爲鍵, NULL 爲值, 將鍵值對添加到字典裏面。 |
SCARD | 調用 intsetLen 函數, 返回整數集合所包含的元素數量, 這個數量就是集合對象所包含的元素數量。 |
調用 dictSize 函數, 返回字典所包含的鍵值對數量, 這個數量就是集合對象所包含的元素數量。 |
SISMEMBER | 調用 intsetFind 函數, 在整數集合中查找給定的元素, 若是找到了說明元素存在於集合, 沒找到則說明元素不存在於集合。 |
調用 dictFind 函數, 在字典的鍵中查找給定的元素, 若是找到了說明元素存在於集合, 沒找到則說明元素不存在於集合。 |
SMEMBERS | 遍歷整個整數集合, 使用 intsetGet 函數返回集合元素。 |
遍歷整個字典, 使用 dictGetKey 函數返回字典的鍵做爲集合元素。 |
SRANDMEMBER | 調用 intsetRandom 函數, 從整數集合中隨機返回一個元素。 |
調用 dictGetRandomKey 函數, 從字典中隨機返回一個字典鍵。 |
SPOP | 調用 intsetRandom 函數, 從整數集合中隨機取出一個元素, 在將這個隨機元素返回給客戶端以後, 調用 intsetRemove 函數, 將隨機元素從整數集合中刪除掉。 |
調用 dictGetRandomKey 函數, 從字典中隨機取出一個字典鍵, 在將這個隨機字典鍵的值返回給客戶端以後, 調用 dictDelete 函數, 從字典中刪除隨機字典鍵所對應的鍵值對。 |
SREM | 調用 intsetRemove 函數, 從整數集合中刪除全部給定的元素。 |
調用 dictDelete 函數, 從字典中刪除全部鍵爲給定元素的鍵值對。 |
(4)集合對象
集合命令的實現方法
命令 | intset 編碼的實現方法 |
hashtable 編碼的實現方法 |
---|---|---|
SADD | 調用 intsetAdd 函數, 將全部新元素添加到整數集合裏面。 |
調用 dictAdd , 以新元素爲鍵, NULL 爲值, 將鍵值對添加到字典裏面。 |
SCARD | 調用 intsetLen 函數, 返回整數集合所包含的元素數量, 這個數量就是集合對象所包含的元素數量。 |
調用 dictSize 函數, 返回字典所包含的鍵值對數量, 這個數量就是集合對象所包含的元素數量。 |
SISMEMBER | 調用 intsetFind 函數, 在整數集合中查找給定的元素, 若是找到了說明元素存在於集合, 沒找到則說明元素不存在於集合。 |
調用 dictFind 函數, 在字典的鍵中查找給定的元素, 若是找到了說明元素存在於集合, 沒找到則說明元素不存在於集合。 |
SMEMBERS | 遍歷整個整數集合, 使用 intsetGet 函數返回集合元素。 |
遍歷整個字典, 使用 dictGetKey 函數返回字典的鍵做爲集合元素。 |
SRANDMEMBER | 調用 intsetRandom 函數, 從整數集合中隨機返回一個元素。 |
調用 dictGetRandomKey 函數, 從字典中隨機返回一個字典鍵。 |
SPOP | 調用 intsetRandom 函數, 從整數集合中隨機取出一個元素, 在將這個隨機元素返回給客戶端以後, 調用 intsetRemove 函數, 將隨機元素從整數集合中刪除掉。 |
調用 dictGetRandomKey 函數, 從字典中隨機取出一個字典鍵, 在將這個隨機字典鍵的值返回給客戶端以後, 調用 dictDelete 函數, 從字典中刪除隨機字典鍵所對應的鍵值對。 |
SREM | 調用 intsetRemove 函數, 從整數集合中刪除全部給定的元素。 |
調用 dictDelete 函數, 從字典中刪除全部鍵爲給定元素的鍵值對。 |
對象的引用計數信息會隨着對象的使用狀態而不斷變化:
1
;0
時, 對象所佔用的內存會被釋放。Redis 中, 讓多個鍵共享同一個值對象須要執行如下兩個步驟:
目前來講, Redis 會在初始化服務器時, 建立一萬個字符串對象, 這些對象包含了從 0
到 9999
的全部整數值, 當服務器須要用到值爲 0
到 9999
的字符串對象時, 服務器就會使用這些共享對象, 而不是新建立對象。
爲何 Redis 不共享包含字符串的對象?
當服務器考慮將一個共享對象設置爲鍵的值對象時, 程序須要先檢查給定的共享對象和鍵想建立的目標對象是否徹底相同, 只有在共享對象和目標對象徹底相同的狀況下, 程序纔會將共享對象用做鍵的值對象, 而一個共享對象保存的值越複雜, 驗證共享對象和目標對象是否相同所需的複雜度就會越高, 消耗的 CPU 時間也會越多:
所以, 儘管共享更復雜的對象能夠節約更多的內存, 但受到 CPU 時間的限制, Redis 只對包含整數值的字符串對象進行共享。
鍵的空轉時長還有另一項做用: 若是服務器打開了 maxmemory
選項, 而且服務器用於回收內存的算法爲 volatile-lru
或者 allkeys-lru
, 那麼當服務器佔用的內存數超過了 maxmemory
選項所設置的上限值時, 空轉時長較高的那部分鍵會優先被服務器釋放, 從而回收內存。
0
到 9999
的字符串對象。