通過了解數據結構,就能知道對象性能,邊界條件,就自然而然知道如何恰當的使用它們,做業務時就能選到最合適的對象。
上圖是Redis最基本的結構體,所有Redis對象都被封裝在RedisObject中。最基本的結構代碼往往是最精簡的。該結構中有5個成員,type 4 比特,encoding也是4比特。從代碼得知:
Redis的數據類型不超過16種,編碼方式不超過16種,且類型跟編碼方式不一一對應,一種類型可能有多個編碼方式,數據也可以共享。
首先看Object的第一個成員type,實際上Redis里面一共有5種類型:字符串、列表、集合、有序集合、哈希,這幾種方式和type的對應關系見下表。
當字符串較小,Redis里字符串長度<=39時,會用EMBSTR編碼方式。在這種編碼方式下,字符串跟Object在連續的內存上,省去了多次內存分配。不過當字符串增長或者改變時,不能用該種方式,需要換成第一種,所以長度限制為39。
String類型還有一種特殊的編碼方式,即字符串數值是整數的時候,為特殊的INT類型編碼。INT類型不需要ptr指到字符空間,而是直接用指針的值代表字符串的值,因此ptr已經不是指針。這樣就省去了sds開銷,其內存占用最小。實際上在Redis里,程序啟動時直接創建了10000個RedisObject,代表1-10000的整型,如果LRU沒有意義,后面就沒有其他開銷,用預先分配好的值。簡單來說,整數類型的Value比普通的Value節省內存,其值為0-10000,LRU無效情況下的String Object可共享,而且一般情況下沒必要強求EMBSTR。
上圖是壓縮列表,它相當于把所有的成員都疊在一起,沒有額外的數據結構,空間占用比較小。缺點是讀寫的時候整個壓縮列表都需要修改,所以一般在數據量小的時候才使用,一般能達到10倍的壓縮比。數據量大小都可以通過配置文件更改,Hash和List的默認情況是512和64,需要利用時就對業務進行改造,可以按日期拆分,每天一個Key,也可以按數值取模,或按前綴拆分等。通過合理的拆分,充分利用壓縮列表特性,壓縮率可達10倍,平均為5倍。
那其他容器在普通情況下用什么樣的數據結構呢?算法類的數據結構里用的最多的為哈希表。因為它的讀寫復雜度都是O(1),是所有數據結構里面最快的一種。Redis中的哈希表使用鏈地址法解決hash沖突問題,若有多個key的hash值一致,通過遍歷鏈表的形式找到目標Key。當哈希表的負載因子過大時,沖突幾率變大,其性能就會下降。Redis里面哈希表槽的數目是動態增長的,HT默認初始大小為4。當負載因子超出合理范圍(0.1 – 5)時進行擴縮容(rehash),將原來哈希表里面的數值rehash,放在新的哈希表里面,也就是說同時存在兩個哈希表,一舊一新。不過一次性rehash太多的Key可能導致服務長時間不可用,Redis采用漸進式rehash,分批進行。
Redis里用字典結構對Redis進行封裝,主要就是兩個哈希表,讀寫復雜度均為O(1)。DICT的讀寫效率最高。那什么時間進行漸進式Rehash的算法呢?每次對DICT執行添加、刪除、查找或者更新操作時,除了執行指定的操作以外,還會順帶將ht[0] 哈希表在rehashidx索引上的所有鍵值對rehash到ht[1],并將rehashidx的值增1;直到整個ht[0]全部完成rehash后,rehashindex設為-1,釋放ht[0],ht[1]置為ht[0],在ht[1]中創建一個新的空白表。
跳躍表是哈希里面用來做排序的,實現簡單,算法巧妙,算法效率和平衡樹一樣。算法核心為,每插入一個節點,節點是隨機數,第n+1層的節點數目為第n層的1/4,性能如上表所示。
原地址:https://mp.weixin.qq.com/s/n4HXKXPKf87qgZ_e6s4gPg