大家有什麼建議歡迎在下方評論提出,多多討論
大家可能發現這個部分寫的很粗略,因為redis本身設計的就是很簡單的,這些資料結構基本沒有太多好寫的,主要是寫 了後做一個總結
1. SDS(簡單動態字串)
每個 sds.h/sdshdr
結構表示一個 SDS 值:
struct sdshdr {
// 記錄 buf 陣列中已使用位元組的數量
// 等於 SDS 所儲存字串的長度
int len;
// 記錄 buf 陣列中未使用位元組的數量
int free;
// 位元組陣列,用於儲存字串
char buf[];
};
複製程式碼
為什麼不使用c語言簡單字串
原因有如下
- 獲取長度複雜度為O(1)
- 避免緩衝區溢位
優化策略
redis非常注重效能,於是優化是非常必要的。
減少記憶體重分配
- 空間預分配
記憶體的分配設計到複雜演算法或可能的系統呼叫,比較耗時
於是,每次再字串增長操作時,不僅僅會擴容到剛好所需的空間,而是會通過一定策略額外分配空間
- 惰性空間釋放
字串的縮短操作,不會進行記憶體重分配,而是會通過free記錄起來,為未來可能存在的增長提供優化
二進位制安全
不使用\0辨認結尾,而是通過len
並且在SDS中\0被保留,可以相容部分c字串函式
2. 連結串列
redis是一個設計追求簡單的資料庫,連結串列這種簡單實用的資料結構在很多地方都得到了應用,(事實上應該說是雙向連結串列。)
譬如 列表(List),慢查詢,監視器,訂閱與釋出等。包括客戶端狀態也是用連結串列在伺服器中進行儲存的。
結構
typedef struct listNode {
// 前置節點
struct listNode *prev;
// 後置節點
struct listNode *next;
// 節點的值
void *value;
} listNode;
複製程式碼
3. 字典
字典是使用雜湊表作為底層實現。
一圖勝千言
看的出來,解決鍵衝突(collision)的辦法是通過每個雜湊表節點都是一個連結串列。(鏈地址法)
- 雜湊演算法
- 鏈地址法
以上兩者細節就不解釋了
rehash擴容
要擴多少內容,開多大空間的新dictEntry,然後把之前的dictEntry轉到新的。
當然,一次性轉會有效能問題,所以
漸進式rehash
相當於是搭便車
每次對字典進行更新新增查詢刪除操作時,會順帶的轉移舊的dictEntry的一個index連結串列到新的dictEntry
詳細執行過程不予介紹
4. 跳躍表(跳錶)
跳躍表(skiplist)是一種有序資料結構, 它通過在每個節點中維持多個指向其他節點的指標, 從而達到快速訪問節點的目的。
跳躍表支援平均 O(\log N) 最壞 O(N) 複雜度的節點查詢, 還可以通過順序性操作來批量處理節點。
在大部分情況下, 跳躍表的效率可以和平衡樹相媲美, 並且因為跳躍表的實現比平衡樹要來得更為簡單, 所以有不少程式都使用跳躍表來代替平衡樹。
Redis 使用跳躍表作為有序集合鍵的底層實現之一: 如果一個有序集合包含的元素數量比較多, 又或者有序集合中元素的成員(member)是比較長的字串時, Redis 就會使用跳躍表來作為有序集合鍵的底層實現
從上圖可以看書,跳錶其實就是一種分治思想的,利用空間換取時間的一種演算法 在看了上圖之後,這個時候再看下圖redis的跳錶。就應該容易理解多了
整數集合
整數集合就比較簡單了,重要的一個點主要是升級
redis的整數集合型別其實是會隨著存入數字而變化的,當數都比較小的時候,型別可能就是int8_t,int16_t,但是一旦加入一個大數,集合會升級型別為相應型別,此時該集合中所有數都會被更新為該型別
另外注意
整數集合的陣列不會出現降級