Redis設計與實現-內部數據結構篇


  題記:這本書是2015年11月份開始讀的,大約花了一個多月的時間通讀了一遍,最近由於需要對redis做一些深入的了解,因此又花了兩個多月仔細精讀了一遍,由於本書設計的內容較多,且每部分的內容都比較細致,因此在整理讀書筆記的時候花了很多時間,但確實也收獲了很多,本書是針對redis底層的數據結構部分做的整理。 -----Dimmacro 2016年11月7日18:21:54。

我們知道redis可以存儲字符串、列表、哈希對象、集合、有序集合等五種對象類型,但是在redis內部,根據不同對象類型的數據特點,有對應的數據結構作為其底層的存儲結構,而且即使是同一對象類型,當數據量或數據大小不同時,為了提供更好的性能及減少內存使用率,也會切換不同的底層數據結構。因此了解這些底層數據結構對我們深入了解redis分布式緩存有非常重要的幫助。

(一)數據結構部分-字符串
  1. redis是鍵值對數據庫,每個鍵都是字符串對象,而值可以是字符串對象,列表對象,哈希對象,集合對象,有序集合對象五種之一;
  2. redis使用sds(simple dynamic string)簡單動態字符串來表示最基本的字符串數據,該結構記錄了用於保存字符串的字節數組char buf[]、已使用長度int len和未使用長度int free。有點類似於java中的String對象。
  3. 此sds利用c字符串作為字面量,並遵循以空字符'\0'作為字符串末尾的C風格,使得其可以直接重用C字符串函數庫的部分函數,但相比較於C字符串有以下優點:
    • 直接保存字符串長度而不是像C那樣需要遍歷才能獲取長度;
    • 通過空間預分配及惰性空間釋放來減少由於修改字符串帶來的內存重分配。空間預分配是指:當需要擴展字符數組容量時,如果分配后的長度將小於1MB,那么會預分配與當前len長度一樣的字節量,如果超過1MB,則會分配1MB。惰性空間釋放是指:當縮短sds字符串時,多余出來的字節數組並不回收,而是通過增長free記錄起來,這樣下次當需要增長到時候如果free本身就夠用了,就不需要申請內存了。當然,也有API可調用來主動釋放。
    • 使用二進制方式處理buf數組,保持二進制數據,因此可以保存除文本數據外的其他格式,如圖片,音視頻,壓縮文件等;
    •  

 
(二)數據結構部分-鏈表linkedlist
  1. 鏈表通過高效的節點重排、順序訪問、增刪節點靈活調整期長度等特點,應用於redis中的列表鍵、發布與訂閱、慢查詢、監視器等;
  2. 鏈表的數據結構:表頭head、表尾tail、節點數量即長度len、節點值復制函數dup、釋放函數free、節點值對比函數match;
  3. 表中節點數據結構:前置節點prev、后置節點next、節點值value;
  4. 鏈表特點:雙端、無環、表頭表尾指針、長度計數器、多態(使用void *指針來保存節點值,可以用於保存各種不同類型的值)
 
(三)數據結構部分-字典hashtable(字典--->哈希表2張--->哈希表數組--->哈希表節點
  1. 字典是redis數據庫的底層實現,對數據的增刪查改操作都是構建在字典操作上的;
  2. 字典dict數據結構:類型特定函數dictType(多態字典)、私有函數void *privdata(類型特定的參數)、哈希表數據dictht ht[2](兩個哈希表用於做rehash和漸進式hast);
  3. 哈希表是字典的底層實現,其結構為:dictEntry **table哈希表數組、long size哈希表大小、long sizemask哈希表掩碼,用於計算索引值、long used哈希表已有節點數量、int trehashidx rehash是否在進行的標識;
  4. 哈希表數組的元素是哈希表節點,是保存字典中鍵值對的地方,即真正保存數據的地方,其結構為:*key鍵指針、v值、dictEntry *next下個哈希表節點,用於哈希值相同時,將當期值插入到表頭,形成列表,解決鍵沖突問題;
  5. 哈希過程:根據鍵值使用MurmurHash2算法計算哈希值,然后與表掩碼取模,得到index作為存放哈希表數組的位置,如果當前index已經有節點了,則在此節點頭插入當前節點,形成鏈表。類似java中map結構put值的過程;
  6. rehash的過程:當字典中的哈希表[0]變化的時候,為了讓負載因子維持在合理范圍,會做rehash操作,其步驟如下:
    • 為哈希表[1]分配空間,並根據擴展還是壓縮操作設置其大小,值為2的N次方,擴展值為第一個大於等於ht[0]*used*2的2的n次冪,收縮操作為第一個大於等於ht[0]*used的2的N次冪;
    • 將ht[0]中的所有鍵值rehash到ht[1]上,完成后釋放ht[0],並間ht[1]設為ht[0],並重新創建一個空白的ht[1哈希表為下一次rehash做准備;(如果鍵值量大,會采用漸進式rehash的方式,在此期間會同時使用ht[0]和ht[1])。
(四)數據結構部分-跳躍表skiplist
  1. 跳躍表是一種有序數據結構,通過在每個節點中維持多個指向其他及節點的指針達到快速訪問節點的目的;
  2. redis使用跳躍表用作實現有序集合鍵以及在集群節點中用作內部數據結構
(五)數據結構部分-整數集合intset
  1. 用於少量整數的集合,是集合鍵的底層實現之一;
  2. 整數集合intset由encoding編碼方式、length包含元素數量、contents元素數組三部分組成,各項在數組contents中按值的大小從小到大有序排列,不包含重復項;
  3. 當新添加的元素值大於現有集合encoding制定的范圍時引發升級,現有元素所在位數增加;升級操作可以提升存儲數據的靈活性,並節約內存;
  4.  不支持降級操作
(六)數據結構部分-壓縮列表ziplist
    1. 壓縮列表是一系列特殊編碼的連續內存塊組成的順序型數據結構,為節約內存而開發,是列表鍵和哈希鍵的底層實現之一;
    2. 組成部分:zlbytes:4個字節,記錄整個壓縮列表占用的內存字節數;zltail4個字節,記錄表尾節點距離壓縮列表起始地址有多少字節;zllen,2個字節,記錄壓縮列表包含的節點數量;entryX列表節點;zlend:1個字節,特殊值0xFF用於標記壓縮列表的末端;
    3. 列表節點由三部分構成:previous_entry_length記錄壓縮列表中前一個節點的長度,可用於從表尾向表頭遍歷;encoding記錄本節點所保存的數據類型及長度;content保存節點值,可以是字節數組或整數;
    4. 當前一節點長度小於254字節,previous_entry_length占1字節,否則占5字節,因此有可能插入一個長度大於254字節的節點到表頭是,如果后續節點都介於250到253之間,則可能發生連鎖更新,影響性能。不過概率很小。

######以上文字來自Dimmacro,轉載請說明來源:http://www.cnblogs.com/dimmacro/ #######

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM