前言
參考資料:《Redis設計與實現 第二版》;
本篇筆記按照書里的脈絡,將知識點分為四個部分。其中第一部分數據結構與對象分為上中下篇,上篇包括:SDS、鏈表和字典;中篇包括跳躍表、整數集合和壓縮列表;下篇為對象;
上篇的鏈接:https://www.cnblogs.com/dlhjw/p/15569578.html
中篇的鏈接:https://www.cnblogs.com/dlhjw/p/15582039.html
與本章相關的 Redis 命令總結在下篇文章,歡迎點擊收藏,本篇將不再重復:
《Redis常用命令及示例總結(API)》:https://www.cnblogs.com/dlhjw/p/15639773.html
1. Redis對象概述
-
Redis沒有直接使用前面介紹的數據結構來實現鍵值對數據庫,而是基於這些數據結構的對象系統;
-
Redis有五種基本對象,分別是字符串、列表、哈希、集合、有序集合。使用以下8種編碼方式中的幾種作為底層實現底層實現:long類型整數、embstr編碼的SDS、SDS、字典、雙端鏈表、壓縮列表、整數集合、跳躍表;
-
Redis在創建鍵值對時,至少會生成兩個對象,鍵對象和值對象;
1.1 對象的定義
-
Redis中的每個對象都由一個
redisObject結構來表示:typedef struct redisObject{ //類型 unsigned type:4; //編碼 unsigned encoding:4; //指向底層實現數據結構的的指針 void *ptr; //…… }-
類型
type的可選類型:類型常量 對象的名稱 TYPE命令的輸出 REDIS_STRING 字符串對象 string REDIS_LIST 列表對象 list REDIS_HASH 哈希對象 hash REDIS_SET 集合對象 set REDIS_ZSET 有序集合對象 zset -
編碼
encoding的可選類型;同種類型可以有不同的編碼形式:類型 對象 編碼 編碼方式 OBJECT ENCODING命令輸出 REDIS_STRING 字符串 REDIS_ENCODING_INT 使用整數數值實現 int REDIS_STRING 字符串 REDIS_ENCODING_EMBSTR 使用embstr編碼 embstr REDIS_STRING 字符串 REDIS_ENCODING_RAW 使用SDS實現 raw REDIS_LIST 列表 REDIS_ENCODING_ZIPLIST 使用壓縮列表實現 ziplist REDIS_LIST 列表 REDIS_ENCODING_LINKEDLIST 使用雙端鏈表實現 linkedlist REDIS_HASH 哈希 REDIS_ENCODING_ZIPLIST 使用壓縮列表實現 ziplist REDIS_HASH 哈希 REDIS_ENCODING_HT 使用字典實現 hashtable REDIS_SET 集合 REDIS_ENCODING_INTSET 使用整數集合實現 intset REDIS_SET 集合 REDIS_ENCODING_HT 使用字典實現 hashtable REDIS_ZSET 有序集合 REDIS_ENCODING_ZIPLIST 使用壓縮列表實現 ziplist REDIS_ZSET 有序集合 REDIS_ENCODING_SKIPLIST 使用跳躍表和字典實現 skiplist
-
2. 字符串對象
-
字符串編碼可以是
int、raw、embstr;編碼類型 說明 int 字符串保存整數值,並且這個整數可以用long類型表示 raw 字符串值的長度大於39字節 embstr 字符串值的長度小於39字節 -
embstr編碼是專門用於保存短字符串的一種優化編碼方式; -
raw與embstr的異同:- 二者都使用
redisObject結構與sdshdr結構來表示字符串; embstr通過調用一次內存分配函數來分配一塊連續的空間;raw通過調用兩次內存分配函數來分配一塊連續的空間;
- 二者都使用
-
embstr的優點:- 創建時只需要分配一次內存;
- 釋放時只需要調用一次內存釋放函數;
- 連續保存在一塊連續內存里,對緩存友好;

-
long double類型表示的浮點數在Redis中也是作為字符串值來保存的;
-
embstr編碼的字符串對象實際上是只讀的,要修改先會轉成raw編碼,再執行修改命令; -
字符串命令請見《Redis常用命令及示例總結》;
3. 列表對象
- 列表的編碼對象可以是
ziplist或linkedlist; - redis 3.2以后,
quicklist作為列表鍵的實現底層實現之一,代替了壓縮列表。 ziplist編碼的條件:- 列表對象保存的所有字符串元素的長度都小於64字節;
- 列表對象保存的元素數量小於512個;

-
linklist編碼:

-
列表命令請見《Redis常用命令及示例總結》;
3.1 quicklist 快速鏈表
-
quicklist的定義在
quicklist.h:typedef struct quicklist { //指向頭部(最左邊)quicklist節點的指針 quicklistNode *head; //指向尾部(最右邊)quicklist節點的指針 quicklistNode *tail; //ziplist中的entry節點計數器 unsigned long count; //quicklist的quicklistNode節點計數器 unsigned int len; //保存ziplist的大小,配置文件設定,占16bits int fill : 16; //保存壓縮程度值,配置文件設定,占16bits,0表示不壓縮 unsigned int compress : 16; } quicklist; -
quicklist節點的定義:
typedef struct quicklistNode { struct quicklistNode *prev; //前驅節點指針 struct quicklistNode *next; //后繼節點指針 //不設置壓縮數據參數recompress時指向一個ziplist結構 //設置壓縮數據參數recompress指向quicklistLZF結構 unsigned char *zl; //壓縮列表ziplist的總長度 unsigned int sz; //ziplist中包的節點數,占16 bits長度 unsigned int count : 16; //表示是否采用了LZF壓縮算法壓縮quicklist節點,1表示壓縮過,2表示沒壓縮,占2 bits長度 unsigned int encoding : 2; //表示一個quicklistNode節點是否采用ziplist結構保存數據,2表示壓縮了,1表示沒壓縮,默認是2,占2bits長度 unsigned int container : 2; //標記quicklist節點的ziplist之前是否被解壓縮過,占1bit長度 //如果recompress為1,則等待被再次壓縮 unsigned int recompress : 1; //測試時使用 unsigned int attempted_compress : 1; //額外擴展位,占10bits長度 unsigned int extra : 10; } quicklistNode;

4. 哈希對象
-
哈希對象編碼可以是
ziplist或hashtable; -
使用
ziplist編碼的條件:- 鍵和值的字符串長度小於64字節;
- 鍵值對數量少於512個;
-
使用
ziplist編碼時:

-
使用
hashtable編碼時:

- 哈希表命令請見《Redis常用命令及示例總結》;
5. 集合對象
- 集合對象的編碼可以是
intset或hashtable; - 使用
inset編碼的條件:- 所有元素為整數值;
- 保存的元素不超過512個;
- 使用
inset編碼時:

- 使用
hashtable編碼時:

- 集合命令請見《Redis常用命令及示例總結》;
6. 有序集合對象
-
有序集合的編碼可以是
ziplist或skiplist; -
壓縮列表內的集合元素按分值從小到大排列;
-
使用
ziplist編碼時:


-
使用
ziplist編碼的條件:- 元素數量少於128個;
- 元素長度小於64字節;
-
使用
skiplist編碼時,使用zset結構作為底層實現; -
zset的定義:typedef struct zset{ //跳躍表 zskiplist *zsl; //字典 dict *dict; } zset;- 跳躍表和字典使用指針來共享相同的元素和分值,因此不會產生重復,也不會造成內存浪費;

- 跳躍表和字典使用指針來共享相同的元素和分值,因此不會產生重復,也不會造成內存浪費;
-
有序集合命令請見《Redis常用命令及示例總結》;
7. Redis對象的特點
7.1 類型檢查與命令多態
- Redis的命令基本上分兩類。一種是可以對任意類型的鍵操作(基於類型的多態),一種是只能對特定類型的鍵執行(基於編碼的多態);
- 在執行特定類型命令之前,服務器會先檢查
redisObject結構的type屬性,判斷是否為執行該命令所需的類型。是則執行,否則返回類型錯誤; - Redis還會根據值對象的編碼方式選擇正確底層方法,使一個命令可以同時用於處理多種不同編碼方式的數據結構,進而實現多態命令;

7.2 內存回收
-
C語言不具備自動回收內存的功能,Redis構建一個引用計數計數實現內存回收機制;
-
引用計數由
redisObject結構的refcount屬性記錄:typedef struct redisObject{ //... //引用計數 int refcount; //... }- 創建新對象時,
refcount被初始化為1; - 對象被新程序使用時,
refcount++; - 對象不被一個程序使用時,
refcount--; - 對象計數值為0時,對象占用的內存會被釋放;
- 創建新對象時,
7.3 對象共享
- 對象的計數屬性帶有對象共享的作用;
- 當多個鍵保存同一個值時,這些鍵的值指針指向同一個值對象,值對象的
refcounr為n; - Redis在初始化服務器時,會創建一萬個字符串對象(0~9999的字符串對象),當服務器需要用到這些值對象時,服務器會使用這些共享對象,而不是創建新對象;
- Redis只對包含整數值的字符串對象進行共享,驗證數字的時間復雜度為O(1);

7.4 對象的空轉時長
redisObject結構里有個lru屬性,記錄對象最后一次被命令程序訪問的時間;typedef struct redisObject{ //... unsigned lru:22; //... }- 使用命令
OBJECT IDLETIME可以顯示對象的空轉時間,不會改變對象的空轉時間; - 如果服務器打開
maxmemory選項,並且回收內存的算法為volatile-lru或allkeys-lru,那么當服務器占用的內存數超過maxmemory選項設置的上限值是,空轉時間較高的鍵會優先被服務器釋放,回收內存;
最后
