生活随笔
收集整理的這篇文章主要介紹了
将一个键值对添加入一个对象_细品Redis高性能数据结构之hash对象
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
背景
上一節(jié)講Redis的高性能字符串結構SDS,今天我們來看一下redis的hash對象。
Hash對象
簡介
- redis的hash對象有兩種編碼(底層實現(xiàn))方式,字典編碼和壓縮列表編碼。在使用字典編碼的時候程序就是將hash表的key存為字典的鍵,hash的value作為字典的值,字典的鍵值都是用的是字符串類型。
- 在哈希對象保存的所有鍵值對的鍵和值的字符串長度都小于 64 字節(jié)和哈希對象保存的鍵值對數(shù)量小于 512 個使用的是ziplist,不能滿足這個的使用的是hashtable(字典編碼)
深度理解
ZipList(壓縮列表)
redis 的壓縮列表是一塊連續(xù)的內(nèi)存空間,元素之間緊挨著存儲,沒有任何冗余空間源碼struct ziplist<T> {
int32 zlbytes; // 整個壓縮列表占用字節(jié)數(shù)
int32 zltail_offset; // 最后一個元素距離壓縮列表起始位置的偏移量,用于快速定位到最后一個
節(jié)點
int16 zllength; // 元素個數(shù)
T[] entries; // 元素內(nèi)容列表,挨個挨個緊湊存儲
int8 zlend; // 標志壓縮列表的結束,值恒為 0xFF
}/**
*entry對象源碼
*/
struct entry {
int<var> prevlen; // 前一個 entry 的字節(jié)長度
int<var> encoding; // 元素類型編碼
optional byte[] content; // 元素內(nèi)容
}
壓縮列表是支持雙向遍歷,所以才會有zltail_offset這個字段的,可以進行快速定位到最后一個元素。然后倒序查找(O(1))prevlen 表示的是前一個字段的長度,有人就有疑問了,為什么是前一個entry的長度,為什么不是自己的呢,其實他還有一個作用是在壓縮列表倒敘遍歷的時候,需要通過這個字段來快速定位到下一個元素的位置,由于他是一個連續(xù)的存儲空間,已經(jīng)知道當前元素的位置+這個空間地址就可以確定寫一個entry的位置。為什么會這樣呢?因為entry的大小是不一樣的。如果是一樣的話就可以根據(jù)下表進行行為(個人理解,有錯誤還請指出),且prevlen 是一個變長的整數(shù),redis的常規(guī)操作,將不同長度使用不同的數(shù)據(jù)類型。節(jié)省內(nèi)存encoding的意思是元素的編碼類型,有了這個字段就可以決定元素內(nèi)容的設定,內(nèi)存大小的分配。防止內(nèi)存分配浪費的一種方式。具體內(nèi)容查看下面1、00xxxxxx 最大長度位 63 的短字符串,后面的 6 個位存儲字符串的位數(shù),剩余的字
節(jié)就是字符串的內(nèi)容。
2、01xxxxxx xxxxxxxx 中等長度的字符串,后面 14 個位來表示字符串的長度,剩余的
字節(jié)就是字符串的內(nèi)容。
3、10000000 aaaaaaaa bbbbbbbb cccccccc dddddddd 特大字符串,需要使用額外 4 個字節(jié)
來表示長度。第一個字節(jié)前綴是 10,剩余 6 位沒有使用,統(tǒng)一置為零。后面跟著字符串內(nèi)
容。不過這樣的大字符串是沒有機會使用的,壓縮列表通常只是用來存儲小數(shù)據(jù)的。
4、11000000 表示 int16,后跟兩個字節(jié)表示整數(shù)。
5、11010000 表示 int32,后跟四個字節(jié)表示整數(shù)。
6、11100000 表示 int64,后跟八個字節(jié)表示整數(shù)。
7、11110000 表示 int24,后跟三個字節(jié)表示整數(shù)。
8、11111110 表示 int8,后跟一個字節(jié)表示整數(shù)。
9、11111111 表示 ziplist 的結束,也就是 zlend 的值 0xFF。
10、1111xxxx 表示極小整數(shù),xxxx 的范圍只能是 (0001~1101), 也就是 1~13,因為
0000、1110、1111 都被占用了。讀取到的 value 需要將 xxxx 減 1,也就是整數(shù) 0~12 就是
最終的 value。
之前有講到hash對像選用壓縮列表的兩個前提條件,其中之一是鍵值的大小都小于64,具體為什么小于64和簡=鍵值對小于512就不具體說了,可以結合一下SDS中的擴容方式思考一下,壓縮列表沒有冗余空間,在進行擴容的時候會出現(xiàn)頻繁擴容,再加上占用空間大了后進行copy數(shù)據(jù)就很浪費性能了。所以當數(shù)據(jù)量大了后,就選擇了另一種數(shù)據(jù)結構那就是hashtable(字典)HashTable(字典)
簡介
- redis 的hashtable和java中的hashMap實現(xiàn)方式是類似的,都是通過數(shù)組和鏈表實現(xiàn)的。也就是key-value形式。當然它解決hash沖突的方式也是使用鏈地址法(解決hash沖突的幾種方法可以想一下),當不同的key創(chuàng)建出了相同的hash值時將vlue就放入鏈表上,如下圖。
- 在細節(jié)方面和java中的hashMap差別還是很大的。列如擴容的過程,key值得hash算法等等。接下來我們根據(jù)源碼細細的品一品。
- 官方給的解釋:字典(dictionary), 又名映射(map)或關聯(lián)數(shù)組(associative array), 是一種抽象數(shù)據(jù)結構, 由一集鍵值對(key-value pairs)組成, 各個鍵值對的鍵各不相同, 程序可以添加新的鍵值對到字典中, 或者基于鍵進行查找、更新或刪除等操作
其字典的底層結構是使用的是redis 中dict。不僅是hash對象底層使用了dict,而且在redis全局也是使用的是key-vlue結構,也就是字典的形式,還有Zset的數(shù)據(jù)結構底層也是基于redis 中的dict結構。我們來看一下其源碼:
// resdis 全局使用的字典結構
struct RedisDb {
dict* dict; // all keys key=>value
dict* expires; // all expired keys key=>long(timestamp)
...}
// 有序集合的底層數(shù)據(jù)結構
struct zset {
dict *dict; // all values value=>score
zskiplist *zsl;
}
2. dict結構深度解析
/** 字典** 每個字典使用兩個哈希表,用于實現(xiàn)漸進式 rehash*/
typedef struct dict {// 特定于類型的處理函數(shù)dictType *type;// 類型處理函數(shù)的私有數(shù)據(jù)void *privdata;// 哈希表(2 個)dictht ht[2];// 記錄 rehash 進度的標志,值為 -1 表示 rehash 未進行int rehashidx;// 當前正在運作的安全迭代器數(shù)量int iterators;} dict;
- 可以類的成員變量中看到有兩個hashtable,通常情況下是一個有值一個沒有值。在壓縮列表中我們遇到的問題是在擴容方面存在性能問題,這兩個hashtable就是來解決擴容問題的。在擴容和縮容時進行漸進式搬遷,當搬遷結束的時候將舊的hashtable進行刪除,新的hashtable 取而代之。
- 那我們來細細的研究一下hashtable,(Java中的hashtable是Java中hashMap的線程安全版本)。在這里的hashtable和java中的hashmap是類似的,解決hash沖突的方式通過分桶的方式。一維數(shù)組,二維鏈表。但是在擴容還是有一些區(qū)別的。
struct dictEntry {
void* key;
void* val;
dictEntry* next; // 鏈接下一個 entry
}
struct dictht {
dictEntry** table; // 二維
long size; // 第一維數(shù)組的長度
long used; // hash 表中的元素個數(shù)
...
}
- 來看一下redis中hash是如何進行的
1.大字典的擴容是非常耗時間的,需要重新申請新的數(shù)組,然后將舊的字典所有的鏈表中的元素重新掛接到新的數(shù)組下面,這個過程時間復雜度為O(n),作為單線程的redis怎么會把時間浪費在這里呢,。于是他就采用了漸進式處理的方式(說到漸進式是否能想到他漸進式批量根據(jù)key查詢呢scan 和 keys), rehash的過程點擊這里。其思想也就是我們上面所說的小步執(zhí)行。 - 聯(lián)系一下Set結構也是通過字典實現(xiàn)的,只不是所有的value都是NULL,有沒有想到什么?Java中的hashSet是不是也和這個類似呢?。
總結
hash對象有兩種底層實現(xiàn)方式,hashtable(字典) 和 ziplist(壓縮鏈表)壓縮鏈表由于是連續(xù)空間在剛開始數(shù)據(jù)量小的時候性能是顯著的,但是在數(shù)據(jù)量大的時候就會出現(xiàn)擴容慢的問題字典通過雙hahstable的方式,再加上漸進式hash的方式解決了壓縮列表的擴容的問題redis 高性能數(shù)據(jù)結構我們可以看到他在很對細節(jié)的把握很多,如不同的數(shù)字大小選用不同的字段類型,不同的存儲方式采用不同的
總結
以上是生活随笔為你收集整理的将一个键值对添加入一个对象_细品Redis高性能数据结构之hash对象的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。