標籤:style blog http color io strong ar for 資料
對於REDIS來講 其實就是一個字典結構,key ---->value 就是一個典型的字典結構
【當然 對於vaule來講的話,有不同的記憶體組織圖 這是後話】
試想一個這樣的儲存情境:
key:"city"
value:"beijing"
如果有若干個這樣的索引值對,你該怎麼去儲存它們呢 要保證寫入和查詢速度非常理想~!
拋開redis不說,如果你想要儲存 快速尋找的話, Hash演算法是最快的,理想的雜湊函數可以帶來O(1)的尋找速度,你都這樣想,那麼redis也的確採用這種方法來做~!
但是HASH演算法有2個致命的弱點:1)填滿因數不能太滿 2)不好的HASH演算法可能會導致一個衝突率非常高。
填滿因數不能太滿 這個理論上一般為0.5左右 過高 就是雜湊槽都被塞滿了 ,即使在好的雜湊分布演算法 也無法避免key衝突。不好的雜湊分布演算法
丟到第一個因素來講, 如果一個不好的雜湊分布演算法會導致了key分布不均勻,也就是通過雜湊Function Compute出來的雜湊槽都是落在了一個桶裡,這樣的雜湊分布演算法是最不理想的,最理想的情況下是 保證每個key都落在不同的雜湊槽裡【雜湊槽>key】
實際儲存的雜湊儲存設計
1)一般來講,雜湊分布函數確定後,可調控的因子就是這個填滿因數 如果填滿因數大於你卡的某個閾值,那麼你就要做雜湊結構遷移工作,遷移到一個更大的雜湊槽中。而對用同用的這種雜湊分布 函數,有許多人用各種數學方法計算過,這裡也沒有深入研究這個分布函數,倒是在這個填滿因數上面,卡的閾值是需要仔細思考。
2) 雜湊槽遷移 雜湊槽在遷移的過程中,無論是單線程環境還是多線程環境,都會造成一個短暫的停止服務過程。這個對生產環境會造成非常短暫的影響 我個人認為在伺服器 特別儲存伺服器過程中,本來就是面向大量高並發儲存,應該可以把雜湊槽設定的更加大一些,這樣儘可能避免雜湊槽的一個遷移。
REDIS雜湊儲存設計
前面說到的一些情境是一些雜湊儲存引擎都會面臨到的問題,REDIS的解決方面如下:
1)代碼層面 我覺得REDIS的代碼開發人員寫代碼風格真的是太棒了 封裝性,易看性都是很值得學習的 一步一步的看看:
用C寫的redis,但是裡面有很多STL的那種設計理念: 迭代器 動態記憶體管理 等
如果你寫一個雜湊儲存,最基本的幾個子資料結構是必須的:
每個基本的元素
struct DicElement
{
/* data */
void* key;
void* value;
struct DicElement *next;
};
雜湊槽
struct DicElement **HASHTABLE[HASHSOLT];
這是redis的真實源碼,中間用了一個union聯合體 要麼是指標,要麼就是一個64位的數字。
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
dictht就是一個完整的雜湊槽,這裡面記錄了table有多少個雜湊槽被用了,【used】 已經雜湊槽有多少個 【size】
一般對於靜態雜湊儲存結構來講 上面2個資料結構就可以了,但是redis有一個特性:就是支援擴容,動態擴容,和stl的vector的策略是相似的 當達到臨界閾值時,就會增加的到一倍。
真正的dic結果如下:
typedef struct dict {
//這裡封裝了dic的函數指標結構體 典型的C寫法 如果是c++ 就是一個類 更易讀
dictType *type;
void *privdata;
//2個字典 一個空 一個是需要寫入的
dictht ht[2];
//如果重新雜湊 就是擴容 這個標記位就會改寫
int rehashidx;
int iterators;
} dict;
rehashidx 表示正在索引的索引值,字典正在賦值的索引號。
題外話:
如果用C++來寫 程式碼片段更加容易看懂。
字典迭代器討論
typedef struct dictIterator {
// 正在迭代的字典
dict *d;
int table, // 是雜湊表1還是2
index, // 迭代那個雜湊槽
safe;
dictEntry *entry, // 現在雜湊結點
*nextEntry; // 後面一個
} dictIterator;
這裡的迭代器提出了safe欄位:迭代器的安全
迭代器安全:REDIS不是一次性全部遷移過來的,而是根據時間片來遷移,這樣的話也就是如果沒有遷移完的話,如果有插入迭代器或者刪除迭代器存在的話,可能會導致漏掉或者多複製現象存在。
這樣的話 還是採用最好的戰術模式:記錄操作這個dic的迭代器數量,只有當全部是安全迭代器時,才可以進行遷移工作。
在生產環境下,如果是HASHTABLE是多線程的呢? 多個線程進行讀和寫,可控制性將會變得非常不可控啊~! 而且如果是多線程,一致性怎麼能夠得到保證呢~!
關於REDIS雜湊槽擴容設計
1) 每次進行add del,lookfor操作時,都會做執行dicRehashStep函數一次,在調用dictRehash(d,1)一次,這裡的一就是執行rehashidex那個下一個不為null的值一次,也就是把一個槽給遷移到ht[1]中,只執行一次 也是為了不會讓redis出現太長時間的暫停服務而考慮的一種設計。 但是這裡的前提就是安全iterator迭代器的數量為0 也就是不包含增 刪 改這3個操作的iterator~! 如果含有增,刪,改,那麼有可能會出現漏掉entry的情況。
2)這裡是提示用多少毫秒作為一個間隔來做rehash操作,也就是把ht[0]遷移到ht[1]上,每次的base值是100,時間是由伺服器來控制,這是第2種遷移方式,這種遷移方式每次遷移的槽多,相對來講所需要的時間更多,所以ms間隔是需要仔細評估,如果沒有弄好,會造成一個時間上的空檔。
int dictRehashMilliseconds(dict *d, int ms) {
long long start = timeInMilliseconds();
int rehashes = 0;
while(dictRehash(d,100)) {
rehashes += 100;
if (timeInMilliseconds()-start > ms) break;
}
return rehashes;
}
REDIS 字典資料結構