redis的數(shù)據(jù)模型和對(duì)象模型

1. redis的幾種基本數(shù)據(jù)類型

一般來(lái)說(shuō)祥绞,最常用的集中數(shù)據(jù)類型有五種,字符串鸭限,隊(duì)列蜕径,集合,有序集合败京,哈希兜喻。在較新的redis版本中還會(huì)有bitmap,hyperloglog,地理位置信息等赡麦。這一系列的數(shù)據(jù)結(jié)構(gòu)支撐了互聯(lián)網(wǎng)的很多業(yè)務(wù)朴皆,redis對(duì)外展示是一個(gè)簡(jiǎn)單的命令行,輸入指令返回信息泛粹,但是每一種數(shù)據(jù)類型在內(nèi)部實(shí)現(xiàn)的時(shí)候往往都會(huì)有幾種自定義數(shù)據(jù)結(jié)構(gòu)相結(jié)合去實(shí)現(xiàn)遂铡,(實(shí)際上,這也是很多其他c++開源工具的做法戚扳,造個(gè)輪子必須得從字符串開始就用自己實(shí)現(xiàn)的)忧便,簡(jiǎn)單介紹一下實(shí)現(xiàn)。

  • 字符串:set數(shù)字就是int帽借,普通字符串就是sds(simple dynamic string)珠增,一個(gè)沒(méi)有\(zhòng)0作為結(jié)束符的,不用內(nèi)存對(duì)齊的字符串?dāng)?shù)據(jù)結(jié)構(gòu)砍艾。
  • 隊(duì)列:ziplist + linkedlist蒂教。·ziplist(壓縮列表):當(dāng)列表的元素個(gè)數(shù)小于list-max-ziplist-entries配置(默認(rèn)512個(gè))脆荷,同時(shí)列表中每個(gè)元素的值都小于list-max-ziplist-value配置時(shí)(默認(rèn)64字節(jié))凝垛,Redis會(huì)選用ziplist來(lái)作為列表的內(nèi)部實(shí)現(xiàn)來(lái)減少內(nèi)存的使用。Redis3.2版本提供了quicklist內(nèi)部編碼蜓谋,簡(jiǎn)單地說(shuō)它是以一個(gè)ziplist為節(jié)點(diǎn)的linkedlist梦皮,它結(jié)合了ziplist和linkedlist兩者的優(yōu)勢(shì),為列表類型提供了一種更為優(yōu)秀的內(nèi)部編碼實(shí)現(xiàn)桃焕。

·linkedlist(鏈表):當(dāng)列表類型無(wú)法滿足ziplist的條件時(shí)剑肯,Redis會(huì)使用linkedlist作為列表的內(nèi)部實(shí)現(xiàn)。

redis> RPUSH numbers 1 "three" 5
image.png

image.png

linkedlist編碼的列表對(duì)象在底層的雙端鏈表結(jié)構(gòu)中包含了多個(gè)字符串對(duì)象观堂,這種嵌套字符串對(duì)象的行為在稍后介紹的哈希對(duì)象让网、集合對(duì)象和有序集合對(duì)象中都會(huì)出現(xiàn)呀忧,字符串對(duì)象是Redis五種類型的對(duì)象中唯一一種會(huì)被其他四種類型對(duì)象嵌套的對(duì)象。

  • 集合:inset + hashtable(依托于dict溃睹,底層是hashtable)
  • 有序集合:ziplist + skiplist
  • hash:是一種field - value類型的數(shù)據(jù)結(jié)構(gòu)而账,而不是key-value,ziplist和hashtable因篇。ziplist(壓縮列表):當(dāng)哈希類型元素個(gè)數(shù)小于hash-max-ziplist-entries配置(默認(rèn)512個(gè))泞辐、同時(shí)所有值都小于hash-max-ziplist-value配置(默認(rèn)64字節(jié))時(shí),Redis會(huì)使用ziplist作為哈希的內(nèi)部實(shí)現(xiàn)竞滓,ziplist使用更加緊湊的結(jié)構(gòu)實(shí)現(xiàn)多個(gè)元素的連續(xù)存儲(chǔ)铛碑,所以在節(jié)省內(nèi)存方面比hashtable更加優(yōu)秀。hashtable:當(dāng)哈希類型無(wú)法滿足ziplist的條件時(shí)虽界,Redis會(huì)使用hashtable(依托于dict,底層為hashtable)作為哈希的內(nèi)部實(shí)現(xiàn)涛菠,因?yàn)榇藭r(shí)ziplist的讀寫效率會(huì)下降莉御,而hashtable的讀寫時(shí)間復(fù)雜度為O(1)。


    image.png

    此時(shí)俗冻,鍵和值都是字符串對(duì)象礁叔。

image.png
redisObject對(duì)象

redis的所有對(duì)象都用如下數(shù)據(jù)結(jié)構(gòu)進(jìn)行表示:

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount; // 引用計(jì)數(shù),達(dá)到內(nèi)存回收
    void *ptr;
} robj;

對(duì)象的type屬性記錄了對(duì)象的類型迄薄,
image.png

對(duì)于Redis數(shù)據(jù)庫(kù)保存的鍵值對(duì)來(lái)說(shuō)琅关,鍵總是一個(gè)字符串對(duì)象,而值則可以是字符串對(duì)象讥蔽、列表對(duì)象涣易、哈希對(duì)象、集合對(duì)象或者有序集合對(duì)象的其中一種

encoding屬性記錄了對(duì)象所使用的編碼冶伞,也即是說(shuō)這個(gè)對(duì)象使用了什么數(shù)據(jù)結(jié)構(gòu)作為對(duì)象的底層實(shí)現(xiàn)新症,
image.png

2. redis幾種數(shù)據(jù)結(jié)構(gòu)

redis的數(shù)據(jù)結(jié)構(gòu)突出一個(gè)省內(nèi)存,一般都要加attributes關(guān)鍵字表示不需要內(nèi)存對(duì)齊响禽,壓縮表這種結(jié)構(gòu)更是這一精神的人為體現(xiàn)徒爹。

ziplist

image.png

image.png

字典

所謂的字典,歸根到底還是一個(gè)鍵值對(duì)的形式芋类,

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;


/* 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;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;

key屬性保存著鍵值對(duì)中的鍵隆嗅,而v屬性則保存著鍵值對(duì)中的值,其中鍵值對(duì)的值可以是一個(gè)指針侯繁,或者是一個(gè)uint64_t整數(shù)胖喳,又或者是一個(gè)int64_t整數(shù)。
next屬性是指向另一個(gè)哈希表節(jié)點(diǎn)的指針巫击,這個(gè)指針可以將多個(gè)哈希值相同的鍵值對(duì)連接在一次禀晓,以此來(lái)解決鍵沖突(collision)的問(wèn)題精续。

隨著操作的不斷執(zhí)行,哈希表保存的鍵值對(duì)會(huì)逐漸地增多或者減少粹懒,為了讓哈希表的負(fù)載因子(load factor)維持在一個(gè)合理的范圍之內(nèi)重付,當(dāng)哈希表保存的鍵值對(duì)數(shù)量太多或者太少時(shí),程序需要對(duì)哈希表的大小進(jìn)行相應(yīng)的擴(kuò)展或者收縮凫乖。
Redis對(duì)字典的哈希表執(zhí)行rehash的步驟如下:
1)為字典的ht[1]哈希表分配空間确垫,這個(gè)哈希表的空間大小取決于要執(zhí)行的操作,以及ht[0]當(dāng)前包含的鍵值對(duì)數(shù)量(也即是ht[0].used屬性的值):
·如果執(zhí)行的是擴(kuò)展操作帽芽,那么ht[1]的大小為第一個(gè)大于等于ht[0].used*2的2 n(2的n次方冪)删掀;
·如果執(zhí)行的是收縮操作,那么ht[1]的大小為第一個(gè)大于等于ht[0].used的2 n导街。
2)將保存在ht[0]中的所有鍵值對(duì)rehash到ht[1]上面:rehash指的是重新計(jì)算鍵的哈希值和索引值披泪,然后將鍵值對(duì)放置到ht[1]哈希表的指定位置上。
3)當(dāng)ht[0]包含的所有鍵值對(duì)都遷移到了ht[1]之后(ht[0]變?yōu)榭毡恚┌峁澹尫舎t[0]款票,將ht[1]設(shè)置為ht[0],并在ht[1]新創(chuàng)建一個(gè)空白哈希表泽论,為下一次rehash做準(zhǔn)備艾少。

但是,這個(gè)rehash動(dòng)作并不是一次性翼悴、集中式地完成的缚够,而是分多次、漸進(jìn)式地完成的鹦赎。
這樣做的原因在于谍椅,如果ht[0]里只保存著四個(gè)鍵值對(duì),那么服務(wù)器可以在瞬間就將這些鍵值對(duì)全部rehash到ht[1]钙姊;但是毯辅,如果哈希表里保存的鍵值對(duì)數(shù)量不是四個(gè),而是四百萬(wàn)煞额、四千萬(wàn)甚至四億個(gè)鍵值對(duì)思恐,那么要一次性將這些鍵值對(duì)全部rehash到ht[1]的話,龐大的計(jì)算量可能會(huì)導(dǎo)致服務(wù)器在一段時(shí)間內(nèi)停止服務(wù)膊毁。
因此胀莹,為了避免rehash對(duì)服務(wù)器性能造成影響,服務(wù)器不是一次性將ht[0]里面的所有鍵值對(duì)全部rehash到ht[1]婚温,而是分多次描焰、漸進(jìn)式地將ht[0]里面的鍵值對(duì)慢慢地rehash到ht[1]。
以下是哈希表漸進(jìn)式rehash的詳細(xì)步驟:
1)為ht[1]分配空間,讓字典同時(shí)持有ht[0]和ht[1]兩個(gè)哈希表荆秦。
2)在字典中維持一個(gè)索引計(jì)數(shù)器變量rehashidx篱竭,并將它的值設(shè)置為0,表示rehash工作正式開始步绸。
3)在rehash進(jìn)行期間掺逼,每次對(duì)字典執(zhí)行添加、刪除瓤介、查找或者更新操作時(shí)吕喘,程序除了執(zhí)行指定的操作以外,還會(huì)順帶將ht[0]哈希表在rehashidx索引上的所有鍵值對(duì)rehash到ht[1]刑桑,當(dāng)rehash工作完成之后氯质,程序?qū)ehashidx屬性的值增一。
4)隨著字典操作的不斷執(zhí)行祠斧,最終在某個(gè)時(shí)間點(diǎn)上闻察,ht[0]的所有鍵值對(duì)都會(huì)被rehash至ht[1],這時(shí)程序?qū)ehashidx屬性的值設(shè)為-1琢锋,表示rehash操作已完成蜓陌。
漸進(jìn)式rehash的好處在于它采取分而治之的方式,將rehash鍵值對(duì)所需的計(jì)算工作均攤到對(duì)字典的每個(gè)添加吩蔑、刪除、查找和更新操作上填抬,從而避免了集中式rehash而帶來(lái)的龐大計(jì)算量烛芬。

rehash總結(jié):分配新空間,慢慢復(fù)制飒责,記錄復(fù)制位置赘娄,在漸進(jìn)式rehash執(zhí)行期間,新添加到字典的鍵值對(duì)一律會(huì)被保存到ht[1]里面宏蛉,而ht[0]則不再進(jìn)行任何添加操作遣臼,這一措施保證了ht[0]包含的鍵值對(duì)數(shù)量會(huì)只減不增,并隨著rehash操作的執(zhí)行而最終變成空表拾并。

有序列表

兩種實(shí)現(xiàn)方式:ziplist揍堰,dict + skiplist。
redisObject模型:


image.png

壓縮表模型:


image.png

跳轉(zhuǎn)表模型:


image.png

3 單機(jī)數(shù)據(jù)庫(kù)

數(shù)據(jù)結(jié)構(gòu)如下:

/* Redis database representation. There are multiple databases identified
 * by integers from 0 (the default database) up to the max configured
 * database. The database number is the 'id' field in the structure. */
typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */ -- 就是用戶空間可見(jiàn)的
    dict *expires;              /* Timeout of keys with a timeout set */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

可以看到嗅义,所有的鍵值對(duì)都是通過(guò)dict進(jìn)行存儲(chǔ)的屏歹,假如我們輸入如下命令:

redis> SET message "hello world"
OK
redis> RPUSH alphabet "a" "b" "c"
(integer)3
redis> HSET book name "Redis in Action"
(integer) 1
redis> HSET book author "Josiah L. Carlson"
(integer) 1
redis> HSET book publisher "Manning"
(integer) 1

則redisDb如下所示:


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市之碗,隨后出現(xiàn)的幾起案子蝙眶,更是在濱河造成了極大的恐慌,老刑警劉巖褪那,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幽纷,死亡現(xiàn)場(chǎng)離奇詭異式塌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)友浸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門峰尝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人尾菇,你說(shuō)我怎么就攤上這事境析。” “怎么了派诬?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵劳淆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我默赂,道長(zhǎng)沛鸵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任缆八,我火速辦了婚禮曲掰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奈辰。我一直安慰自己栏妖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布奖恰。 她就那樣靜靜地躺著吊趾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瑟啃。 梳的紋絲不亂的頭發(fā)上论泛,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音蛹屿,去河邊找鬼屁奏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛错负,可吹牛的內(nèi)容都是我干的坟瓢。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼犹撒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼载绿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起油航,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤崭庸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怕享,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡执赡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了函筋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沙合。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖跌帐,靈堂內(nèi)的尸體忽然破棺而出首懈,到底是詐尸還是另有隱情,我是刑警寧澤谨敛,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布究履,位于F島的核電站,受9級(jí)特大地震影響脸狸,放射性物質(zhì)發(fā)生泄漏最仑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一炊甲、第九天 我趴在偏房一處隱蔽的房頂上張望泥彤。 院中可真熱鬧,春花似錦卿啡、人聲如沸吟吝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)爸黄。三九已至,卻和暖如春揭鳞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梆奈。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工野崇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亩钟。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓乓梨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親清酥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扶镀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361