redis的對象類型和編碼

redis的對象類型和編碼

redis使用的武大數據類型都可以用鍵值對來標識,每次在redis創建鍵值對時至少創建兩個對象:一個鍵對象,一個值對象;redis裏的每個對象都可以用redisObject的結構來標識。

typedef struct redisObject{
     unsigned type:4;		//類型    
     unsigned encoding:4;	//編碼 
     void *ptr;				//指向底層數據結構的指針
     int refcount;			//引用計數
     unsigned lru:22; 		//記錄最後一次被程序訪問的時間
}robj;

對象的type字段記錄了對象的類型,在redis中,有五種數據類型,如下

/*
 * 對象類型
 */
#define REDIS_STRING 0  // 字符串   string
#define REDIS_LIST 1    // 列表     list
#define REDIS_SET 2     // 集合     hash
#define REDIS_ZSET 3    // 有序集   set
#define REDIS_HASH 4    // 哈希表   zset

可以通過type <key>來判斷對象的類型。 redis中鍵總是一個字符串對象,而值可以是五種中的任意一種,所有我們通常說的鍵爲字符串鍵,表示的事這個鍵對應的值得爲字符串對象

對象的encoding字段記錄了對象所保存的值的編碼, 它的值可能是以下常量的其中一個

/*
 * 對象編碼
 */
#define REDIS_ENCODING_RAW 0            // 編碼爲字符串
#define REDIS_ENCODING_INT 1            // 編碼爲整數
#define REDIS_ENCODING_HT 2             // 編碼爲哈希表
#define REDIS_ENCODING_ZIPMAP 3         // 編碼爲 zipmap
#define REDIS_ENCODING_LINKEDLIST 4     // 編碼爲雙端鏈表
#define REDIS_ENCODING_ZIPLIST 5        // 編碼爲壓縮列表
#define REDIS_ENCODING_INTSET 6         // 編碼爲整數集合
#define REDIS_ENCODING_SKIPLIST 7       // 編碼爲跳躍表

而每個類型的編碼有對應至少兩種不同的編碼 image.png 可以通過 object encoding <key> 命令查看值對象的編碼

**對象的*ptr **作爲指針指向對象底層的數據結構

對象的refcount表示引用計數 對象的lru表示對象的最後一次被命令程序訪問的時間

String的數據結構

String作爲redis最基本的數據類型,如上所寫,key也是基於redisObject的,而且所有key的數據類型都是字符串類型,其他幾種數據類型的構成元素也是字符串類型,字符串的長度不能超過512m string類型由三種編碼方式

  1. int編碼,保存的事可以用long類型表示的整數值
  2. raw編碼,保存長度大於44個字節的字符串
  3. embstr編碼,保存長度小於44個字節的字符串

embstr編碼是一種轉麼用來保存短字符串的一種優化編碼,embstr和raw的區別如下 image.png image.png embstr和raw都是使用redisobect和sds保存數據,embstr的使用是分配一次內存空間,兒raw需要分配兩次內存空間。embstr的好處在於創建時少分配一次空間,刪除時少釋放一次空間,以及對象的所有數據都連在一起,查找方便;壞處就是字符串長度增加需要重新分配內存是,整個redisobject和sds都需要重新分配空間,因此redis中的embstr實現爲只讀 ​

Redis中對於浮點數類型也是作爲字符串保存的,在需要的時候再將其轉換成浮點數類型。 ​

編碼的轉換:當 int 編碼保存的值不再是整數,或大小超過了long的範圍時,自動轉化爲raw。對於 embstr 編碼,由於 Redis 沒有對其編寫任何的修改程序(embstr 是隻讀的),在對embstr對象進行修改時,都會先轉化爲raw再進行修改,因此,只要是修改embstr對象,修改後的對象一定是raw的,無論是否達到了44個字節。 ​

SDS詳解

SDS的特點:可擴展內存、二進制安全、快速遍歷和兼容傳統c語言的字符串。

struct sdshdr {

    // buf 中已佔用空間的長度
    int len;

    // buf 中剩餘可用空間的長度
    int free;

    // 字節數組
    char buf[];
};

SDS內部爲當前字符串實際分配的空間 。其中capacity是最大容量,len是實際長度,一般要高於實際字符串長度 len。 當字符串長度小於 1M 時,擴容都是加倍現有的空間,如果超過 1M,擴容時一次只會多擴 1M 的空間。(字符串最大長度爲 512M) image.png

SDS擴容策略

擴容策略是字符串在長度小於 SDS_MAX_PREALLOC =1M之前,擴容空間採用加倍策略,也就是保留 100% 的冗餘空間。當長度超過 SDS_MAX_PREALLOC 之後,爲了避免加倍後的冗餘空間過大而導致浪費,每次擴容只會多分配 SDS_MAX_PREALLOC大小的冗餘空間。

惰性空間釋放策略

惰性空間釋放用於優化SDS的字符串縮短操作。

  • 當要縮短SDS保存的字符串時,程序並不立即使用內存充分配來回收縮短後多出來的字節,而是使用表頭的free成員將這些字節記錄起來,並等待將來使用。

List的數據結構

在redis中list是一種雙向鏈表,每個元素都是一個節點,類似java中的LinkedList,所以也有雙向鏈表的特點,插入刪除非常快,時間複雜度爲O(1),但刪除和定位會很慢,時間複雜度爲O(n)。

壓縮列表結構——ziplist

ziplist在Redis中的使用場景很多,除了在list鏈表中使用,它還是哈希鍵的底層實現之一,所有hash和zset的存儲結構(hash和zset都存在hash結構),也都會依賴於ziplist結構。它是經過特殊編碼的雙向鏈表,主要目的是爲了節約內存空間的使用。 ziplist是一系列特殊編碼的連續內存塊組成的順序序列數據結構,可以包含任意多個節點(entry),每一個節點可以保存一個字節數組或者一個整數值。數據結構如下:

struct ziplist<T> { 
    int32 zlbytes; // 佔4個字節,整個壓縮列表佔用的字節數 
    int32 zltail_offset; // 最後一個元素距離壓縮列表起始位置的偏移量,用於快速定位到最後一個節點
    int16 zllength; // 元素個數 
    T[] entries; // 元素內容列表,挨個挨個緊湊存儲 
    int8 zlend; // 標誌壓縮列表的結束,值恆爲 0xFF
}

image.png ziplist爲了支持雙向遍歷,所以纔會有 ztail_offset 這個字段,通過這個字段,可以快速定位到最後一個元素,然後倒着進行鏈表的遍歷。 entry的數據結構如下

struct entry { 
    int<var> prevlen; // 前一個 entry 的字節長度 
    int<var> encoding; // 元素類型編碼
    optional byte[] content; // 元素內容 
}

在entry的結構體定義中,prevlen 字段表示前一個 entry 的字節長度,當壓縮列表倒着遍歷時,需要通過這個字段來快速定位到下一個元素的位置。 同時,prevlen 也是一個變長的整數,當字符串長度小於 254(0xFE) 時,使用1個字節表示;如果達到或超出 254(0xFE) 那就使用 5 個字節來表示。第一個字節是 0xFE(254),剩餘四個字節表示字符串長度。 ​

encoding字段存儲了元素內容的信息,ziplist 通過這個字段來決定後面的 content 內容的形式,總共有11種不同的情況。

quicklist

quicklist 是 ziplist 和 linkedlist 的混合體,它將 linkedlist 按段切分,每一段使用 ziplist 來緊湊存儲,多個 ziplist 之間使用雙向指針串接起來。這樣既滿足了快速的插入刪除性能,又不會出現太大的空間冗餘 image.png

參考

https://blog.csdn.net/magic_world_wow/article/details/103954650 https://blog.csdn.net/qq193423571/article/details/81637075

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章