主頁(yè) > 知識(shí)庫(kù) > Redis底層數(shù)據(jù)結(jié)構(gòu)詳解

Redis底層數(shù)據(jù)結(jié)構(gòu)詳解

熱門(mén)標(biāo)簽:山東外呼銷(xiāo)售系統(tǒng)招商 超呼電話機(jī)器人 北京400電話辦理收費(fèi)標(biāo)準(zhǔn) 十堰營(yíng)銷(xiāo)電銷(xiāo)機(jī)器人哪家便宜 宿遷便宜外呼系統(tǒng)平臺(tái) 日本中國(guó)地圖標(biāo)注 貴州電銷(xiāo)卡外呼系統(tǒng) 魔獸2青云地圖標(biāo)注 鄭州人工智能電銷(xiāo)機(jī)器人系統(tǒng)

Redis作為Key-Value存儲(chǔ)系統(tǒng),數(shù)據(jù)結(jié)構(gòu)如下:

Redis沒(méi)有表的概念,Redis實(shí)例所對(duì)應(yīng)的db以編號(hào)區(qū)分,db本身就是key的命名空間。

比如:user:1000作為key值,表示在user這個(gè)命名空間下id為1000的元素,類(lèi)似于user表的id=1000的行。

RedisDB結(jié)構(gòu)

Redis中存在“數(shù)據(jù)庫(kù)”的概念,該結(jié)構(gòu)由redis.h中的redisDb定義。

當(dāng)redis 服務(wù)器初始化時(shí),會(huì)預(yù)先分配 16 個(gè)數(shù)據(jù)庫(kù)

所有數(shù)據(jù)庫(kù)保存到結(jié)構(gòu) redisServer 的一個(gè)成員 redisServer.db 數(shù)組中

redisClient中存在一個(gè)名叫db的指針指向當(dāng)前使用的數(shù)據(jù)庫(kù)

RedisDB結(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;                 /* 存儲(chǔ)數(shù)據(jù)庫(kù)所有的key-value */
    dict *expires;              /* 存儲(chǔ)key的過(guò)期時(shí)間 */
    dict *blocking_keys;        /* blpop 存儲(chǔ)阻塞key和客戶端對(duì)象*/
    dict *ready_keys;           /* 阻塞后push 響應(yīng)阻塞客戶端 存儲(chǔ)阻塞后push的key和客戶端對(duì)象 */
    dict *watched_keys;         /* 存儲(chǔ)watch監(jiān)控的的key和客戶端對(duì)象 */
    int id;                     /* Database ID */
    long long avg_ttl;          /* 存儲(chǔ)的數(shù)據(jù)庫(kù)對(duì)象的平均ttl(time to live),用于統(tǒng)計(jì) */
    unsigned long expires_cursor; /* 循環(huán)過(guò)期檢查的光標(biāo). */
    list *defrag_later;         /* 需要嘗試去清理磁盤(pán)碎片的鏈表,會(huì)慢慢的清理 */
} redisDb;

id
id是數(shù)據(jù)庫(kù)序號(hào),為0-15(默認(rèn)Redis有16個(gè)數(shù)據(jù)庫(kù))

dict
存儲(chǔ)數(shù)據(jù)庫(kù)所有的key-value,后面要詳細(xì)講解

expires
存儲(chǔ)key的過(guò)期時(shí)間,后面要詳細(xì)講解

RedisObject結(jié)構(gòu)

Value是一個(gè)對(duì)象
包含字符串對(duì)象,列表對(duì)象,哈希對(duì)象,集合對(duì)象和有序集合對(duì)象

結(jié)構(gòu)信息概覽

typedef struct redisObject {
    unsigned type:4; //類(lèi)型 對(duì)象類(lèi)型
    unsigned encoding:4;//編碼
	// LRU_BITS為24bit 記錄最后一次被命令程序訪問(wèn)的時(shí)間
    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ù)
    void *ptr;//指向底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針
} robj;

4位type

type 字段表示對(duì)象的類(lèi)型,占 4 位;

REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。

當(dāng)我們執(zhí)行 type 命令時(shí),便是通過(guò)讀取 RedisObject 的 type 字段獲得對(duì)象的類(lèi)型

127.0.0.1:6379> set a1 111
OK
127.0.0.1:6379> type a1
string

4位encoding

encoding 表示對(duì)象的內(nèi)部編碼,占 4 位

每個(gè)對(duì)象有不同的實(shí)現(xiàn)編碼

Redis 可以根據(jù)不同的使用場(chǎng)景來(lái)為對(duì)象設(shè)置不同的編碼,大大提高了 Redis 的靈活性和效率。

通過(guò) object encoding 命令,可以查看對(duì)象采用的編碼方式

127.0.0.1:6379> OBJECT encoding a1
"int"

24位LRU
lru 記錄的是對(duì)象最后一次被命令程序訪問(wèn)的時(shí)間,( 4.0 版本占 24 位,2.6 版本占 22 位)。

高16位存儲(chǔ)一個(gè)分鐘數(shù)級(jí)別的時(shí)間戳,低8位存儲(chǔ)訪問(wèn)計(jì)數(shù)(lfu : 最近訪問(wèn)次數(shù))

   lru----> 高16位: 最后被訪問(wèn)的時(shí)間

   lfu----->低8位:最近訪問(wèn)次數(shù)

refcount
refcount 記錄的是該對(duì)象被引用的次數(shù),類(lèi)型為整型。

refcount 的作用,主要在于對(duì)象的引用計(jì)數(shù)和內(nèi)存回收。

當(dāng)對(duì)象的refcount>1時(shí),稱(chēng)為共享對(duì)象

Redis 為了節(jié)省內(nèi)存,當(dāng)有一些對(duì)象重復(fù)出現(xiàn)時(shí),新的程序不會(huì)創(chuàng)建新的對(duì)象,而是仍然使用原來(lái)的對(duì)象。

ptr
ptr 指針指向具體的數(shù)據(jù),比如:set hello world,ptr 指向包含字符串 world 的 SDS。

7種type 字符串對(duì)象

C語(yǔ)言: 字符數(shù)組 “\0”

Redis 使用了 SDS(Simple Dynamic String)。用于存儲(chǔ)字符串和整型數(shù)據(jù)。

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

buf[] 的長(zhǎng)度=len+free+1

SDS的優(yōu)勢(shì):

1.SDS 在 C 字符串的基礎(chǔ)上加入了 free 和 len 字段,獲取字符串長(zhǎng)度:SDS 是 O(1),C 字符串是
O(n)。
buf數(shù)組的長(zhǎng)度=free+len+1

2.SDS 由于記錄了長(zhǎng)度,在可能造成緩沖區(qū)溢出時(shí)會(huì)自動(dòng)重新分配內(nèi)存,杜絕了緩沖區(qū)溢出。

3.可以存取二進(jìn)制數(shù)據(jù),以字符串長(zhǎng)度len來(lái)作為結(jié)束標(biāo)識(shí)

  • C:

\0 空字符串 二進(jìn)制數(shù)據(jù)包括空字符串,所以沒(méi)有辦法存取二進(jìn)制數(shù)據(jù)

  • SDS :

非二進(jìn)制 \0
二進(jìn)制: 字符串長(zhǎng)度 可以存二進(jìn)制數(shù)據(jù)

使用場(chǎng)景:
SDS的主要應(yīng)用在:存儲(chǔ)字符串和整型數(shù)據(jù)、存儲(chǔ)key、AOF緩沖區(qū)和用戶輸入緩沖。

跳躍表(重要)

跳躍表是有序集合(sorted-set)的底層實(shí)現(xiàn),效率高,實(shí)現(xiàn)簡(jiǎn)單。

跳躍表的基本思想:

將有序鏈表中的部分節(jié)點(diǎn)分層,每一層都是一個(gè)有序鏈表。

查找

在查找時(shí)優(yōu)先從最高層開(kāi)始向后查找,當(dāng)?shù)竭_(dá)某個(gè)節(jié)點(diǎn)時(shí),如果next節(jié)點(diǎn)值大于要查找的值或next指針指向null,則從當(dāng)前節(jié)點(diǎn)下降一層繼續(xù)向后查找。

舉例:

查找元素9,按道理我們需要從頭結(jié)點(diǎn)開(kāi)始遍歷,一共遍歷8個(gè)結(jié)點(diǎn)才能找到元素9。

第一次分層:

遍歷5次找到元素9(紅色的線為查找路徑)

第二次分層:

遍歷4次找到元素9


第三層分層:

遍歷4次找到元素9

這種數(shù)據(jù)結(jié)構(gòu),就是跳躍表,它具有二分查找的功能。

插入與刪除

上面例子中,9個(gè)結(jié)點(diǎn),一共4層,是理想的跳躍表。

通過(guò)拋硬幣(概率1/2)的方式來(lái)決定新插入結(jié)點(diǎn)跨越的層數(shù),每層都需要判斷:

正面:插入上層

背面:不插入

達(dá)到1/2概率(計(jì)算次數(shù))

刪除

找到指定元素并刪除每層的該元素即可

跳躍表特點(diǎn):

每層都是一個(gè)有序鏈表

查找次數(shù)近似于層數(shù)(1/2)

底層包含所有元素

空間復(fù)雜度 O(n) 擴(kuò)充了一倍

Redis跳躍表的實(shí)現(xiàn)

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
    /* 存儲(chǔ)字符串類(lèi)型數(shù)據(jù) redis3.0版本中使用robj類(lèi)型表示,但是在redis4.0.1中直接使用sds類(lèi)型表示 */
    sds ele;
    /*存儲(chǔ)排序的分值*/
    double score;
    /*后退指針,指向當(dāng)前節(jié)點(diǎn)最底層的前一個(gè)節(jié)點(diǎn)*/
    struct zskiplistNode *backward;
    /*層,柔性數(shù)組,隨機(jī)生成1-64的值*/
    struct zskidictEntryplistLevel {
        struct zskiplistNode *forward; //指向本層下一個(gè)節(jié)點(diǎn)
        unsigned long span; //本層下個(gè)節(jié)點(diǎn)到本節(jié)點(diǎn)的元素個(gè)數(shù)
    } level[];
} zskiplistNode;

typedef struct zskiplist {
    //表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)
    struct zskiplistNode *header, *tail;
    //表中節(jié)點(diǎn)的數(shù)量
    unsigned long length;
    //表中層數(shù)最大的節(jié)點(diǎn)的層數(shù)
    int level;
} zskiplist;

完整的跳躍表結(jié)構(gòu)體:


跳躍表的優(yōu)勢(shì):
1、可以快速查找到需要的節(jié)點(diǎn) O(logn)
2、可以在O(1)的時(shí)間復(fù)雜度下,快速獲得跳躍表的頭節(jié)點(diǎn)、尾結(jié)點(diǎn)、長(zhǎng)度和高度。
應(yīng)用場(chǎng)景:有序集合的實(shí)現(xiàn)

字典(重要)

字典dict又稱(chēng)散列表(hash),是用來(lái)存儲(chǔ)鍵值對(duì)的一種數(shù)據(jù)結(jié)構(gòu)。
Redis整個(gè)數(shù)據(jù)庫(kù)是用字典來(lái)存儲(chǔ)的。(K-V結(jié)構(gòu))
對(duì)Redis進(jìn)行CURD操作其實(shí)就是對(duì)字典中的數(shù)據(jù)進(jìn)行CURD操作。

數(shù)組

數(shù)組:用來(lái)存儲(chǔ)數(shù)據(jù)的容器,采用頭指針+偏移量的方式能夠以O(shè)(1)的時(shí)間復(fù)雜度定位到數(shù)據(jù)所在的內(nèi)存地址。

Redis 海量存儲(chǔ) 快

Hash函數(shù)

Hash(散列),作用是把任意長(zhǎng)度的輸入通過(guò)散列算法轉(zhuǎn)換成固定類(lèi)型、固定長(zhǎng)度的散列值。

hash函數(shù)可以把Redis里的key:包括字符串、整數(shù)、浮點(diǎn)數(shù)統(tǒng)一轉(zhuǎn)換成整數(shù)。

key=100.1 String “100.1” 5位長(zhǎng)度的字符串

Redis-cli :times 33

Redis-Server : MurmurHash

數(shù)組下標(biāo)=hash(key)%數(shù)組容量(hash值%數(shù)組容量得到的余數(shù))

Hash沖突

不同的key經(jīng)過(guò)計(jì)算后出現(xiàn)數(shù)組下標(biāo)一致,稱(chēng)為Hash沖突。

采用單鏈表在相同的下標(biāo)位置處存儲(chǔ)原始key和value

當(dāng)根據(jù)key找Value時(shí),找到數(shù)組下標(biāo),遍歷單鏈表可以找出key相同的value

Redis字典的實(shí)現(xiàn)

Redis字典實(shí)現(xiàn)包括:字典(dict)、Hash表(dictht)、Hash表節(jié)點(diǎn)(dictEntry)。

Hash表

typedef struct dictht {
    dictEntry **table; // 哈希表數(shù)組
    unsigned long size; // 哈希表數(shù)組的大小
    unsigned long sizemask; // 用于映射位置的掩碼,值永遠(yuǎn)等于(size-1)
    unsigned long used; // 哈希表已有節(jié)點(diǎn)的數(shù)量,包含next單鏈表數(shù)據(jù)
} dictht;

1、hash表的數(shù)組初始容量為4,隨著k-v存儲(chǔ)量的增加需要對(duì)hash表數(shù)組進(jìn)行擴(kuò)容,新擴(kuò)容量為當(dāng)前量的一倍,即4,8,16,32

2、索引值=Hash值掩碼值(Hash值與Hash表容量取余)

Hash表節(jié)點(diǎn)

typedef struct dictEntry {
    void *key; // 鍵
    union { // 值v的類(lèi)型可以是以下4種類(lèi)型
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next; // 指向下一個(gè)哈希表節(jié)點(diǎn),形成單向鏈表 解決hash沖突, 單鏈表中會(huì)存儲(chǔ)key和value
} dictEntry;

key字段存儲(chǔ)的是鍵值對(duì)中的鍵

v字段是個(gè)聯(lián)合體,存儲(chǔ)的是鍵值對(duì)中的值。

next指向下一個(gè)哈希表節(jié)點(diǎn),用于解決hash沖突

dict字典

typedef struct dict {
    dictType *type; //該字典對(duì)應(yīng)的特定操作函數(shù)
    void *privdata; //上述類(lèi)型函數(shù)對(duì)應(yīng)的可選參數(shù)
    dictht ht[2];/* 兩張哈希表,存儲(chǔ)鍵值對(duì)數(shù)據(jù),ht[0]為原生哈希表,ht[1]為 rehash 哈希表 */
    long rehashidx; /* rehash標(biāo)識(shí) 當(dāng)?shù)扔?1時(shí)表示沒(méi)有在rehash,否則表示正在進(jìn)行rehash操作,
    				存儲(chǔ)的值表示hash表 ht[0]的rehash進(jìn)行到哪個(gè)索引值(數(shù)組下標(biāo))*/
    unsigned long iterators; /* 當(dāng)前運(yùn)行的迭代器數(shù)量 */
} dict;

type字段,指向dictType結(jié)構(gòu)體,里邊包括了對(duì)該字典操作的函數(shù)指針

typedef struct dictType {
    // 計(jì)算哈希值的函數(shù)
    uint64_t (*hashFunction)(const void *key);

    // 復(fù)制鍵的函數(shù)
    void *(*keyDup)(void *privdata, const void *key);

    // 比較鍵的函數(shù)
    void *(*valDup)(void *privdata, const void *obj);

    // 比較鍵的函數(shù)
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);

    // 銷(xiāo)毀鍵的函數(shù)
    void (*keyDestructor)(void *privdata, void *key);

    // 銷(xiāo)毀值的函數(shù)
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

  Redis字典除了主數(shù)據(jù)庫(kù)的K-V數(shù)據(jù)存儲(chǔ)以外,還可以用于:散列表對(duì)象、哨兵模式中的主從節(jié)點(diǎn)管理等在不同的應(yīng)用中,字典的形態(tài)都可能不同,dictType是為了實(shí)現(xiàn)各種形態(tài)的字典而抽象出來(lái)的操作函數(shù)(多態(tài))。

完整的Redis字典數(shù)據(jù)結(jié)構(gòu):

字典擴(kuò)容

字典達(dá)到存儲(chǔ)上限(閾值 0.75),需要rehash(擴(kuò)容)

說(shuō)明:

初次申請(qǐng)默認(rèn)容量為4個(gè)dictEntry,非初次申請(qǐng)為當(dāng)前hash表容量的一倍。rehashidx=0表示要進(jìn)行rehash操作。新增加的數(shù)據(jù)在新的hash表h[1]修改、刪除、查詢?cè)诶蟞ash表h[0]、新hash表h[1]中(rehash中)將老的hash表h[0]的數(shù)據(jù)重新計(jì)算索引值后全部遷移到新的hash表h[1]中,這個(gè)過(guò)程稱(chēng)為rehash。 漸進(jìn)式rehash

當(dāng)數(shù)據(jù)量巨大時(shí)rehash的過(guò)程是非常緩慢的,所以需要進(jìn)行優(yōu)化。
服務(wù)器忙,則只對(duì)一個(gè)節(jié)點(diǎn)進(jìn)行rehash
服務(wù)器閑,可批量rehash(100節(jié)點(diǎn))
應(yīng)用場(chǎng)景:
1、主數(shù)據(jù)庫(kù)的K-V數(shù)據(jù)存儲(chǔ)
2、散列表對(duì)象(hash)
3、哨兵模式中的主從節(jié)點(diǎn)管理

壓縮列表

壓縮列表(ziplist)是由一系列特殊編碼的連續(xù)內(nèi)存塊組成的順序型數(shù)據(jù)結(jié)構(gòu)

節(jié)省內(nèi)存
是一個(gè)字節(jié)數(shù)組,可以包含多個(gè)節(jié)點(diǎn)(entry)。每個(gè)節(jié)點(diǎn)可以保存一個(gè)字節(jié)數(shù)組或一個(gè)整數(shù)。

壓縮列表的數(shù)據(jù)結(jié)構(gòu)如下:

zlbytes:壓縮列表的字節(jié)長(zhǎng)度
zltail:壓縮列表尾元素相對(duì)于壓縮列表起始地址的偏移量
zllen:壓縮列表的元素個(gè)數(shù)
entry1…entryX : 壓縮列表的各個(gè)節(jié)點(diǎn)
zlend:壓縮列表的結(jié)尾,占一個(gè)字節(jié),恒為0xFF(255)
entryX元素的編碼結(jié)構(gòu):

previous_entry_length:前一個(gè)元素的字節(jié)長(zhǎng)度
encoding:表示當(dāng)前元素的編碼
content:數(shù)據(jù)內(nèi)容

ziplist結(jié)構(gòu)體如下:

struct ziplistT>{
    unsigned int zlbytes; // ziplist的長(zhǎng)度字節(jié)數(shù),包含頭部、所有entry和zipend。
    unsigned int zloffset; // 從ziplist的頭指針到指向最后一個(gè)entry的偏移量,用于快速反向查詢
    unsigned short int zllength; // entry元素個(gè)數(shù)T[] entry; // 元素值
    unsigned char zlend; // ziplist結(jié)束符,值固定為0xFF
}
typedef struct zlentry {
    unsigned int prevrawlensize; //previous_entry_length字段的長(zhǎng)度
    unsigned int prevrawlen; //previous_entry_length字段存儲(chǔ)的內(nèi)容
    unsigned int lensize; //encoding字段的長(zhǎng)度
    unsigned int len; //數(shù)據(jù)內(nèi)容長(zhǎng)度
    unsigned int headersize; //當(dāng)前元素的首部長(zhǎng)度,即previous_entry_length字段長(zhǎng)度與 encoding字段長(zhǎng)度之和。
    unsigned char encoding; //數(shù)據(jù)類(lèi)型
    unsigned char *p; //當(dāng)前元素首地址
} zlentry;

應(yīng)用場(chǎng)景:
sorted-set和hash元素個(gè)數(shù)少且是小整數(shù)或短字符串(直接使用)

list用快速鏈表(quicklist)數(shù)據(jù)結(jié)構(gòu)存儲(chǔ),而快速鏈表是雙向列表與壓縮列表的組合。(間接使用)

整數(shù)集合

整數(shù)集合(intset)是一個(gè)有序的(整數(shù)升序)、存儲(chǔ)整數(shù)的連續(xù)存儲(chǔ)結(jié)構(gòu)。
當(dāng)Redis集合類(lèi)型的元素都是整數(shù)并且都處在64位有符號(hào)整數(shù)范圍內(nèi)(-2^63 ~ 2^63 -1 ),使用該結(jié)構(gòu)體存儲(chǔ)。

127.0.0.1:6379> SADD set:1 12  6  8
(integer) 3
127.0.0.1:6379> OBJECT encoding set:1
"intset"
127.0.0.1:6379> SADD set:2 1 1000000000000000000000000000000000000000000000000000000 99999999999999999999999999999
(integer) 3
127.0.0.1:6379> OBJECT encoding set:2
"hashtable"

intset的結(jié)構(gòu)圖如下:

typedef struct intset{
    //編碼方式
    uint32_t encoding;
    //集合包含的元素?cái)?shù)量
    uint32_t length;
    //保存元素的數(shù)組
    int8_t contents[];
}intset;

應(yīng)用場(chǎng)景:
可以保存類(lèi)型為int16_t、int32_t 或者int64_t 的整數(shù)值,并且保證集合中不會(huì)出現(xiàn)重復(fù)元素。

快速列表(重要)

  快速列表(quicklist)是Redis底層重要的數(shù)據(jù)結(jié)構(gòu)。是列表的底層實(shí)現(xiàn)。(在Redis3.2之前,Redis采用雙向鏈表(adlist)和壓縮列表(ziplist)實(shí)現(xiàn)。)在Redis3.2以后結(jié)合adlist和ziplist的優(yōu)勢(shì)Redis設(shè)計(jì)出了quicklist。

127.0.0.1:6379> LPUSH list:001  2 3 5 6 7
(integer) 5
127.0.0.1:6379> OBJECT encoding list:001
"quicklist"

雙向列表(adlist)


雙向鏈表優(yōu)勢(shì):

  1. 雙向:鏈表具有前置節(jié)點(diǎn)和后置節(jié)點(diǎn)的引用,獲取這兩個(gè)節(jié)點(diǎn)時(shí)間復(fù)雜度都為O(1)。
  2. 普通鏈表(單鏈表):節(jié)點(diǎn)類(lèi)保留下一節(jié)點(diǎn)的引用。鏈表類(lèi)只保留頭節(jié)點(diǎn)的引用,只能從頭節(jié)點(diǎn)插入刪除
  3. 無(wú)環(huán):表頭節(jié)點(diǎn)的 prev 指針和表尾節(jié)點(diǎn)的 next 指針都指向 NULL,對(duì)鏈表的訪問(wèn)都是以 NULL 結(jié)束。
  4. 環(huán)狀:頭的前一個(gè)節(jié)點(diǎn)指向尾節(jié)點(diǎn)
  5. 帶鏈表長(zhǎng)度計(jì)數(shù)器:通過(guò) len 屬性獲取鏈表長(zhǎng)度的時(shí)間復(fù)雜度為 O(1)。
  6. 多態(tài):鏈表節(jié)點(diǎn)使用 void* 指針來(lái)保存節(jié)點(diǎn)值,可以保存各種不同類(lèi)型的值。

快速列表
quicklist是一個(gè)雙向鏈表,鏈表中的每個(gè)節(jié)點(diǎn)時(shí)一個(gè)ziplist結(jié)構(gòu)。quicklist中的每個(gè)節(jié)點(diǎn)ziplist都能夠存儲(chǔ)多個(gè)數(shù)據(jù)元素。


quicklist的結(jié)構(gòu)定義如下:

#if UINTPTR_MAX == 0xffffffff
/* 32-bit */
#   define QL_FILL_BITS 14
#   define QL_COMP_BITS 14
#   define QL_BM_BITS 4
#elif UINTPTR_MAX == 0xffffffffffffffff
/* 64-bit */
#   define QL_FILL_BITS 16
#   define QL_COMP_BITS 16
#   define QL_BM_BITS 4 /* we can encode more, but we rather limit the user
                           since they cause performance degradation. */
#else
#   error unknown arch bits count
#endif

/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
 * 'count' is the number of total entries.
 * 'len' is the number of quicklist nodes.
 * 'compress' is: 0 if compression disabled, otherwise it's the number
 *                of quicklistNodes to leave uncompressed at ends of quicklist.
 * 'fill' is the user-requested (or default) fill factor.
 * 'bookmakrs are an optional feature that is used by realloc this struct,
 *      so that they don't consume memory when not used. */
typedef struct quicklist {
    quicklistNode *head; // 指向quicklist的頭部
    quicklistNode *tail; // 指向quicklist的尾部
    unsigned long count;        /* 列表中所有數(shù)據(jù)項(xiàng)的個(gè)數(shù)總和 */
    unsigned long len;          /* quicklist節(jié)點(diǎn)的個(gè)數(shù),即ziplist的個(gè)數(shù) */
    int fill : QL_FILL_BITS;              /* ziplist大小限定,由list-max-ziplist-size給定  (Redis設(shè)定)*/
    unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
    unsigned int bookmark_count: QL_BM_BITS; /*節(jié)點(diǎn)壓縮深度設(shè)置,由list-compress-depth給定*/
    quicklistBookmark bookmarks[]; /*可選新特性 使用realloc重新分配空間的時(shí)候會(huì)用到*/
} quicklist;

quicklistNode的結(jié)構(gòu)定義如下:

/* quicklistNode is a 32 byte struct describing a ziplist for a quicklist.
 * We use bit fields keep the quicklistNode at 32 bytes.
 * count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually  32k).
 * encoding: 2 bits, RAW=1, LZF=2.
 * container: 2 bits, NONE=1, ZIPLIST=2.
 * recompress: 1 bit, bool, true if node is temporary decompressed for usage.
 * attempted_compress: 1 bit, boolean, used for verifying during testing.
 * extra: 10 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode {
    struct quicklistNode *prev; // 指向上一個(gè)ziplist節(jié)點(diǎn)
    struct quicklistNode *next; // 指向下一個(gè)ziplist節(jié)點(diǎn)
    unsigned char *zl; // 數(shù)據(jù)指針,如果沒(méi)有被壓縮,就指向ziplist結(jié)構(gòu),反之指向 quicklistLZF結(jié)構(gòu)
    unsigned int sz; // 表示指向ziplist結(jié)構(gòu)的總長(zhǎng)度(內(nèi)存占用長(zhǎng)度)
    unsigned int count : 16; // 表示ziplist中的數(shù)據(jù)項(xiàng)個(gè)數(shù)
    unsigned int encoding : 2; // 編碼方式,1--ziplist,2--quicklistLZF
    unsigned int container : 2; // 預(yù)留字段,存放數(shù)據(jù)的方式,1--NONE,2--ziplist
    unsigned int recompress : 1; // 解壓標(biāo)記,當(dāng)查看一個(gè)被壓縮的數(shù)據(jù)時(shí),需要暫時(shí)解壓,標(biāo)記此參數(shù)為 1,之后再重新進(jìn)行壓縮
    unsigned int attempted_compress : 1; // 測(cè)試相關(guān)
    unsigned int extra : 10; // 擴(kuò)展字段,暫時(shí)沒(méi)用
} quicklistNode;

數(shù)據(jù)壓縮
  quicklist每個(gè)節(jié)點(diǎn)的實(shí)際數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)為ziplist,這種結(jié)構(gòu)的優(yōu)勢(shì)在于節(jié)省存儲(chǔ)空間。為了進(jìn)一步降低ziplist的存儲(chǔ)空間,還可以對(duì)ziplist進(jìn)行壓縮。Redis采用的壓縮算法是LZF。其基本思想是:數(shù)據(jù)與前面重復(fù)的記錄重復(fù)位置及長(zhǎng)度,不重復(fù)的記錄原始數(shù)據(jù)。

  壓縮過(guò)后的數(shù)據(jù)可以分成多個(gè)片段,每個(gè)片段有兩個(gè)部分:解釋字段和數(shù)據(jù)字段。quicklistLZF的結(jié)構(gòu)體如下:

/* quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'.
 * 'sz' is byte length of 'compressed' field.
 * 'compressed' is LZF data with total (compressed) length 'sz'
 * NOTE: uncompressed length is stored in quicklistNode->sz.
 * When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */
typedef struct quicklistLZF {
    unsigned int sz; /* LZF壓縮后占用的字節(jié)數(shù)*/
    char compressed[]; //柔性數(shù)組,指向數(shù)據(jù)部分
} quicklistLZF;

應(yīng)用場(chǎng)景
列表(List)的底層實(shí)現(xiàn)、發(fā)布與訂閱、慢查詢、監(jiān)視器等功能。

流對(duì)象

stream主要由:消息、生產(chǎn)者、消費(fèi)者和消費(fèi)組構(gòu)成。


Redis Stream的底層主要使用了listpack(緊湊列表)和Rax樹(shù)(基數(shù)樹(shù))。

listpack
listpack表示一個(gè)字符串列表的序列化,listpack可用于存儲(chǔ)字符串或整數(shù)。用于存儲(chǔ)stream的消息內(nèi)容。
結(jié)構(gòu)如下圖:


Rax樹(shù)
Rax 是一個(gè)有序字典樹(shù) (基數(shù)樹(shù) Radix Tree),按照 key 的字典序排列,支持快速地定位、插入和刪除操作。


  

Rax 被用在 Redis Stream 結(jié)構(gòu)里面用于存儲(chǔ)消息隊(duì)列,在 Stream 里面消息 ID 的前綴是時(shí)間戳 + 序號(hào),這樣的消息可以理解為時(shí)間序列消息。使用 Rax 結(jié)構(gòu) 進(jìn)行存儲(chǔ)就可以快速地根據(jù)消息 ID 定位到具體的消息,然后繼續(xù)遍歷指定消息 之后的所有消息。


應(yīng)用場(chǎng)景:
stream的底層實(shí)現(xiàn)

10種encoding

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

encoding 表示對(duì)象的內(nèi)部編碼,占 4 位。

Redis通過(guò) encoding 屬性為對(duì)象設(shè)置不同的編碼

對(duì)于少的和小的數(shù)據(jù),Redis采用小的和壓縮的存儲(chǔ)方式,體現(xiàn)Redis的靈活性

大大提高了 Redis 的存儲(chǔ)量和執(zhí)行效率

比如Set對(duì)象:

intset : 元素是64位以內(nèi)的整數(shù)

hashtable:元素是64位以外的整數(shù)

127.0.0.1:6379> sadd set:001 1 3 5 6 2
(integer) 5
127.0.0.1:6379> object encoding set:001
"intset"
127.0.0.1:6379> sadd set:004 1 100000000000000000000000000 9999999999
(integer) 3
127.0.0.1:6379> object encoding set:004
"hashtable"

String

int、raw、embstr

int

REDIS_ENCODING_INT(int類(lèi)型的整數(shù))

127.0.0.1:6379> set n1 123
OK
127.0.0.1:6379> object encoding n1
"int"

embstr

REDIS_ENCODING_EMBSTR(編碼的簡(jiǎn)單動(dòng)態(tài)字符串)
小字符串 長(zhǎng)度小于44個(gè)字節(jié)

127.0.0.1:6379> set name:001 zhangfei
OK
127.0.0.1:6379> object encoding name:001
"embstr"

raw

REDIS_ENCODING_RAW (簡(jiǎn)單動(dòng)態(tài)字符串)
大字符串 長(zhǎng)度大于44個(gè)字節(jié)

127.0.0.1:6379> set address:001 asdasdasdasdasdasdsadasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdas
OK
127.0.0.1:6379> object encoding address:001
"raw"

list

列表的編碼是quicklist。
REDIS_ENCODING_QUICKLIST(快速列表)

127.0.0.1:6379> lpush list:001 1 2 5 4 3
(integer) 5
127.0.0.1:6379> object encoding list:001
"quicklist"

hash

散列的編碼是字典和壓縮列表

dict

REDIS_ENCODING_HT(字典)
當(dāng)散列表元素的個(gè)數(shù)比較多或元素不是小整數(shù)或短字符串時(shí)。

127.0.0.1:6379> hmset user:003 username111111111111111111111111111111111111111111111111111111111111111111111111 11111111111111111111111111111111 zhangfei password 111 num 2300000000000000000000000000000000000000000000000000
OK
127.0.0.1:6379> object encoding user:003
"hashtable"

ziplist

REDIS_ENCODING_ZIPLIST(壓縮列表)
當(dāng)散列表元素的個(gè)數(shù)比較少,且元素都是小整數(shù)或短字符串時(shí)。

127.0.0.1:6379> HSET user:001 username zhanyun password 123456
(integer) 2
127.0.0.1:6379> OBJECT encoding user:001
"ziplist"

set

集合的編碼是整形集合和字典

intset

REDIS_ENCODING_INTSET(整數(shù)集合)
當(dāng)Redis集合類(lèi)型的元素都是整數(shù)并且都處在64位有符號(hào)整數(shù)范圍內(nèi)(-9223372036854775808 ~ 9223372036854775807)

127.0.0.1:6379> sadd set:001 1 3 5 6 2
(integer) 5
127.0.0.1:6379> object encoding set:001
"intset"

dict

REDIS_ENCODING_HT(字典)
當(dāng)Redis集合類(lèi)型的元素是非整數(shù)或都處在64位有符號(hào)整數(shù)范圍外(>9223372036854775807)

127.0.0.1:6379> sadd set:004 1 100000000000000000000000000 9999999999
(integer) 3
127.0.0.1:6379> object encoding set:004
"hashtable"

zset

有序集合的編碼是壓縮列表和跳躍表+字典

ziplist

REDIS_ENCODING_ZIPLIST(壓縮列表)

當(dāng)元素的個(gè)數(shù)比較少,且元素都是小整數(shù)或短字符串時(shí)。

127.0.0.1:6379> zadd hit:1 100 item1 20 item2 45 item3
(integer) 3
127.0.0.1:6379> object encoding hit:1
"ziplist"

skiplist + dict

REDIS_ENCODING_SKIPLIST(跳躍表+字典)

當(dāng)元素的個(gè)數(shù)比較多或元素不是小整數(shù)或短字符串時(shí)。

127.0.0.1:6379> zadd hit:2 100 item1111111111111111111111111111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111 20 item2 45 item3
(integer) 3
127.0.0.1:6379> object encoding hit:2
"skiplist"

到此這篇關(guān)于Redis底層數(shù)據(jù)結(jié)構(gòu)的文章就介紹到這了,更多相關(guān)Redis數(shù)據(jù)結(jié)構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:
  • 詳解Redis數(shù)據(jù)結(jié)構(gòu)之跳躍表
  • redis中的數(shù)據(jù)結(jié)構(gòu)和編碼詳解
  • redis內(nèi)部數(shù)據(jù)結(jié)構(gòu)之SDS簡(jiǎn)單動(dòng)態(tài)字符串詳解
  • redis數(shù)據(jù)結(jié)構(gòu)之intset的實(shí)例詳解
  • 詳解redis數(shù)據(jù)結(jié)構(gòu)之sds
  • 詳解redis數(shù)據(jù)結(jié)構(gòu)之壓縮列表
  • Redis中5種數(shù)據(jù)結(jié)構(gòu)的使用場(chǎng)景介紹
  • Redis底層數(shù)據(jù)結(jié)構(gòu)之dict、ziplist、quicklist詳解

標(biāo)簽:楊凌 北京 臺(tái)州 朝陽(yáng) 吉安 江蘇 大慶 果洛

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Redis底層數(shù)據(jù)結(jié)構(gòu)詳解》,本文關(guān)鍵詞  Redis,底層,數(shù)據(jù)結(jié)構(gòu),詳解,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《Redis底層數(shù)據(jù)結(jié)構(gòu)詳解》相關(guān)的同類(lèi)信息!
  • 本頁(yè)收集關(guān)于Redis底層數(shù)據(jù)結(jié)構(gòu)詳解的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章