redis
是使用 C 語言編寫的,但是 C
語言是沒有字典這個資料結構的,因此 C
語言自己使用結構體來自定義一個字典結構
src\server.h 中的 redis 資料庫 資料結構
/* Redis database representation. There are multiple databases identified
* by integers from 0 (the default database) up to the max configured
* database. The database number is the 'id' field in the structure. */
typedef struct redisDb {
dict *dict; /* The keyspace for this DB */
dict *expires; /* Timeout of keys with a timeout set */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
redisDb 存放了 redis 資料庫底層的資料結構:
字典型別
過期時間
使用者端等待資料的鍵 (BLPOP)
收到PUSH的鍵被阻塞
監控 MULTI/EXEC CAS 的鍵,例如事務的時候就會使用到
資料庫的 id, 0 – 15
統計平均的 ttl
記錄過期週期
存放 key 的列表
src\dict.h 字典的資料結構
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;
dict
存放字典的資料結構
字典的型別
私有資料
hash 表, 一箇舊表,一個新表,是有當 hash 表擴容的時候,新表才會被使用到,也就是 ht[1]
typedef struct dictType {
uint64_t (*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);
int (*expandAllowed)(size_t moreMem, double usedRatio);
} dictType;
dictType
定義了多個函數指標,便於後續進行方法的實現和呼叫
例如 keyCompare
函數指標,他是一個指標,指向的是一個函數,這個函數有 3 個引數,和 1 個返回值:
3 個引數
具體的資料
key1 這個鍵具體的值
key2 這個鍵具體的值
這個指標 keyCompare 指向的函數作用是比較兩個 key 的大小
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
dictht
存放的是 hash
表使用到的資料結構
實際的 key-value 鍵值對
hashtable 的容量
等於 size -1
hashtable 元素的個數
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
dictEntry
為鍵值對的實際資料結構
key 值,實際上是一個 sds
型別的
value 值,是一個聯合體
dictEntry
指標,指向下一個資料,主要是解決 hash 衝突的
例如上一篇我們介紹到的 hash
,如下圖中,key 就是 1,v 就是 (k3,v3) ,next 指向的就是 (k2,v2),一般預設情況 next 指向 NULL
上述聯合體 v ,裡面第 1 個元素是, void *val;
實際上這個元素才是指向真正的值,這個元素是一個指標,實際的資料結構是這個樣子的
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
} robj;
型別,佔 4 個 bit ,是用來約束使用者端 api 的,例如 string 型別,embstr,hash,zset 等等
編碼型別,佔 4 個bit ,使用到的數位有 0 - 10,分別表示不同的資料型別
lru 佔 24 個bit ,3 個位元組 , 記憶體淘汰演演算法
參照計數 , int 型別,佔 4 個位元組
實際的資料指標 , 64 位元運算系統中, ptr 佔 8個位元組
設定一個 bitmap 的 key,作用為標記 11 號的線上使用者
127.0.0.1:6379> SETBIT login:9:11 25 1
(integer) 0
127.0.0.1:6379> SETBIT login:9:11 26 1
(integer) 0
127.0.0.1:6379> SETBIT login:9:11 27 1
(integer) 0
127.0.0.1:6379> BITCOUNT login:9:11
(integer) 3
127.0.0.1:6379> strlen login:9:11
(integer) 4
通過 BITCOUNT 可以看出 11 號線上人數 3 個人,login:9:11 佔用位元組數位 4 位元組
127.0.0.1:6379> SETBIT login:9:12 26 1
(integer) 0
127.0.0.1:6379> SETBIT login:9:12 25 0
(integer) 0
127.0.0.1:6379> SETBIT login:9:12 27 1
(integer) 0
127.0.0.1:6379> STRLEN login:9:12
(integer) 4
通過 BITCOUNT 可以看出 12 號線上人數 2 個人,login:9:12 佔用位元組數位 4 位元組
下面我們將取 login:9:11 和 login:9:12 的 與操作,來計算 11 號 和 12 號兩天來都線上的人數
127.0.0.1:6379> BITOP and login:and login:9:11 login:9:12
(integer) 4
127.0.0.1:6379> BITCOUNT login:and
(integer) 2
根據上述結果我們可以看出,11 號 和 12 號兩天來都線上的人數為 2 人,驗證 ok
我們再來看看11 號 和 12 號任意一天線上的人數
127.0.0.1:6379> BITOP or login:or login:9:11 login:9:12
(integer) 4
127.0.0.1:6379> BITCOUNT login:or
(integer) 3
根據上述結果我們可以看出,11 號 和 12 號任意一天線上的人數為 3 人,驗證 ok
127.0.0.1:6379> type login:or
string
127.0.0.1:6379> OBJECT encoding login:or
"raw"
127.0.0.1:6379> OBJECT encoding login:9:12
"raw"
127.0.0.1:6379> OBJECT encoding login:and
"raw"
咱們來看看上述用到的 key ,在 redis 裡面實際是什麼資料型別吧,
可以看出上述都是 「raw」 型別, 也就是 redis 的 sds 型別
咱們再來看一個小例子,redis 中設定一個字串 key
127.0.0.1:6379> set name xiaoming
OK
127.0.0.1:6379> OBJECT encoding name
"embstr"
我們可以看出 name 的型別是 「embstr」,那麼 「embstr」 底層是如何實現的呢?「embstr」 有能承載多少個位元組的資料呢?
上述我們有說到 redis 裡面存放鍵值對的地方在 dictEntry 結構體中,dictEntry 結構體中的val 指標指向的是一個 redisObject 結構體,是這樣的
我們在一個 64 位的機器中,CPU 在記憶體中讀取資料的是通過讀取快取行的方式來實現的
一個快取行有 64 位元組
一個 redisObject 結構體佔 16 位元組
那麼就還剩 48 位元組 可以使用,那麼使用 redis 裡面 哪一個 sds 資料結構來存放資料資料呢?
使用 hisdshdr8 型別,hisdshdr8 型別 sds 的前 3 個元素佔用 3 個位元組,那麼剩下的 buf 存放資料就可以存放 45個位元組(64 - 16 - 3)的資料了
如果你這麼認為了,那麼就有點粗心哦,因為 redis 為了相容 C 語言的標準,會在字串的後面加上 1 個 ‘\0’ ,他是佔一個位元組的因此最終「embstr」
實際能存放的位元組數是:
44 位元組
來回顧上一篇文章,可以看出
當資料佔用空間在 0 - - 2^5-1 , 使用 hisdshdr5 資料型別
2^5 – 2^8-1 的佔用空間的時候,使用 hisdshdr8 資料型別
我們在 redis 中設定一個 test 的值為一個 44位元組 的內容,檢視這個 key 的型別,是 embstr
127.0.0.1:6379> set test 99999999991111111111222222222233333333334444
OK
127.0.0.1:6379> OBJECT encoding test
"embstr"
127.0.0.1:6379> STRLEN test
(integer) 44
再來設定 test2 為 大於 44 位元組的內容,再檢視他的內容是 raw
127.0.0.1:6379> set test2 999999999911111111112222222222333333333344449
OK
127.0.0.1:6379> OBJECT encoding test2
"raw"
最後送上一張上述資料結構的關係圖
參考資料:
reids 原始碼 reids-6.2.5 Redis 6.2.5 is the latest stable version.
朋友們,你的支援和鼓勵,是我堅持分享,提高質量的動力
好了,本次就到這裡
技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。
我是小魔童哪吒,歡迎點贊關注收藏,下次見~