redis之字串命令原始碼解析(二)

2021-06-26 16:11:35 字數 3656 閱讀 5672

形象化設計模式實戰

hello!架構

redis命令原始碼解析

在redis之字串命令原始碼解析(一)中講了get的簡單實現,並沒有對如何取到資料做深入分析,這裡將深入。

(一)中說set test "hello redis",「hello redis」會最終儲存在robj中,redisobject是redis的核心,資料庫的每個鍵、值,以及redis本身處理的引數都表示為這種資料型別,其結構如下:

/* the actual redis object */

/* * redis 物件

*/#define redis_lru_bits 24

#define redis_lru_clock_max ((1

#define redis_lru_clock_resolution 1000 /* lru clock resolution in ms */

typedef struct redisobject robj;

物件型別有:

#define redis_string 0 // 字串

#define redis_list 1 // 列表

#define redis_set 2 // 集合

#define redis_zset 3 // 有序集

#define redis_hash 4 // 雜湊表

物件編碼有:

#define redis_encoding_raw 0 // 編碼為字串

#define redis_encoding_int 1 // 編碼為整數

#define redis_encoding_ht 2 // 編碼為雜湊表

#define redis_encoding_zipmap 3 // 編碼為zipmap

#define redis_encoding_linkedlist 4 // 編碼為雙端鍊錶

#define redis_encoding_ziplist 5 // 編碼為壓縮列表

#define redis_encoding_intset 6 // 編碼為整數集合

#define redis_encoding_skiplist 7 // 編碼為跳躍表

redis使用的是高效且實現簡單的雜湊作為字典的底層實現。

dict.h中定義如下

/*

* 字典

*/typedef struct dict dict;

雜湊表dictht的結構:

/* this is our hash table structure. every dictionary has two of this as we

* implement incremental rehashing, for the old to the new table. */

/* * 雜湊表

* * 每個字典都使用兩個雜湊表,從而實現漸進式 rehash 。

*/typedef struct dictht dictht;

雜湊表陣列dictentry的結構:
/*

* 雜湊表節點

*/typedef struct dictentry v;

// 指向下個雜湊表節點,形成鍊錶

struct dictentry *next;

} dictentry;

那麼乙個dict可以**表示為:

由圖可清晰地看出redis字典雜湊表所使用的雜湊碰撞解決方法是鏈位址法,這個方法就是使用鍊錶將多個雜湊值相同的節點串連在一起,從而解決衝突問題。

set命令最終會呼叫dict.c中的dictadd方法將test => "hello redis" 儲存到字典中

/* add an element to the target hash table */

/* * 嘗試將給定鍵值對新增到字典中

* * 只有給定鍵 key 不存在於字典時,新增操作才會成功

* * 新增成功返回 dict_ok ,失敗返回 dict_err

* * 最壞 t = o(n) ,平灘 o(1)

*/int dictadd(dict *d, void *key, void *val)

整個set可簡略如下圖(此圖省去了許多其它操作):

從圖中你會發現,其實key的過期時間就相當於是key的另乙個val,儲存在另乙個dict中,簡單地說就是有兩個dict,乙個是key=>value,乙個是key=>expire。

dict有兩個ht,就是每個字典有兩個雜湊表,為毛要有兩個,其作用是對dict進行擴容和收縮,因為如果節點數量比雜湊表的大小要大很多的話,那麼雜湊表就會退化成多個鍊錶,雜湊表本身的效能優勢就不再存在。

dict.c中的_dictexpandifneeded方法對雜湊表何時可rehash作了判斷:

// 一下兩個條件之一為真時,對字典進行擴充套件

// 1)字典已使用節點數和字典大小之間的比率接近 1:1

// 並且 dict_can_resize 為真

// 2)已使用節點數和字典大小之間的比率超過 dict_force_resize_ratio(預設值為5)

if (d->ht[0].used >= d->ht[0].size &&

(dict_can_resize ||

d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))

rehash的**這裡不貼出,因為實現簡單,大致的過程是

1. 釋放ht[0] 的空間;

2. 用ht[1] 來代替ht[0] ,使原來的ht[1] 成為新的ht[0] ;

3. 建立乙個新的空雜湊表,並將它設定為ht[1] ;

4. 將字典的rehashidx 屬性設定為-1 ,標識rehash 已停止;

但我在看源**時,發現並不是一將rehashidx設為0就進行rehash操作的,而是當再次dictadd時,才dictrehash(d,1),第二個引數是1,也就是說每次rehash只會對單個索引上的節點進行遷移,這種做法幾乎不會消耗什麼時間,客戶端可以快速的得到響應。當然這種除了這種方式進行rehash外,redis還有個定時任務呼叫dictrehashmilliseconds方法,在規定的時間內,盡可能地對資料庫字典中那些需要rehash的字典進行rehash,從而加速rehash的程序。

現在我知道redis並不是一下子就rehash完成,而是需要一定時間的,那麼如果客戶端在這段時間內向redis傳送get set del請求,那redis會如何處理,從而保證資料的完整和正確呢?

• 因為在rehash 時,字典會同時使用兩個雜湊表,所以在這期間的所有查詢、刪除等操作,除了在ht[0] 上進行,還需要在ht[1] 上進行。

• 在執行新增操作時,新的節點會直接新增到ht[1] 而不是ht[0] ,這樣保證ht[0] 的節點數量在整個rehash 過程中都只減不增。

Redis 字串原始碼原理

1 redis的字串結構被設計成乙個 sds 結構,字串實際內容是被存放在乙個陣列中,如下表 structsds 當字串的大小超出當前分配的capacity大小時,陣列將擴容,分配更大的陣列,將舊的陣列拷貝到新陣列中,再將增加到字串新增進去。2 embstr 與raw 1 redis的字串的儲存方式...

Redis原始碼閱讀 sds字串實現

從開始工作就開始使用redis,也有一段時間了,但都只是停留在使用階段,沒有往更深的角度探索,每次想讀原始碼都止步在閱讀書籍上,因為看完書很快又忘了,這次逼自己先讀 因為個人覺得寫作需要閱讀文本來增強靈感,那麼寫 的,就閱讀更多 來增強靈感吧。redis的實現原理,在 redis設計與實現 一書中講...

Redis資料之字串命令

賦值 set key value 取值 當鍵不存在時會返回空 get key 向尾部追加值 獲取字串長度 strlen命令返回鍵值的長度,如果鍵不存在則返回0 strlen key 獲得 設定多個鍵值 mget mset 與get set 相似,不過mget mset 可以同時獲得 設定多個鍵的鍵值...