搞定 Redis 資料儲存原理,別隻會 set、get 了
在上一篇透過原始碼編譯構建出可調式環境之後,想必你想更深入瞭解我的整體架構。當你熟悉我的整體架構和每個模組,遇到問題才能直擊本源,直搗黃龍,一笑破蒼穹。
我的核心模組如圖 1-10。
圖 1-10
Client 客戶端,官方提供了 C 語言開發的客戶端,可以傳送命令,效能分析和測試等。 網路層事件驅動模型,基於 I/O 多路複用,封裝了一個短小精悍的高效能 ae 庫,全稱是 a simple event-driven programming library
。在 ae 這個庫裡面,我透過 aeApiState
結構體對epoll、select、kqueue、evport
四種 I/O 多路複用的實現進行適配,讓上層呼叫方感知不到在不同作業系統實現 I/O 多路複用的差異。Redis 中的事件可以分兩大類:一類是網路連線、讀、寫事件;另一類是時間事件,也就是特定時間觸發的事件,比如定時執行 rehash 操作。 命令解析和執行層,負責執行客戶端的各種命令,比如 SET、DEL、GET
等。記憶體分配和回收,為資料分配記憶體,提供不同的資料結構儲存資料。 持久化層,提供了 RDB 記憶體快照檔案 和 AOF 兩種持久化策略,實現資料可靠性。 高可用模組,提供了副本、哨兵、叢集實現高可用。 監控與統計,提供了一些監控工具和效能分析工具,比如監控記憶體使用、基準測試、記憶體碎片、bigkey 統計、慢指令查詢等。
掌握了整體架構和模組後,接下來進入 src 原始碼目錄,使用如下指令執行 redis-server
可執行程式啟動 Redis。
./redis-server ../redis.conf
每個被啟動的服務我都會抽象成一個 redisServer,原始碼定在server.h
的redisServer
結構體。
這個結構體包含了儲存鍵值對的資料庫例項、redis.conf 檔案路徑、命令列表、載入的 Modules、網路監聽、客戶端列表、RDB AOF 載入資訊、配置資訊、RDB 持久化、主從複製、客戶端快取、資料結構壓縮、pub/sub、Cluster、哨兵等一些列 Redis 例項執行的必要資訊。
結構體欄位很多,不再一一列舉,部分核心欄位如下。
truct redisServer {
pid_t pid; /* 主程式 pid. */
pthread_t main_thread_id; /* 主執行緒 id */
char *configfile; /*redis.conf 檔案絕對路徑*/
redisDb *db; /* 儲存鍵值對資料的 redisDb 例項 */
int dbnum; /* DB 個數 */
dict *commands; /* 當前例項能處理的命令表,key 是命令名,value 是執行命令的入口 */
aeEventLoop *el;/* 事件迴圈處理 */
int sentinel_mode; /* true 則表示作為哨兵例項啟動 */
/* 網路相關 */
int port;/* TCP 監聽埠 */
list *clients; /* 連線當前例項的客戶端列表 */
list *clients_to_close; /* 待關閉的客戶端列表 */
client *current_client; /* 當前執行命令的客戶端*/
};
1.2.1 資料儲存原理
其中redisDb *db
指標非常重要,它指向了一個長度為 dbnum(預設 16)的 redisDb 陣列,它是整個儲存的核心,我就是用這玩意來儲存鍵值對。
redisDb
redisDb 結構體定義如下。
typedef struct redisDb {
dict *dict;
dict *expires;
dict *blocking_keys;
dict *ready_keys;
dict *watched_keys;
int id;
long long avg_ttl;
unsigned long expires_cursor;
list *defrag_later;
clusterSlotToKeyMapping *slots_to_keys;
} redisDb;
dict 和 expires
dict 和 expires 是最重要的兩個屬性,底層資料結構是字典,分別用於儲存鍵值對資料和 key 的過期時間。 expires,底層資料結構是 dict 字典,儲存每個 key 的過期時間。
❝MySQL:“為什麼分開儲存?”
好問題,之所以分開儲存,是因為過期時間並不是每個 key 都會設定,它不是鍵值對的固有屬性,分開後雖然需要兩次查詢,但是能節省記憶體開銷。
blocking_keys 和 ready_keys
底層資料結構是 dict 字典,主要是用於實現 BLPOP 等阻塞命令。
當客戶端使用 BLPOP 命令阻塞等待取出列表元素的時候,我會把 key 寫到 blocking_keys 中,value 是被阻塞的客戶端。
當下一次收到 PUSH 命令執時,我會先檢查 blocking_keys 中是否存在阻塞等待的 key,如果存在就把 key 放到 ready_keys 中,在下一次 Redis 事件處理過程中,會遍歷 ready_keys 資料,並從 blocking_keys 中取出阻塞的客戶端響應。
watched_keys
用於實現 watch 命令,儲存 watch 命令的 key。
id
Redis 資料庫的唯一 ID,一個 Redis 服務支援多個資料庫,預設 16 個。
avg_ttl
用於統計平均過期時間。
expires_cursor
統計過期事件迴圈執行的次數。
defrag_later
儲存逐一進行碎片整理的 key 列表。
slots_to_keys
僅用於 Cluster 模式,當使用 Cluster 模式的時候,只能有一個資料庫 db 0。slots_to_keys 用於記錄 cluster 模式下,儲存 key 與雜湊槽對映關係的陣列。
dict
Redis 使用 dict 結構來儲存所有的鍵值對(key-value)資料,這是一個雜湊表,所以 key 查詢時間複雜度是 O(1) 。
所謂雜湊表,我們可以類比 Java 中的 HashMap
,其實就是一個陣列,陣列的每個元素叫做雜湊桶。
dict 結構體原始碼在 dict.h
中定義。
struct dict {
dictType *type;
dictEntry **ht_table[2];
unsigned long ht_used[2];
long rehashidx;
int16_t pauserehash;
signed char ht_size_exp[2];
};
dict 的結構體裡,有 dictType *type
,**ht_table[2]
,long rehashidx
三個很重要的結構。
type 儲存了 hash 函式,key 和 value 的複製等函式; ht_table[2],長度為 2 的陣列,預設使用 ht_table[0] 儲存鍵值對資料。我會使用 ht_table[1] 來配合實現漸進式 reahsh 操作。 rehashidx 是一個整數值,用於標記是否正在執行 rehash 操作,-1 表示沒有進行 rehash。如果正在執行 rehash,那麼其值表示當前 rehash 操作執行的 ht_table[1] 中的 dictEntry 陣列的索引。 pauserehash 表示 rehash 的狀態,大於 0 時表示 rehash 暫停了,小於 0 表示出錯了。 ht_used[2],長度為 2 的陣列,表示每個雜湊桶儲存了多少個 鍵值對實體(dictEntry),值越大,雜湊衝突的機率越高。 ht_size_exp[2],每個雜湊表的大小,也就是雜湊桶個數。
重點關注 ht_table 陣列,陣列每個位置叫做雜湊桶,就是這玩意儲存了所有鍵值對,每個雜湊桶的型別是 dictEntry。
❝MySQL:“Redis 支援那麼多的資料型別,雜湊桶咋儲存?”
他的玄機就在 dictEntry 中,每個 dict 有兩個 ht_table,用於儲存鍵值對資料和實現漸進式 rehash。
dictEntry 結構如下。
typedef struct dictEntry {
void *key;
union {
// 指向實際 value 的指標
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
// 雜湊表衝突生成的連結串列
struct dictEntry *next;
void *metadata[];
} dictEntry;
*key
指向鍵值對的鍵的指標,指向一個 sds 物件,key 都是 string 型別。v 是鍵值對的 value 值,是個 union(聯合體),當它的值是 uint64_t、int64_t 或 double 數字型別時,就不再需要額外的儲存,這有利於減少記憶體碎片。(為了節省記憶體操碎了心)當值為非數字型別,就是用 val
指標儲存。*next
指向另一個 dictEntry 結構, 多個 dictEntry 可以透過 next 指標串連成連結串列, 從這裡可以看出, ht_table 使用鏈地址法來處理鍵碰撞:當多個不同的鍵擁有相同的雜湊值時,雜湊表用一個連結串列將這些鍵連線起來。
雜湊桶並沒有儲存值本身,而是指向具體值的指標,從而實現了雜湊桶能存不同資料型別的需求。
redisObject
dictEntry
的 *val
指標指向的值實際上是一個 redisObject
結構體,這是一個非常重要的結構體。
我的 key 只能是字串型別,而 value 可以是 String、Lists、Set、Sorted Set、雜湊表等資料型別。
鍵值對的值都被包裝成 redisObject 物件, redisObject
在 server.h
中定義。
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS;
int refcount;
void *ptr;
} robj;
type:記錄了物件的型別,string、set、hash 、Lis、Sorted Set 等,根據該型別來確定是哪種資料型別,使用什麼樣的 API 操作。 encoding:編碼方式,表示 ptr 指向的資料型別具體資料結構,即這個物件使用了什麼資料結構作為底層實現儲存資料。同一個物件使用不同編碼實現記憶體佔用存在明顯差異,內部編碼對記憶體最佳化非常重要。 lru:LRU_BITS:LRU 策略下物件最後一次被訪問的時間,如果是 LFU 策略,那麼低 8 位表示訪問頻率,高 16 位表示訪問時間。 refcount :表示引用計數,由於 C 語言並不具備記憶體回收功能,所以 Redis 在自己的物件系統中新增了這個屬性,當一個物件的引用計數為 0 時,則表示該物件已經不被任何物件引用,則可以進行垃圾回收了。 ptr 指標:指向物件的底層實現資料結構,指向值的指標。
如圖 1-11 是由 redisDb、dict、dictEntry、redisObejct 關係圖:
圖 1-11
注意,一開始的時候,我只使用 ht_table[0] 這個雜湊表讀寫資料,ht_table[1] 指向 NULL,當這個雜湊表容量不足,觸發擴容操作,這時候就會建立一個更大的雜湊表 ht_table[1]。
接著我會使用漸進式 rehash 的方式將 ht_table[0] 的資料遷移到 ht_table[1] 上,全部遷移完成後,再修改下指標,讓 ht_table[0] 指向擴容後的雜湊表,回收掉原來的雜湊表,ht_table[1] 再次指向 NULL。
課後思考
當客戶端傳送一個指令請求,Redis 服務端會發生什麼?
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2929048/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 【Redis】redis各型別資料儲存分析Redis型別
- redis-4.資料儲存型別Redis型別
- redis叢集資料儲存和獲取原理Redis
- 07 redis-資料型別 setRedis資料型別
- SRAM資料儲存原理
- redis 儲存結構原理 2Redis
- Redis資料儲存位置匯出資料Redis
- laravelS使用redis stream完成每日億級別資料儲存LaravelRedis
- redis-5.資料儲存格式Redis
- Redis資料儲存和讀寫Redis
- Redis資料結構(一)-Redis的資料儲存及String型別的實現Redis資料結構型別
- Redis 基本資料型別(Set) 的操作命令Redis資料型別
- Redis學習手冊(Set資料型別)Redis資料型別
- Redis常用資料型別及其儲存結構(原始碼篇)Redis資料型別原始碼
- Redis(一):基本資料型別與底層儲存結構Redis資料型別
- [BUG反饋]模型中的欄位型別為日期是隻儲存了年份模型型別
- set unused 是否會釋放儲存空間
- Redis五大資料型別之 Set(集合)Redis大資料資料型別
- 重新學習Mysql資料庫3:Mysql儲存引擎與資料儲存原理MySql資料庫儲存引擎
- 《閒扯Redis九》Redis五種資料型別之Set型Redis資料型別
- lombok get/set 與 JavaBean get/setLombokJavaBean
- pgsql資料庫的表儲存策略原理SQL資料庫
- HP EVA系列儲存資料恢復原理資料恢復
- Redis為何這麼快–資料儲存角度Redis
- MySQL 常用資料儲存引擎區別MySql儲存引擎
- MYSQL-資料型別儲存-DATEMySql資料型別
- 你選對儲存結構了嗎?你會玩UVM配置資料庫了嗎?資料庫
- JavaScript中的資料型別-儲存差別JavaScript資料型別
- 資料儲存--檔案儲存
- 華為雲PB級資料庫GaussDB(for Redis)揭秘第13期:如何搞定推薦系統儲存難題資料庫Redis
- 本地儲存VS雲端儲存:區別不只是資料存放位置
- 【資料庫】資料庫儲存元素型別基礎資料庫型別
- PHP memcached 各種資料型別儲存PHP資料型別
- MYSQL 資料型別儲存-數值型MySQL 資料型別
- oracle資料型別與儲存結構Oracle資料型別
- 資料儲存
- redis-9.set型別Redis型別
- Redis的集合型別(Set)Redis型別