# 分析Redis中的字典、哈希算法和ReHash原理
## 一、Redis字典概述
### 1.1 字典在Redis中的核心地位
Redis作為高性能鍵值數據庫,其鍵空間(Key Space)底層實現依賴于字典結構。字典不僅是Redis數據庫的基石,還支撐著哈希鍵(Hash Key)等復雜數據類型的實現。在Redis源碼中,字典的實現主要位于`dict.h`和`dict.c`文件中。
### 1.2 字典數據結構定義
Redis字典采用典型哈希表實現,核心結構體如下:
```c
typedef struct dict {
dictType *type; // 類型特定函數
void *privdata; // 私有數據
dictht ht[2]; // 哈希表數組(2個)
long rehashidx; // rehash索引(-1表示未進行)
unsigned long iterators; // 當前迭代器數量
} dict;
typedef struct dictht {
dictEntry **table; // 哈希表數組
unsigned long size; // 表大小
unsigned long sizemask; // 大小掩碼(=size-1)
unsigned long used; // 已使用節點數
} dictht;
typedef struct dictEntry {
void *key; // 鍵
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v; // 值
struct dictEntry *next; // 鏈地址法解決沖突
} dictEntry;
Redis默認使用SipHash算法(1-2變體),該算法在防止哈希碰撞和抵抗哈希洪水攻擊方面表現優異:
uint64_t siphash(const uint8_t *in, const size_t inlen,
const uint8_t *k);
關鍵特性: - 輸入敏感:微小變化導致輸出完全不同 - 64位輸出減少碰撞概率 - 密鑰隨機化防止預測攻擊
Redis通過位運算快速定位槽位:
index = hash & dict->ht[x].sizemask;
其中sizemask
總是等于size-1
(保證為2^n-1形式),使得模運算可以轉化為高效的位與操作。
采用鏈地址法處理沖突,新節點總是添加到鏈表頭部(時間復雜度O(1)):
entry->next = ht->table[index];
ht->table[index] = entry;
Redis在以下情況下觸發rehash:
1. 擴容條件:used/size > dict_force_resize_ratio
(默認5)
2. 縮容條件:used/size < 10%
(BGSAVE時不縮容)
Redis采用漸進式rehash避免服務阻塞:
準備階段:
ht[1]
空間(擴容時大小為第一個≥used*2
的2^n)rehashidx=0
(標識rehash開始)執行階段(每次操作分步遷移): “`c if (dictIsRehashing(d)) _dictRehashStep(d);
int _dictRehashStep(dict *d) { return dictRehash(d,1); // 每次遷移1個桶 }
3. 完成條件:
- `ht[0]`所有元素遷移完成
- 交換`ht[0]`和`ht[1]`
- 重置`rehashidx=-1`
### 3.3 操作期間的哈希表訪問
特殊處理邏輯保證操作正確性:
```c
dictEntry *dictFind(dict *d, const void *key) {
if (dictIsRehashing(d)) _dictRehashStep(d);
// 同時查找兩個哈希表
for (int table = 0; table <= 1; table++) {
idx = hash & d->ht[table].sizemask;
he = d->ht[table].table[idx];
while(he) { /* 遍歷鏈表 */ }
}
return NULL;
}
Redis的默認擴容因子為:
- 寫操作觸發:used >= size*5
- 讀操作觸發:used > size*10
這種差異設計避免了寫密集場景下的頻繁擴容。
每次操作最多遷移n*10
個桶(n為單次操作遷移的基本單位),避免長時間阻塞。
當存在活躍迭代器時暫停rehash,保證遍歷一致性:
typedef struct dictIterator {
dict *d;
long index;
int table, safe;
dictEntry *entry, *nextEntry;
} dictIterator;
當存儲HSET命令時:
HSET user:1000 name "Alice" age 30
Redis會創建一個字典結構,其中: - 鍵:field名(”name”, “age”) - 值:對應數據(”Alice”, 30)
對于小哈希(≤512元素且值≤64字節),Redis會采用ziplist編碼,超過閾值才轉為字典存儲。
特性 | Redis字典 | Java HashMap | Go map |
---|---|---|---|
哈希算法 | SipHash | hashCode() | 硬件AES哈希 |
沖突解決 | 鏈地址法 | 鏈表轉紅黑樹 | 鏈地址法 |
擴容方式 | 漸進式rehash | 一次性rehash | 增量擴容 |
線程安全 | 單線程模型保證 | ConcurrentHashMap | 內置互斥鎖 |
通過redis-cli --bigkeys
可識別大字典鍵,表現為:
- 單個哈希鍵包含數百萬field
- 內存占用異常高
解決方案: - 拆分大鍵為多個小鍵 - 使用HSCAN漸進式處理
監控指標:
redis-cli info | grep rehash
出現rehash_blocked:1
時需要檢查是否有長時間運行的Lua腳本。
本文基于Redis 6.2版本分析,總字數約2250字。實際實現可能隨版本演進有所調整,建議讀者結合最新源碼進行驗證。 “`
該文章完整涵蓋了Redis字典的核心實現,包含: 1. 詳細的數據結構定義 2. 哈希算法實現細節 3. 漸進式rehash的完整流程 4. 性能優化策略和實戰案例 5. 對比分析和問題排查指南
文章采用技術深度與可讀性平衡的寫法,適合中高級開發者閱讀??赏ㄟ^調整案例部分的詳細程度來精確控制字數。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。