1. 程式人生 > 其它 >redis5.0.2原始碼分析——物件系統

redis5.0.2原始碼分析——物件系統

  Redis是一個開源的 key-value 儲存系統,它使用六種底層資料結構構建了包含字串物件、列表物件、雜湊物件、集合物件和有序集合物件的物件系統。今天我們就通過12張圖來全面瞭解一下它的資料結構和物件系統的實現原理。

本文的內容如下:

  • 首先介紹六種基礎資料結構:動態字串,連結串列,字典,跳躍表,整數集合和壓縮列表。

  • 其次介紹 Redis 的物件系統中的字串物件(String)、列表物件(List)、雜湊物件(Hash)、集合物件(Set)和有序集合物件(ZSet)。

  • 最後介紹 Redis 的鍵空間和過期鍵( expire )實現。

資料結構

簡單動態字串

Redis 使用動態字串 SDS 來表示字串值。下圖展示了一個值為 Redis 的 SDS結構 :

  • len: 表示字串的真正長度(不包含NULL結束符在內)。

  • alloc: 表示字串的最大容量(不包含最後多餘的那個位元組)。

  • flags: 總是佔用一個位元組。其中的最低3個bit用來表示header的型別。

  • buf: 字元陣列。

SDS 的結構可以減少修改字串時帶來的記憶體重分配的次數,這依賴於記憶體預分配和惰性空間釋放兩大機制。

當 SDS 需要被修改,並且要對 SDS 進行空間擴充套件時,Redis 不僅會為 SDS 分配修改所必須要的空間,還會為 SDS 分配額外的未使用的空間

  • 如果修改後, SDS 的長度(也就是len屬性的值)將小於 1MB ,那麼 Redis 預分配和 len 屬性相同大小的未使用空間。

  • 如果修改後, SDS 的長度將大於 1MB ,那麼 Redis 會分配 1MB 的未使用空間。

比如說,進行修改後 SDS 的 len 長度為20位元組,小於 1MB,那麼 Redis 會預先再分配 20 位元組的空間, SDS 的 buf陣列的實際長度(除去最後一位元組)變為 20 + 20 = 40 位元組。當 SDS的 len 長度大於 1MB時,則只會再多分配 1MB的空間。

類似的,當 SDS 縮短其儲存的字串長度時,並不會立即釋放多出來的位元組,而是等待之後使用。

連結串列

連結串列在 Redis 中的應用非常廣泛,比如列表物件的底層實現之一就是連結串列。除了連結串列物件外,釋出和訂閱、慢查詢、監視器等功能也用到了連結串列。

Redis 的連結串列是雙向連結串列,示意圖如上圖所示。連結串列是最為常見的資料結構,這裡就不在細說。

Redis 的連結串列結構的dup 、 free 和 match 成員屬性是用於實現多型連結串列所需的型別特定函式:

  • dup 函式用於複製連結串列節點所儲存的值,用於深度拷貝。

  • free 函式用於釋放連結串列節點所儲存的值。

  • match 函式則用於對比連結串列節點所儲存的值和另一個輸入值是否相等。

字典

字典被廣泛用於實現 Redis 的各種功能,包括鍵空間和雜湊物件。其示意圖如下所示。

Redis 使用 MurmurHash2 演算法來計算鍵的雜湊值,並且使用鏈地址法來解決鍵衝突,被分配到同一個索引的多個鍵值對會連線成一個單向連結串列。

跳躍表

  Redis 使用跳躍表作為有序集合物件的底層實現之一。它以有序的方式在層次化的連結串列中儲存元素, 效率和平衡樹媲美 —— 查詢、刪除、新增等操作都可以在對數期望時間下完成, 並且比起平衡樹來說, 跳躍表的實現要簡單直觀得多。

  跳錶的示意圖如上圖所示,這裡只簡單說一下它的核心思想,並不進行詳細的解釋。

  如示意圖所示,zskiplistNode 是跳躍表的節點,其 ele 是保持的元素值,score 是分值,節點按照其 score 值進行有序排列,而 level 陣列就是其所謂的層次化連結串列的體現。

  每個 node 的 level 陣列大小都不同, level 陣列中的值是指向下一個 node 的指標和 跨度值 (span),跨度值是兩個節點的score的差值。越高層的 level 陣列值的跨度值就越大,底層的 level 陣列值的跨度值越小。

  level 陣列就像是不同刻度的尺子。度量長度時,先用大刻度估計範圍,再不斷地用縮小刻度,進行精確逼近。

  當在跳躍表中查詢一個元素值時,都先從第一個節點的最頂層的 level 開始。比如說,在上圖的跳錶中查詢 o2 元素時,先從o1 的節點開始,因為 zskiplist 的 header 指標指向它。

  先從其 level[3] 開始查詢,發現其跨度是 2,o1 節點的 score 是1.0,所以加起來為 3.0,大於 o2 的 score 值2.0。所以,我們可以知道 o2 節點在 o1 和 o3 節點之間。這時,就改用小刻度的尺子了。就用level[1]的指標,順利找到 o2 節點。

整數集合

整數集合 intset 是集合物件的底層實現之一,當一個集合只包含整數值元素,並且這個集合的元素數量不多時, Redis 就會使用整數集合作為集合物件的底層實現。

  如上圖所示,整數集合的 encoding 表示它的型別,有int16t,int32t 或者int64_t。其每個元素都是 contents 陣列的一個數組項,各個項在陣列中按值的大小從小到大有序的排列,並且陣列中不包含任何重複項。length 屬性就是整數集合包含的元素數量。

壓縮列表

壓縮佇列 ziplist 是列表物件和雜湊物件的底層實現之一。當滿足一定條件時,列表物件和雜湊物件都會以壓縮佇列為底層實現。

壓縮佇列是 Redis 為了節約記憶體而開發的,是由一系列特殊編碼的連續記憶體塊組成的順序型資料結構。它的屬性值有:

  • zlbytes : 長度為 4 位元組,記錄整個壓縮陣列的記憶體位元組數。

  • zltail : 長度為 4 位元組,記錄壓縮隊列表尾節點距離壓縮佇列的起始地址有多少位元組,通過該屬性可以直接確定尾節點的地址。

  • zllen : 長度為 2 位元組,包含的節點數。當屬性值小於 INT16_MAX時,該值就是節點總數,否則需要遍歷整個佇列才能確定總數。

  • zlend : 長度為 1 位元組,特殊值,用於標記壓縮佇列的末端。

中間每個節點 entry 由三部分組成:

  • previous_entry_length : 壓縮列表中前一個節點的長度,和當前的地址進行指標運算,計算出前一個節點的起始地址。

  • encoding: 節點儲存資料的型別和長度

  • content :節點值,可以為一個位元組陣列或者整數。

Redis 快速列表(quicklist)

quicklist結構是在redis 5.0.2版本中用在列表的底層實現。

  quicklist結構在quicklist.c中的解釋為A doubly linked list of ziplists意思為一個由ziplist組成的雙向連結串列。quicklist是由ziplist組成的雙向連結串列,連結串列中的每一個節點都以壓縮列表ziplist的結構儲存著資料,而ziplist有多個entry節點,儲存著資料。相當與一個quicklist節點儲存的是一片資料,而不再是一個數據。

壓縮列表的特點:

  • 壓縮列表ziplist結構本身就是一個連續的記憶體塊,由表頭、若干個entry節點和壓縮列表尾部識別符號zlend組成,通過一系列編碼規則,提高記憶體的利用率,使用於儲存整數和短字串。
  • 壓縮列表ziplist結構的缺點是:每次插入或刪除一個元素時,都需要進行頻繁的呼叫realloc()函式進行記憶體的擴充套件或減小,然後進行資料”搬移”,甚至可能引發連鎖更新,造成嚴重效率的損失。

總結出一下quicklist的特點:

  • quicklist巨集觀上是一個雙向連結串列,因此,它具有一個雙向連結串列的有點,進行插入或刪除操作時非常方便,雖然複雜度為O(n),但是不需要記憶體的複製,提高了效率,而且訪問兩端元素複雜度為O(1)。
  • quicklist微觀上是一片片entry節點,每一片entry節點記憶體連續且順序儲存,可以通過二分查詢以 log2(n)log2(n) 的複雜度進行定位。

Redis 物件系統

1. 介紹

  redis中基於雙端連結串列、簡單動態字串(sds)、字典、跳躍表、整數集合、壓縮列表、快速列表等等資料結構實現了一個物件系統,並且實現了5種不同的物件,每種物件都使用了至少一種前面的資料結構,優化物件在不同場合下的使用效率。

2. 物件的系統的實現

  上面介紹了 7種底層資料結構,Redis 並沒有直接使用這些資料結構來實現鍵值資料庫,而是基於這些資料結構建立了一個物件系統,這個系統包含字串物件、列表物件、雜湊物件、集合物件、有序集合以及流Stream這六種型別的物件,每個物件都使用到了至少一種前邊講的底層資料結構。

Redis 根據不同的使用場景和內容大小來判斷物件使用哪種資料結構,從而優化物件在不同場景下的使用效率和記憶體佔用。

2.1 物件的結構

物件結構robj功能:

  • 為6種不同的物件型別提供同一的表示形式。
  • 為不同的物件適用於不同的場景,支援同一種物件型別採用多種的資料結構方式。
  • 支援引用計數,實現物件共享機制。
  • 記錄物件的訪問時間,便於刪除物件。

物件結構定義在redis 5.0.2版本的server.h

 1 /**
 2  * Objects encoding. Some kind of objects like Strings and Hashes can be
 3  * internally represented in multiple ways. The 'encoding' field of the object
 4  * is set to one of this fields for this object.
 5  * encoding 的11種類型
 6  */
 7 //原始表示方式,字串物件是簡單動態字串
 8 #define OBJ_ENCODING_RAW 0     /* Raw representation */
 9 //long型別的整數
10 #define OBJ_ENCODING_INT 1     /* Encoded as integer */
11 //字典
12 #define OBJ_ENCODING_HT 2      /* Encoded as hash table */
13 //不在使用
14 #define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
15 //雙端連結串列,不在使用
16 #define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
17 //壓縮列表
18 #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
19 //整數集合
20 #define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
21 //跳躍表和字典
22 #define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
23 //embstr編碼的簡單動態字串
24 #define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
25 //由使用壓縮演算法的壓縮列表或未使用壓縮演算法的壓縮列表組成的雙向列表-->快速列表
26 #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
27 //Stream(流物件)的底層儲存結構為字首壓縮樹
28 #define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
29 
30 #define LRU_BITS 24
31 #define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
32 #define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
33 
34 #define OBJ_SHARED_REFCOUNT INT_MAX
35 /**
36  * https://blog.csdn.net/qq_35433716/article/details/82179168
37  * type:儲存物件的型別,Redis中有5中資料型別:String,List,Hash,Set,Zset,
38  *      可以通過 type {key}命令檢視物件的型別,返回的是值物件型別,所有的key物件都是String型別
39  * encoding:資料儲存的Redis中後採用的是那種內部編碼格式,這個後邊會細講一下
40  * lru:記錄的是物件被最後一次訪問的時間,當配置了maxmemory之後,配合LRU演算法對相關的key值進行刪除,
41  *     可以通過object idletime {key}檢視key最近一次被訪問的時間。
42  *     也可以通過scan + object idletime命令批量查詢那些鍵長時間沒有被使用,從而可以刪除長時間沒有被使用的鍵值,
43  *     減少記憶體的佔用。
44  * refcount:記錄當前物件被引用的次數。根據當前欄位來判斷該物件時候可回收,當refcount為0時,
45  *          可安全進行物件的回收,可以使用object refcount {key}檢視當前物件引用。
46  * ptr:與物件的資料內容有關。如果是整數,則直接儲存資料(這個地方可以瞭解下共享物件池,
47  *     當物件為整數且範圍在【0-9999】,會直接儲存到共享物件池中),其他型別的資料次欄位則代表的是指標。
48  * redisObject的結構與物件型別、編碼、記憶體回收、共享物件都有關係;一個redisObject物件的大小為16位元組:
49  *      4bit+4bit+24bit+4Byte+8Byte=16Byte。
50  */
51 typedef struct redisObject {
52     unsigned type:4;
53     unsigned encoding:4;
54     unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
55                             * LFU data (least significant 8 bits frequency
56                             * and most significant 16 bits access time). */
57     int refcount;
58     void *ptr;
59 } robj;

其中 type 是物件型別,如下

 1 /* The actual Redis Object 7中物件型別*/
 2 #define OBJ_STRING 0    /* String object. 字串*/
 3 #define OBJ_LIST 1      /* List object. 連結串列*/
 4 #define OBJ_SET 2       /* Set object. 集合*/
 5 #define OBJ_ZSET 3      /* Sorted set object. 有序集合*/
 6 #define OBJ_HASH 4      /* Hash object. 雜湊表*/
 7 
 8 /* The "module" object type is a special one that signals that the object
 9  * is one directly managed by a Redis module. In this case the value points
10  * to a moduleValue struct, which contains the object value (which is only
11  * handled by the module itself) and the RedisModuleType struct which lists
12  * function pointers in order to serialize, deserialize, AOF-rewrite and
13  * free the object.
14  *
15  * Inside the RDB file, module types are encoded as OBJ_MODULE followed
16  * by a 64 bit module type ID, which has a 54 bits module-specific signature
17  * in order to dispatch the loading to the right module, plus a 10 bits
18  * encoding version. */
19 #define OBJ_MODULE 5    /* Module object. */
20 #define OBJ_STREAM 6    /* Stream object. 流物件*/

2.2 字串物件的底層實現型別

我們首先來看字串物件的實現,如下圖所示。

  如果一個字串物件儲存的是一個字串值,並且長度大於44位元組,那麼該字串物件將使用 SDS 進行儲存,並將物件的編碼設定為 raw,如圖的上半部分所示。如果字串的長度小於32位元組,那麼字串物件將使用embstr 編碼方式來儲存。

  embstr 編碼是專門用於儲存短字串的一種優化編碼方式,這個編碼的組成和 raw 編碼一致,都使用 redisObject 結構和 sdshdr 結構來儲存字串,如上圖的下半部所示。

  但是 raw 編碼會呼叫兩次記憶體分配來分別建立上述兩個結構,而 embstr 則通過一次記憶體分配來分配一塊連續的空間,空間中一次包含兩個結構。

  embstr 只需一次記憶體分配,而且在同一塊連續的記憶體中,更好的利用快取帶來的優勢,但是 embstr 是隻讀的,不能進行修改,當一個 embstr 編碼的字串物件進行 append 操作時, redis 會現將其轉變為 raw 編碼再進行操作。

編碼—encoding物件—ptr
OBJ_ENCODING_RAW 簡單動態字串實現的字串物件
OBJ_ENCODING_INT 整數值實現的字串物件
OBJ_ENCODING_EMBSTR embstr編碼的簡單動態字串實現的字串物件

2.3 列表物件的底層實現型別

列表物件的編碼可以是 ziplist 或 linkedlist。其示意圖如下所示。

當列表物件可以同時滿足以下兩個條件時,列表物件使用 ziplist 編碼:

  • 列表物件儲存的所有字串元素的長度都小於 64 位元組。

  • 列表物件儲存的元素數量數量小於 512 個。

不能滿足這兩個條件的列表物件需要使用 linkedlist 編碼或者轉換為 linkedlist 編碼。

編碼—encoding物件—ptr
OBJ_ENCODING_QUICKLIST 快速列表實現的列表物件
OBJ_ENCODING_ZIPLIST 壓縮列表實現的列表物件

2.4 集合物件的底層實現型別

集合物件的編碼可以使用 intset 或者 dict。

intset 編碼的集合物件使用整數集合最為底層實現,所有元素都被儲存在整數集合裡邊。

而使用 dict 進行編碼時,字典的每一個鍵都是一個字串物件,每個字串物件就是一個集合元素,而字典的值全部都被設定為NULL。如下圖所示。

當集合物件可以同時滿足以下兩個條件時,物件使用 intset 編碼:

  • 集合物件儲存的所有元素都是整數值。

  • 集合物件儲存的元素數量不超過512個。

否則使用 dict 進行編碼。

編碼—encoding物件—ptr
OBJ_ENCODING_HT 字典實現的集合物件
OBJ_ENCODING_INTSET 整數集合實現的集合物件


2.5 雜湊物件的底層實現型別

雜湊物件的編碼可以使用 ziplist 或 dict。其示意圖如下所示。

當雜湊物件使用壓縮佇列作為底層實現時,程式將鍵值對緊挨著插入到壓縮佇列中,儲存鍵的節點在前,儲存值的節點在後。如下圖的上半部分所示,該雜湊有兩個鍵值對,分別是 name:Tom 和 age:25。

當雜湊物件可以同時滿足以下兩個條件時,雜湊物件使用 ziplist 編碼:

  • 雜湊物件儲存的所有鍵值對的鍵和值的字串長度都小於64位元組。

  • 雜湊物件儲存的鍵值對數量小於512個。

不能滿足這兩個條件的雜湊物件需要使用 dict 編碼或者轉換為 dict 編碼。

編碼—encoding物件—ptr
OBJ_ENCODING_ZIPLIST 壓縮列表實現的雜湊物件
OBJ_ENCODING_HT 字典實現的雜湊物件

2.6 有序集合物件的底層實現型別

  有序集合的編碼可以為 ziplist 或者 skiplist。

  有序集合使用 ziplist 編碼時,每個集合元素使用兩個緊挨在一起的壓縮列表節點表示,前一個節點是元素的值,第二個節點是元素的分值,也就是排序比較的數值。

  壓縮列表內的集合元素按照分值從小到大進行排序,如下圖上半部分所示。

  有序集合使用 skiplist 編碼時使用 zset 結構作為底層實現,一個 zet 結構同時包含一個字典和一個跳躍表。

  其中,跳躍表按照分值從小到大儲存所有元素,每個跳躍表節點儲存一個元素,其score值是元素的分值。而字典則建立一個一個從成員到分值的對映,字典的鍵是集合成員的值,字典的值是集合成員的分值。通過字典可以在O(1)複雜度查詢給定成員的分值。如下圖所示。

跳躍表和字典中的集合元素值物件都是共享的,所以不會額外消耗記憶體。

當有序集合物件可以同時滿足以下兩個條件時,物件使用 ziplist 編碼:

  • 有序集合儲存的元素數量少於128個;

  • 有序集合儲存的所有元素的長度都小於64位元組。

否則使用 skiplist 編碼。

2.7 流物件的底層實現型別

https://blog.csdn.net/alpha_love/article/details/116568776?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242(後續需要詳細分析)

  OBJ_ENCODING_STREAM流物件是 5.0 版本引入的新的資料物件,與列表物件 List 極為相似,但是功能更為強大,帶有訂閱釋出的能力。其採用的儲存結構OBJ_ENCODING_STREAM與其他的儲存結構截然不同,OBJ_ENCODING_STREAM底層採用壓縮字首樹(radix tree) 來儲存,其每個節點 raxNode 用於儲存鍵值對相關資料,不同鍵相同的字首字元將被壓縮到同一個節點中,並使用 iskey 屬性來標識從根節點到當前節點儲存的字元是否是完整的鍵。

3. 資料庫鍵空間

  Redis 伺服器都有多個 Redis 資料庫,每個Redis 資料都有自己獨立的鍵值空間。每個 Redis 資料庫使用 dict 儲存資料庫中所有的鍵值對。

  鍵空間的鍵也就是資料庫的鍵,每個鍵都是一個字串物件,而值物件可能為字串物件、列表物件、雜湊表物件、集合物件和有序集合物件中的一種物件。

  除了鍵空間,Redis 也使用 dict 結構來儲存鍵的過期時間,其鍵是鍵空間中的鍵值,而值是過期時間,如上圖所示。

  通過過期字典,Redis 可以直接判斷一個鍵是否過期,首先檢視該鍵是否存在於過期字典,如果存在,則比較該鍵的過期時間和當前伺服器時間戳,如果大於,則該鍵過期,否則未過期。

4. 物件系統的重要操作

4.1建立一個字串物件

編碼為OBJ_ENCODING_RAW

 1 robj *createObject(int type, void *ptr) {   //建立一個物件
 2     robj *o = zmalloc(sizeof(*o));          //分配空間
 3     o->type = type;                         //設定物件型別
 4     o->encoding = OBJ_ENCODING_RAW;         //設定編碼方式為OBJ_ENCODING_RAW
 5     o->ptr = ptr;                           //設定
 6     o->refcount = 1;                        //引用計數為1
 7 
 8     /* Set the LRU to the current lruclock (minutes resolution). */
 9     o->lru = LRU_CLOCK();                   //計算設定當前LRU時間
10     return o;
11 }
12 
13 /* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain
14  * string object where o->ptr points to a proper sds string. */
15 robj *createRawStringObject(const char *ptr, size_t len) {
16     return createObject(OBJ_STRING, sdsnewlen(ptr,len));
17 }

編碼為OBJ_ENCODING_EMBSTR

 1 /* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
 2  * an object where the sds string is actually an unmodifiable string
 3  * allocated in the same chunk as the object itself. */
 4 //建立一個embstr編碼的字串物件
 5 robj *createEmbeddedStringObject(const char *ptr, size_t len) {
 6     robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);   //分配空間
 7     struct sdshdr8 *sh = (void*)(o+1);  //o+1剛好就是struct sdshdr8的地址
 8 
 9     o->type = OBJ_STRING;               //型別為字串物件
10     o->encoding = OBJ_ENCODING_EMBSTR;  //設定編碼型別OBJ_ENCODING_EMBSTR
11     o->ptr = sh+1;                      //指向分配的sds物件,分配的len+1的空間首地址
12     o->refcount = 1;                    //設定引用計數
13     o->lru = LRU_CLOCK();               //計算設定當前LRU時間
14 
15     sh->len = len;                      //設定字串長度
16     sh->alloc = len;                    //設定最大容量
17     sh->flags = SDS_TYPE_8;             //設定sds的型別
18     if (ptr) {                          //如果傳了字串引數
19         memcpy(sh->buf,ptr,len);        //將傳進來的ptr儲存到物件中
20         sh->buf[len] = '\0';            //結束符標誌
21     } else {
22         memset(sh->buf,0,len+1);        //否則將物件的空間初始化為0
23     }
24     return o;
25 }

兩種字串物件編碼方式的區別

 1 /* Create a string object with EMBSTR encoding if it is smaller than
 2  * REIDS_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
 3  * used.
 4  *
 5  * The current limit of 39 is chosen so that the biggest string object
 6  * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
 7 
 8 //sdshdr8的大小為3個位元組,加上1個結束符共4個位元組
 9 //redisObject的大小為16個位元組
10 //redis使用jemalloc記憶體分配器,且jemalloc會分配8,16,32,64等位元組的記憶體
11 //一個embstr固定的大小為16+3+1 = 20個位元組,因此一個最大的embstr字串為64-20 = 44位元組
12 #define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
13 
14 // 建立字串物件,根據長度使用不同的編碼型別
15 // createRawStringObject和createEmbeddedStringObject的區別是:
16 // createRawStringObject是當字串長度大於44位元組時,robj結構和sdshdr結構在記憶體上是分開的
17 // createEmbeddedStringObject是當字串長度小於等於44位元組時,robj結構和sdshdr結構在記憶體上是連續的
18 robj *createStringObject(const char *ptr, size_t len) {
19     if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
20         return createEmbeddedStringObject(ptr,len);
21     else
22         return createRawStringObject(ptr,len);
23 }

4.2 字串物件編碼的優化

 1 /* Try to encode a string object in order to save space */
 2 //嘗試優化字串物件的編碼方式以節約空間
 3 robj *tryObjectEncoding(robj *o) {
 4     long value;
 5     sds s = o->ptr;
 6     size_t len;
 7 
 8     /* Make sure this is a string object, the only type we encode
 9      * in this function. Other types use encoded memory efficient
10      * representations but are handled by the commands implementing
11      * the type. */
12     serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
13 
14     /* We try some specialized encoding only for objects that are
15      * RAW or EMBSTR encoded, in other words objects that are still
16      * in represented by an actually array of chars. */
17     //如果字串物件的編碼型別為RAW或EMBSTR時,才對其重新編碼
18     if (!sdsEncodedObject(o)) return o;
19 
20     /* It's not safe to encode shared objects: shared objects can be shared
21      * everywhere in the "object space" of Redis and may end in places where
22      * they are not handled. We handle them only as values in the keyspace. */
23     //如果refcount大於1,則說明物件的ptr指向的值是共享的,不對共享物件進行編碼
24      if (o->refcount > 1) return o;
25 
26     /* Check if we can represent this string as a long integer.
27      * Note that we are sure that a string larger than 20 chars is not
28      * representable as a 32 nor 64 bit integer. */
29     len = sdslen(s);            //獲得字串s的長度
30 
31     //如果len小於等於20,表示符合long long可以表示的範圍,且可以轉換為long型別的字串進行編碼
32     if (len <= 20 && string2l(s,len,&value)) {
33         /* This object is encodable as a long. Try to use a shared object.
34          * Note that we avoid using shared integers when maxmemory is used
35          * because every object needs to have a private LRU field for the LRU
36          * algorithm to work well. */
37         if ((server.maxmemory == 0 ||
38              (server.maxmemory_policy != MAXMEMORY_VOLATILE_LRU &&
39               server.maxmemory_policy != MAXMEMORY_ALLKEYS_LRU)) &&
40             value >= 0 &&
41             value < OBJ_SHARED_INTEGERS)    //如果value處於共享整數的範圍內
42         {
43             decrRefCount(o);                //原物件的引用計數減1,釋放物件
44             incrRefCount(shared.integers[value]); //增加共享物件的引用計數
45             return shared.integers[value];      //返回一個編碼為整數的字串物件
46         } else {        //如果不處於共享整數的範圍
47             if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);   //釋放編碼為OBJ_ENCODING_RAW的物件
48             o->encoding = OBJ_ENCODING_INT;     //轉換為OBJ_ENCODING_INT編碼
49             o->ptr = (void*) value;             //指標ptr指向value物件
50             return o;
51         }
52     }
53 
54     /* If the string is small and is still RAW encoded,
55      * try the EMBSTR encoding which is more efficient.
56      * In this representation the object and the SDS string are allocated
57      * in the same chunk of memory to save space and cache misses. */
58     //如果len小於44,44是最大的編碼為EMBSTR型別的字串物件長度
59     if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
60         robj *emb;
61 
62         if (o->encoding == OBJ_ENCODING_EMBSTR) return o;   //將RAW物件轉換為OBJ_ENCODING_EMBSTR編碼型別
63         emb = createEmbeddedStringObject(s,sdslen(s)); //建立一個編碼型別為OBJ_ENCODING_EMBSTR的字串物件
64         decrRefCount(o);    //釋放之前的物件
65         return emb;
66     }
67 
68     /* We can't encode the object...
69      *
70      * Do the last try, and at least optimize the SDS string inside
71      * the string object to require little space, in case there
72      * is more than 10% of free space at the end of the SDS string.
73      *
74      * We do that only for relatively large strings as this branch
75      * is only entered if the length of the string is greater than
76      * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
77     //無法進行編碼,但是如果s的未使用的空間大於使用空間的10分之1
78     if (o->encoding == OBJ_ENCODING_RAW &&
79         sdsavail(s) > len/10)
80     {
81         o->ptr = sdsRemoveFreeSpace(o->ptr);    //釋放所有的未使用空間
82     }
83 
84     /* Return the original object. */
85     return o;
86 }

4.3 引用計數管理物件

 1 //引用計數加1
 2 void incrRefCount(robj *o) {
 3     if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount++;
 4 }
 5 
 6 //引用計數減1
 7 void decrRefCount(robj *o) {
 8     //當引用物件等於1時,在操作引用計數減1,直接釋放物件的ptr和物件空間
 9     if (o->refcount == 1) {
10         switch(o->type) {
11         case OBJ_STRING: freeStringObject(o); break;
12         case OBJ_LIST: freeListObject(o); break;
13         case OBJ_SET: freeSetObject(o); break;
14         case OBJ_ZSET: freeZsetObject(o); break;
15         case OBJ_HASH: freeHashObject(o); break;
16         case OBJ_MODULE: freeModuleObject(o); break;
17         case OBJ_STREAM: freeStreamObject(o); break;
18         default: serverPanic("Unknown object type"); break;
19         }
20         zfree(o);
21     } else {
22         if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
23         if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;//否則減1
24     }
25 }

4.4 物件的複製,建立的物件非共享

 1 /**
 2  * Duplicate a string object, with the guarantee that the returned object
 3  * has the same encoding as the original one.
 4  *
 5  * This function also guarantees that duplicating a small integer object
 6  * (or a string object that contains a representation of a small integer)
 7  * will always result in a fresh object that is unshared (refcount == 1).
 8  *
 9  * The resulting object always has refcount set to 1.
10  * 返回 複製的o物件的副本的地址,且建立的物件非共享
11  */
12 robj *dupStringObject(const robj *o) {
13     robj *d;
14 
15     //一定是OBJ_STRING型別
16     serverAssert(o->type == OBJ_STRING);
17 
18     switch(o->encoding) {//根據不同的編碼型別
19     case OBJ_ENCODING_RAW:
20         return createRawStringObject(o->ptr,sdslen(o->ptr));//建立的物件非共享
21     case OBJ_ENCODING_EMBSTR:
22         return createEmbeddedStringObject(o->ptr,sdslen(o->ptr));//建立的物件非共享
23     case OBJ_ENCODING_INT://整數編碼型別
24         d = createObject(OBJ_STRING, NULL);//即使是共享整數範圍內的整數,建立的物件也是非共享的
25         d->encoding = OBJ_ENCODING_INT;
26         d->ptr = o->ptr;
27         return d;
28     default:
29         serverPanic("Wrong encoding.");
30         break;
31     }
32 }

4.5 物件的解碼操作

將儲存的整數值解碼成字串物件返回回來。

 1 /**
 2  * Get a decoded version of an encoded object (returned as a new object).
 3  * If the object is already raw-encoded just increment the ref count.
 4  * 將物件是整型的解碼為字串並返回,如果是字串編碼則直接返回輸入物件,只需增加引用計數
 5  */
 6 robj *getDecodedObject(robj *o) {
 7     robj *dec;
 8 
 9     //如果是OBJ_ENCODING_RAW或OBJ_ENCODING_EMBSTR型別的物件
10     if (sdsEncodedObject(o)) {
11         //增加引用計數,返回一個共享的物件
12         incrRefCount(o);
13         return o;
14     }
15     //如果是整數物件
16     if (o->type == OBJ_STRING && o->encoding == OBJ_ENCODING_INT) {
17         char buf[32];
18 
19         //將整數轉換為字串
20         ll2string(buf,32,(long)o->ptr);
21         //建立一個字串物件
22         dec = createStringObject(buf,strlen(buf));
23         return dec;
24     } else {
25         serverPanic("Unknown encoding type");
26     }
27 }

參考文章:

https://blog.csdn.net/men_wen/article/details/70257207

https://mp.weixin.qq.com/s/gQnuynv6XPD_aeIBQBeI2Q

https://blog.csdn.net/alpha_love/article/details/116568776?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242