日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis的存储(实现)原理

發(fā)布時間:2024/4/13 数据库 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis的存储(实现)原理 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Redis 的Hash 本身也是一個KV 的結(jié)構(gòu),類似于Java 中的HashMap。

外層的哈希(Redis KV 的實現(xiàn))只用到了hashtable。當(dāng)存儲hash 數(shù)據(jù)類型時,我們把它叫做內(nèi)層的哈希。內(nèi)層的哈希底層可以使用兩種數(shù)據(jù)結(jié)構(gòu)實現(xiàn):

ziplist:OBJ_ENCODING_ZIPLIST(壓縮列表)

hashtable:OBJ_ENCODING_HT(哈希表)

127.0.0.1:6379> hset h2 f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa (integer) 1 127.0.0.1:6379> hset h3 f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa (integer) 1 127.0.0.1:6379> object encoding h2 "ziplist" 127.0.0.1:6379> object encoding h3 "hashtable"

ziplist 壓縮列表

ziplist 壓縮列表是什么?

/* ziplist.c 源碼頭部注釋*/
The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and
integer values, where integers are encoded as actual integers instead of a series of characters. It allows push and pop
operations on either side of the list in O(1) time. However, because every operation requires a reallocation of the memory
used by the ziplist, the actual complexity is related to the amount of memory used by the ziplist.

ziplist 是一個經(jīng)過特殊編碼的雙向鏈表,它不存儲指向上一個鏈表節(jié)點和指向下一個鏈表節(jié)點的指針,而是存儲上一個節(jié)點長度和當(dāng)前節(jié)點長度,通過犧牲部分讀寫性能,來換取高效的內(nèi)存空間利用率,是一種時間換空間的思想。只用在字段個數(shù)少,字段值小的場景里面。

?

ziplist 的內(nèi)部結(jié)構(gòu)?

ziplist.c 源碼第16 行的注釋:

* <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>

typedef struct zlentry {unsigned int prevrawlensize; /* 上一個鏈表節(jié)點占用的長度*/unsigned int prevrawlen; /* 存儲上一個鏈表節(jié)點的長度數(shù)值所需要的字節(jié)數(shù)*/unsigned int lensize; /* 存儲當(dāng)前鏈表節(jié)點長度數(shù)值所需要的字節(jié)數(shù)*/unsigned int len; /* 當(dāng)前鏈表節(jié)點占用的長度*/unsigned int headersize; /* 當(dāng)前鏈表節(jié)點的頭部大小(prevrawlensize + lensize),即非數(shù)據(jù)域的大小*/unsigned char encoding; /* 編碼方式*/unsigned char *p; /* 壓縮鏈表以字符串的形式保存,該指針指向當(dāng)前節(jié)點起始位置*/ } zlentry;

編碼encoding(ziplist.c 源碼第204 行)
#define ZIP_STR_06B (0 << 6) //長度小于等于63 字節(jié)
#define ZIP_STR_14B (1 << 6) //長度小于等于16383 字節(jié)
#define ZIP_STR_32B (2 << 6) //長度小于等于4294967295 字節(jié)

?

問題:什么時候使用ziplist 存儲?

當(dāng)hash 對象同時滿足以下兩個條件的時候,使用ziplist 編碼:

1)所有的鍵值對的健和值的字符串長度都小于等于64byte(一個英文字母一個字節(jié));

2)哈希對象保存的鍵值對數(shù)量小于512 個。

/* src/redis.conf 配置*/

hash-max-ziplist-value 64 // ziplist 中最大能存放的值長度 hash-max-ziplist-entries 512 // ziplist 中最多能存放的entry 節(jié)點數(shù)量 /* 源碼位置:t_hash.c ,當(dāng)達字段個數(shù)超過閾值,使用HT 作為編碼*/ if (hashTypeLength(o) > server.hash_max_ziplist_entries) hashTypeConvert(o, OBJ_ENCODING_HT); /*源碼位置: t_hash.c,當(dāng)字段值長度過大,轉(zhuǎn)為HT */ for (i = start; i <= end; i++) {if (sdsEncodedObject(argv[i]) &&sdslen(argv[i]->ptr) > server.hash_max_ziplist_value){hashTypeConvert(o, OBJ_ENCODING_HT);break;} }

一個哈希對象超過配置的閾值(鍵和值的長度有>64byte,鍵值對個數(shù)>512 個)時,會轉(zhuǎn)換成哈希表(hashtable)。

?

hashtable(dict)

在Redis 中,hashtable 被稱為字典(dictionary),它是一個數(shù)組+鏈表的結(jié)構(gòu)。

源碼位置:dict.h

前面我們知道了,Redis 的KV 結(jié)構(gòu)是通過一個dictEntry 來實現(xiàn)的。

Redis 又對dictEntry 進行了多層的封裝。

typedef struct dictEntry {void *key; /* key 關(guān)鍵字定義*/union {void *val; uint64_t u64; /* value 定義*/int64_t s64; double d;} v;struct dictEntry *next; /* 指向下一個鍵值對節(jié)點*/ } dictEntry;

dictEntry 放到了dictht(hashtable 里面):

/* This is our hash table structure. Every dictionary has two of this as we * implement incremental rehashing, for the old to the new table. */ typedef struct dictht {dictEntry **table; /* 哈希表數(shù)組*/unsigned long size; /* 哈希表大小*/unsigned long sizemask; /* 掩碼大小,用于計算索引值。總是等于size-1 */unsigned long used; /* 已有節(jié)點數(shù)*/ } dictht;

ht 放到了dict 里面:

typedef struct dict {dictType *type; /* 字典類型*/void *privdata; /* 私有數(shù)據(jù)*/dictht ht[2]; /* 一個字典有兩個哈希表*/long rehashidx; /* rehash 索引*/unsigned long iterators; /* 當(dāng)前正在使用的迭代器數(shù)量*/ } dict;

從最底層到最高層dictEntry——dictht——dict——OBJ_ENCODING_HT

總結(jié):哈希的存儲結(jié)構(gòu)

注意:dictht 后面是NULL 說明第二個ht 還沒用到。dictEntry*后面是NULL 說明沒有hash 到這個地址。dictEntry 后面是NULL 說明沒有發(fā)生哈希沖突。

問題:為什么要定義兩個哈希表呢?ht[2]

redis 的hash 默認使用的是ht[0],ht[1]不會初始化和分配空間。

哈希表dictht 是用鏈地址法來解決碰撞問題的。在這種情況下,哈希表的性能取決于它的大小(size 屬性)和它所保存的節(jié)點的數(shù)量(used 屬性)之間的比率:

比率在1:1 時(一個哈希表ht 只存儲一個節(jié)點entry),哈希表的性能最好;

如果節(jié)點數(shù)量比哈希表的大小要大很多的話(這個比例用ratio 表示,5 表示平均一個ht 存儲5 個entry),那么哈希表就會退化成多個鏈表,哈希表本身的性能優(yōu)勢就不再存在。

在這種情況下需要擴容。Redis 里面的這種操作叫做rehash。

rehash 的步驟:

1、為字符ht[1]哈希表分配空間,這個哈希表的空間大小取決于要執(zhí)行的操作,以及ht[0]當(dāng)前包含的鍵值對的數(shù)量。

擴展:ht[1]的大小為第一個大于等于ht[0].used*2。

2、將所有的ht[0]上的節(jié)點rehash 到ht[1]上,重新計算hash 值和索引,然后放入指定的位置。

3、當(dāng)ht[0]全部遷移到了ht[1]之后,釋放ht[0]的空間,將ht[1]設(shè)置為ht[0]表,并創(chuàng)建新的ht[1],為下次rehash 做準(zhǔn)備。

?

問題:什么時候觸發(fā)擴容?

負載因子(源碼位置:dict.c):

static int dict_can_resize = 1; static unsigned int dict_force_resize_ratio = 5;

ratio = used / size,已使用節(jié)點與字典大小的比例

dict_can_resize 為1 并且dict_force_resize_ratio 已使用節(jié)點數(shù)和字典大小之間的比率超過1:5,觸發(fā)擴容

擴容判斷_dictExpandIfNeeded(源碼dict.c)

if (d->ht[0].used >= d->ht[0].size && (dict_can_resize || d->ht[0].used/d->ht[0].size > dict_force_resize_ratio)) {return dictExpand(d, d->ht[0].used*2); } return DICT_OK;

擴容方法dictExpand(源碼dict.c)

?

縮容:server.c

int htNeedsResize(dict *dict) {long long size, used;size = dictSlots(dict);used = dictSize(dict);return (size > DICT_HT_INITIAL_SIZE &&(used*100/size < HASHTABLE_MIN_FILL)); }

?

總結(jié)

以上是生活随笔為你收集整理的Redis的存储(实现)原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。