REDIS 字典資料結構

來源:互聯網
上載者:User

標籤: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結果如下:

  1. typedef struct dict {

  2.     //這裡封裝了dic的函數指標結構體 典型的C寫法 如果是c++ 就是一個類 更易讀

  3.     dictType *type;

  4. void *privdata;

  5.    //2個字典  一個空 一個是需要寫入的

  6.    dictht ht[2];      

  7.    //如果重新雜湊  就是擴容 這個標記位就會改寫

  8. int rehashidx;

  9. int iterators;     

  10. } dict;

    rehashidx 表示正在索引的索引值,字典正在賦值的索引號。

題外話:如果用C++來寫  程式碼片段更加容易看懂。

字典迭代器討論

typedef struct dictIterator {
// 正在迭代的字典
    dict *d;               
int table,              // 是雜湊表1還是2
        index,              // 迭代那個雜湊槽
        safe;             
    dictEntry *entry,       // 現在雜湊結點
*nextEntry;   // 後面一個
} dictIterator;

 

這裡的迭代器提出了safe欄位:迭代器的安全

迭代器安全:REDIS不是一次性全部遷移過來的,而是根據時間片來遷移,這樣的話也就是如果沒有遷移完的話,如果有插入迭代器或者刪除迭代器存在的話,可能會導致漏掉或者多複製現象存在。

這樣的話 還是採用最好的戰術模式:記錄操作這個dic的迭代器數量,只有當全部是安全迭代器時,才可以進行遷移工作。

在生產環境下,如果是HASHTABLE是多線程的呢? 多個線程進行讀和寫,可控制性將會變得非常不可控啊~!  而且如果是多線程,一致性怎麼能夠得到保證呢~!

  • 在每次遷移完  ht[i]會釋放記憶體 然後制空。 沒遷移完之前,就會查看2個字典桶。

關於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 字典資料結構

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.