前言
參考資料:《Redis設計與實現 第二版》;
本篇筆記按照書裡的脈絡,將知識點分為四個部分。其中第一部分資料結構與物件分為上中下篇,上篇包括:SDS、連結串列和字典;中篇包括跳躍表、整數集合和壓縮列表;下篇為物件;
上篇的連結:https://www.cnblogs.com/dlhjw/p/15569578.html
中篇的連結:https://www.cnblogs.com/dlhjw/p/15582039.html
Redis常用命令及示例總結:https://blog.csdn.net/dlhjw1412/article/details/119713214
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
選項設定的上限值是,空轉時間較高的鍵會優先被伺服器釋放,回收記憶體;