多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > 數據庫 > 數據庫應用 > 《Redis設計與實現》[第一部分]數據結構與對象-C源碼閱讀(一)

《Redis設計與實現》[第一部分]數據結構與對象-C源碼閱讀(一)

來源:程序員人生   發布時間:2016-06-03 19:25:39 閱讀次數:4158次

1、簡單動態字符串SDS

關鍵字:空間預分配,惰性空間釋放,2進制安全

C字符串不容易更改,所以Redis中把C字符串用在1些不必對字符串值進行修改的地方,作為字符串字面量(String literal),比如打印日志:
redisLog(REDIS_WARING, “Redis is now ready to exit, bye bye…”);

在Redis數據庫中,包括字符串的鍵值對在底層都是由SDS實現的。

SDS還被用作緩沖區(buffer):AOF模塊中的AOF緩沖區,和客戶端狀態中的輸入緩沖區,都是SDS實現的。

源碼

SDS結構的定義在sds.h中:

/* * 保存字符串對象的結構 */ struct sdshdr { // buf 中已占用空間的長度 int len; // buf 中剩余可用空間的長度,即未使用空間 int free; // 數據空間 char buf[]; };

獲得1個SDS長度的復雜度為O(1),由SDS的API在履行時自動設置和更新SDS長度,使用SDS不必進行任何手動修改長度的工作。

空間分配

SDS的空間分配策略是:當SDS API需要對SDS進行修改時,API會先檢查SDS的空間是不是滿足修改所需的要求,若不滿足,API會自動將SDS的空間擴大至履行修改所需的大小,然后才履行實際的修改操作,杜絕了產生緩沖區溢出的可能性。

通過未使用空間,SDS實現了空間預分配和惰性空間釋放兩種優化策略:

  • 空間預分配

空間預分配用于減少連續履行字符串增長操作所需的內存分配次數。
通過這類預分配策略,SDS將連續增長N次字符串所需的內存重分配次數從一定N次下降為最多N次。
其中額外分配的未使用空間數量由以下公式決定:

1. 如果對SDS進行修改后,SDS的長度(即len屬性的值)小于1MB,就分配和len屬性一樣大小的未使用空間,即len屬性的值和free屬性的值相同   
2. 如果對SDS進行修改以后,SDS的長度大于等于1MB,就分配1MB的未使用空間。  
  • 惰性空間釋放

惰性空間釋放用于優化SDS字符串縮短操作的內存重分配操作:當SDS的API需要縮短SDS保存的字符串時,程序其實不立即便用內存重分配來回收縮短后多出來的字節,而是使用free屬性將這些字節的數量記錄起來,并等待將來使用。

SDS的API都是2進制安全的(binary-safe),所有SDS API都會以2進制的方式處理SDS寄存在buf數組里的數據,程序不會對其中的數據做任何限制、過濾、或假定,數據在寫入時是甚么樣的,被讀取時就是甚么樣。

Redis用SDS的buf數組保存2進制數據而不是字符。

SDS可以兼容部份C字符串函數。

2、鏈表

關鍵字:多態

當1個列表鍵包括了數量比較多的元素,或是列表中包括的元素都是比較長的字符串時,Redis就會使用鏈表作為列表鍵的底層實現。

integers列表鍵的底層實現就是1個鏈表,鏈表中的每一個結點都保存了1個整數值。

除鏈表以外,發布與定閱、慢查詢、監視器等功能也用到了鏈表,Redis服務器本身還使用鏈表保存多個客戶真個狀態信息,和使用鏈表來構建客戶端輸出緩沖區(output buffer)。

源碼

鏈表結構的定義在adlist.h中:

/* - 雙端鏈表節點 */ typedef struct listNode { // 前置節點 struct listNode *prev; // 后置節點 struct listNode *next; // 節點的值 void *value; } listNode; /* *雙端鏈表迭代器 */ typedef struct listIter { // 當前迭代到的節點 listNode *next; // 迭代的方向 int direction; } listIter; /* - 雙端鏈表結構 */ typedef struct list { // 表頭節點 listNode *head; // 表尾節點 listNode *tail; // 節點值復制函數 void *(*dup)(void *ptr); // 節點值釋放函數 void (*free)(void *ptr); // 節點值對照函數 int (*match)(void *ptr, void *key); // 鏈表所包括的節點數量 unsigned long len; } list;

list結構為鏈表提供了表頭指針head、表尾指針tail,和鏈表長度計數器len,dup、free和match成員則是用于實現多態鏈表所需的類型特定函數:

  • dup函數用于復制鏈表結點所保存的值
  • free函數用于釋放鏈表結點所保存的值;
  • match函數則用于對照鏈表結點所保存的值和另外一個輸入值是不是相等。

Redis的鏈表實現的特性以下:

  • 雙端、無環、帶表頭指針和表尾指針、帶鏈表長度計數器、多態

3、字典

關鍵字:多態,漸進式rehash,murmurhash2

Redis的數據庫就是使用字典來作為底層實現的,對數據庫的增、刪、改、查也是構建在對字典的操作之上的。

字典還是哈希鍵的底層實現之1,當1個哈希鍵包括的鍵值對照較多,或是鍵值對中的元素都是比較長的字符串時,Redis就使用字典作為哈希鍵的底層實現。

Redis的字典使用哈希表作為底層實現,1個哈希表里可以有多個哈希表結點,每一個哈希表結點就保存了字典中的1個鍵值對。

源碼

字典所使用的哈希表在dict.h中定義:

/* * 哈希表 * 每一個字典都使用兩個哈希表,從而實現漸進式 rehash 。 */ typedef struct dictht { // 哈希表數組,數組中的每一個元素都是1個指向dictEntry結構的指針 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩碼,用于計算索引值 // 總是等于 size - 1 unsigned long sizemask; // 該哈希表已有節點的數量 unsigned long used; } dictht;
  • table屬性是1個數組,數組中的每一個元素都是1個指向dictEntry結構的指針,每一個dictEntry結構保存著1個鍵值對。
  • size屬性記錄了哈希表的大小,即是table數組的大小。
  • used屬性則記錄了哈希表目前已有結點(鍵值對的數量)
  • sizemask屬性和哈希值1起決定1個鍵應當被放到table數組的哪一個索引上面
/* * 哈希表節點 */ typedef struct dictEntry { // 鍵 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 指向下個哈希表節點,構成鏈表 struct dictEntry *next; } dictEntry;
  • key屬性保存著鍵值對中的鍵
  • v屬性保存鍵值對中的值,其中鍵值對中的值可以是1個指針,或是1個uint64_t整數,或是1個int64_t整數
  • next屬性指向另外一個哈希表結點的指針,使用鏈地址法解決鍵沖突問題。
/* * 字典 */ typedef struct dict { // 類型特定函數 dictType *type; // 私有數據 void *privdata; // 哈希表 dictht ht[2]; // rehash 索引 // 當 rehash 不在進行時,值為 ⑴ int rehashidx; /* rehashing not in progress if rehashidx == ⑴ */ // 目前正在運行的安全迭代器的數量 int iterators; /* number of iterators currently running */ } dict;

type屬性和privdata屬性是針對不同類型的鍵值對,為創建多態字典而設置的:

  • type屬性是1個指向dictType結構的指針,每一個dictType結構保存了1簇用于操作特定類型鍵值對的函數,Redis會為用處不同的字典設置不同的類型特定函數。
  • privdata屬性保存了需要傳給那些類型特定函數的可選參數。
/* * 字典類型特定函數 */ typedef struct dictType { // 計算哈希值的函數 unsigned int (*hashFunction)(const void *key); // 復制鍵的函數 void *(*keyDup)(void *privdata, const void *key); // 復制值的函數 void *(*valDup)(void *privdata, const void *obj); // 對照鍵的函數 int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 燒毀鍵的函數 void (*keyDestructor)(void *privdata, void *key); // 燒毀值的函數 void (*valDestructor)(void *privdata, void *obj); } dictType;
  • ht屬性是1個包括兩個項的數組,數組中的每一個項都是1個dictht哈希表,1般,字典只使用ht[0]哈希表,ht[1]哈希表只會在對ht[0]哈希表進行rehash時使用。
  • rehashidx屬性記錄了rehash目前的進度,如果目前沒有在進行rehash,那末它的值為⑴.
/* * 字典迭代器 * - 如果 safe 屬性的值為 1 ,那末在迭代進行的進程中, - 程序依然可以履行 dictAdd 、 dictFind 和其他函數,對字典進行修改。 * - 如果 safe 不為 1 ,那末程序只會調用 dictNext 對字典進行迭代, - 而不對字典進行修改。 */ typedef struct dictIterator { // 被迭代的字典 dict *d; // table :正在被迭代的哈希表號碼,值可以是 0 或 1 。 // index :迭代器當前所指向的哈希表索引位置。 // safe :標識這個迭代器是不是安全 int table, index, safe; // entry :當前迭代到的節點的指針 // nextEntry :當前迭代節點的下1個節點 // 由于在安全迭代器運作時, entry 所指向的節點可能會被修改, // 所以需要1個額外的指針來保存下1節點的位置, // 從而避免指針丟失 dictEntry *entry, *nextEntry; long long fingerprint; /* unsafe iterator fingerprint for misuse detection */ } dictIterator;

哈希

Redis計算哈希值和索引值的方法以下:

// 使用字典設置的哈希函數,計算鍵key的哈希值 hash = dict->type->hashFunction(key); // 使用哈希表的sizemask屬性和哈希值,計算出索引值 // 根據情況不同,ht[x]可以是ht[0]或ht[1] index = hash & dict->ht[x].sizemask;
/* ------------------------- hash functions ------------------------------ */ /* Thomas Wang's 32 bit Mix Function */ unsigned int dictIntHashFunction(unsigned int key) { key += ~(key << 15); key ^= (key >> 10); key += (key << 3); key ^= (key >> 6); key += ~(key << 11); key ^= (key >> 16); return key; } /* Identity hash function for integer keys */ unsigned int dictIdentityHashFunction(unsigned int key) { return key; } static uint32_t dict_hash_function_seed = 5381; void dictSetHashFunctionSeed(uint32_t seed) { dict_hash_function_seed = seed; } uint32_t dictGetHashFunctionSeed(void) { return dict_hash_function_seed; } /* MurmurHash2, by Austin Appleby * Note - This code makes a few assumptions about how your machine behaves - * 1. We can read a 4-byte value from any address without crashing * 2. sizeof(int) == 4 * * And it has a few limitations - * * 1. It will not work incrementally. * 2. It will not produce the same results on little-endian and big-endian * machines. */ unsigned int dictGenHashFunction(const void *key, int len) { /* 'm' and 'r' are mixing constants generated offline. They're not really 'magic', they just happen to work well. */ uint32_t seed = dict_hash_function_seed; const uint32_t m = 0x5bd1e995; const int r = 24; /* Initialize the hash to a 'random' value */ uint32_t h = seed ^ len; /* Mix 4 bytes at a time into the hash */ const unsigned char *data = (const unsigned char *)key; while(len >= 4) { uint32_t k = *(uint32_t*)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } /* Handle the last few bytes of the input array */ switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; h *= m; }; /* Do a few final mixes of the hash to ensure the last few * bytes are well-incorporated. */ h ^= h >> 13; h *= m; h ^= h >> 15; return (unsigned int)h; } /* And a case insensitive hash function (based on djb hash) */ unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len) { unsigned int hash = (unsigned int)dict_hash_function_seed; while (len--) hash = ((hash << 5) + hash) + (tolower(*buf++)); /* hash * 33 + c */ return hash; }

當字典被用作數據庫的底層實現,或是哈希鍵的底層實現時,Redis使用MurmurHash2算法計算鍵的哈希值:

  • 該算法的優點在于,即便輸入的鍵是有規律的,算法仍能給出1個很好的隨機散布性,并且算法的計算速度也非常快。

為了讓哈希表的負載因子(load factor)保持在1個公道的范圍以內,當哈希表保存的鍵值對數量太多或太少時,程序需要對哈希表的大小進行相應的擴大或收縮。

  • 哈希表的負載因子計算公式:load_factor = ht[0].used/ht[0].size

rehash

擴大和收縮哈希表的工作可以通過履行rehash(重新散列)操作來完成,Redis對字典的哈希表履行rehash的步驟以下:

  • 為字典的ht[1]哈希表分配空間,這個哈希表的空間大小取決于要履行的操作,和ht[0]當前包括的鍵值對數量(即ht[0].used屬性的值)

    1. 如果履行的是擴大操作,那末ht[1]的大小為第1個大于等于ht[0].used*2的2^n(2的n次方冪);
    2. 如果履行的是收縮操作,那末ht[1]的大小為第1個大于等于ht[0].used的2^n。
  • 將保存在ht[0]中的所有鍵值對rehash到ht[1]上面:rehash指的是重新計算鍵的哈希值和索引值,然后將鍵值對放置到ht[1]哈希表的指定位置上。

  • 當ht[0]包括的所有鍵值對都遷移到ht[1]以后(ht[0]變成空表),釋放ht[0],將ht[1]設置為ht[0],并在ht[1]新創建1個空白哈希表,為下1次rehash做準備。

當以下條件中的任意1個被滿足時,程序會自動開始對哈希表履行擴大操作:

  • 服務器目前沒有在履行BGSAVE命令或BGREWRITEAOF命令,并且哈希表的負載因子大于等于1
  • 服務器目前正在履行BGSAVE命令或BGREWRITEAOF命令,并且哈希表的負載因子大于等于5

在履行BGSAVE命令或BGREWRITEAOF命令的進程中,Redis需要創建當前服務器進程的子進程,而大多數操作系統都采取寫時復制(copy-on-write)技術來優化子進程的使用效力,所以在子進程存在期間,服務器會提高履行擴大操作所需的負載因子,從而盡量地避免在子進程存在期間進行哈希表擴大操作,這避免了沒必要要的內存寫入操作,最大限度地節儉內存。

當哈希表的負載因子小于0.1時,程序自動開始對哈希表履行收縮操作。

漸進式rehash

為了不rehash對服務器性能造成影響,服務器不是1次性將ht[0]里面的所有鍵值對全部rehash到ht[1],而是分屢次、漸進式地將ht[0]里面的鍵值對漸漸rehash到ht[1]。

以下是哈希表漸進式rehash的詳細步驟:

  1. 為ht[1]分配空間,讓字典同時持有ht[0]和ht[1]兩個哈希表。

  2. 在字典中保持1個索引計數器變量rehashidx,值設置為0,表示rehash工作正式開始

  3. 在rehash進行期間,每次對字典履行添加、刪除、查找或更新操作時,程序除履行指定的操作以為,還會順帶將ht[0]哈希表在rehashidx索引上的所有鍵值對rehash到ht[1],當rehash工作完成以后,程序將rehashidx屬性的值增1。

  4. 隨著字典操作的不斷履行,終究在某個時間點上,ht[0]的所有鍵值對都會被rehash到ht[1]上,這是程序將rehashidx屬性的值設為⑴,表示rehash操作已完成

漸進式rehash采取分而治之的方式,將rehash鍵值對所需的計算工作均攤到對字典的每一個添加、刪除、查找和更新操作上,從而避免了集中式rehash而帶來的龐大計算量。

在進行漸進式rehash的進程中,字典會同時使用ht[0]和ht[1]兩個哈希表,所以在漸進式rehash進行期間,字典的刪除、查找、更新會在兩個哈希表上進行,比如現在ht[0]中查找,沒找到再去ht[1]查找

在漸進式rehash履行期間,新添加到字典的鍵值對1律會被保存到ht[1]里面,而ht[0]則不再進行任何添加操作,這樣保證了ht[0]包括的鍵值對數量只減不增,隨著rehash操作的履行終究變成空表。

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 秋霞一级成人欧美理论 | 2018年国产成人精品视频 | 精品久久久久久午夜 | 性欧美极品xxxx欧美一区二区 | 四虎东方va私人影库在线观看 | 国产欧美一区二区三区视频在线观看 | 偷自拍第一页 | 在线免费观看h视频 | 国产大片免费观看中文字幕 | 天堂网址 | 456成人免费高清视频 | 日韩 亚洲 中文 图片 小说 | 欧美性猛交xxxx免费看 | 韩日一级视频 | 日韩精品视频在线播放 | 亚洲国产成a人v在线观看 | 在线精品国精品国产不卡 | 伊人国产在线 | yy6080久久亚洲精品 | 国产一区二区三区免费在线视频 | 亚洲图片天堂 | 国产一区二区不卡视频 | 亚洲玖玖 | 中国精品videossex中国高清 | 成人不卡在线 | 午夜爱爱免费视频 | 韩国三级午夜理伦三级99 | 日韩免费福利视频 | xxxx18野外xxxxfreexxxx日本 | 国内精品哆啪啪 | 高清视频在线播放ww | 欧美精品第1页在线播放 | 欧美性受xxxx黑人xxxx | 91精品乱码一区二区三区 | 亚洲国产专区 | 伊人久久大香线蕉综合7 | 亚洲高清视频在线 | 色综合久久综合欧美综合图片 | 99热成人精品国产免男男 | 免费国产片 | 欧美亚洲精品一区 |