Redis | 第一部分:資料結構與物件 下篇《Redis設計與實現》

多氯環己烷發表於2021-11-23


前言

參考資料:《Redis設計與實現 第二版》;

本篇筆記按照書裡的脈絡,將知識點分為四個部分。其中第一部分資料結構與物件分為上中下篇,上篇包括:SDS連結串列字典;中篇包括跳躍表整數集合壓縮列表;下篇為物件

上篇的連結:https://www.cnblogs.com/dlhjw/p/15569578.html

中篇的連結:https://www.cnblogs.com/dlhjw/p/15582039.html

Redis常用命令及示例總結:https://blog.csdn.net/dlhjw1412/article/details/119713214


1. Redis物件概述

  • Redis沒有直接使用前面介紹的資料結構來實現鍵值對資料庫,而是基於這些資料結構的物件系統;

  • Redis有五種基本物件,分別是字串列表雜湊集合有序集合。使用以下8種編碼方式中的幾種作為底層實現底層實現:long型別整數embstr編碼的SDSSDS字典雙端連結串列壓縮列表整數集合跳躍表

  • Redis在建立鍵值對時,至少會生成兩個物件,鍵物件和值物件;

1.1 物件的定義

  • Redis中的每個物件都由一個redisObject結構來表示:

    typedef struct redisObject{
        //型別
        unsigned type:4;
        //編碼
        unsigned encoding:4;
        //指向底層實現資料結構的的指標
        void *ptr;
        //……
    }
    
    • 型別type的可選型別:

      型別常量 物件的名稱 TYPE命令的輸出
      REDIS_STRING 字串物件 string
      REDIS_LIST 列表物件 list
      REDIS_HASH 雜湊物件 hash
      REDIS_SET 集合物件 set
      REDIS_ZSET 有序集合物件 zset
    • 編碼encoding的可選型別;同種型別可以有不同的編碼形式:

      型別 物件 編碼 編碼方式 OBJECT ENCODING命令輸出
      REDIS_STRING 字串 REDIS_ENCODING_INT 使用整數數值實現 int
      REDIS_STRING 字串 REDIS_ENCODING_EMBSTR 使用embstr編碼 embstr
      REDIS_STRING 字串 REDIS_ENCODING_RAW 使用SDS實現 raw
      REDIS_LIST 列表 REDIS_ENCODING_ZIPLIST 使用壓縮列表實現 ziplist
      REDIS_LIST 列表 REDIS_ENCODING_LINKEDLIST 使用雙端連結串列實現 linkedlist
      REDIS_HASH 雜湊 REDIS_ENCODING_ZIPLIST 使用壓縮列表實現 ziplist
      REDIS_HASH 雜湊 REDIS_ENCODING_HT 使用字典實現 hashtable
      REDIS_SET 集合 REDIS_ENCODING_INTSET 使用整數集合實現 intset
      REDIS_SET 集合 REDIS_ENCODING_HT 使用字典實現 hashtable
      REDIS_ZSET 有序集合 REDIS_ENCODING_ZIPLIST 使用壓縮列表實現 ziplist
      REDIS_ZSET 有序集合 REDIS_ENCODING_SKIPLIST 使用跳躍表字典實現 skiplist

2. 字串物件

  • 字串編碼可以是intrawembstr

    編碼型別 說明
    int 字串儲存整數值,並且這個整數可以用long型別表示
    raw 字串值的長度大於39位元組
    embstr 字串值的長度小於39位元組
  • embstr編碼是專門用於儲存短字串的一種優化編碼方式;

  • rawembstr的異同:

    • 二者都使用redisObject結構與sdshdr結構來表示字串;
    • embstr通過呼叫一次記憶體分配函式來分配一塊連續的空間;
    • raw通過呼叫兩次記憶體分配函式來分配一塊連續的空間;
  • embstr的優點:

    • 建立時只需要分配一次記憶體;
    • 釋放時只需要呼叫一次記憶體釋放函式;
    • 連續儲存在一塊連續記憶體裡,對快取友好;
      embstr編碼的字串物件
  • long double型別表示的浮點數在Redis中也是作為字串值來儲存的;

  • embstr編碼的字串物件實際上是隻讀的,要修改先會轉成raw編碼,再執行修改命令;

  • 字串命令請見《Redis常用命令及示例總結》


3. 列表物件

  • 列表的編碼物件可以是ziplistlinkedlist
  • redis 3.2以後,quicklist作為列表鍵的實現底層實現之一,代替了壓縮列表。
  • ziplist編碼的條件:
    • 列表物件儲存的所有字串元素的長度都小於64位元組;
    • 列表物件儲存的元素數量小於512個;

ziplist編碼的list

3.1 quicklist 快速連結串列

  • quicklist的定義在quicklist.h

    typedef struct quicklist {
        //指向頭部(最左邊)quicklist節點的指標
        quicklistNode *head;
        //指向尾部(最右邊)quicklist節點的指標
        quicklistNode *tail;
        //ziplist中的entry節點計數器
        unsigned long count;  
        //quicklist的quicklistNode節點計數器
        unsigned int len; 
        //儲存ziplist的大小,配置檔案設定,佔16bits
        int fill : 16;      
        //儲存壓縮程度值,配置檔案設定,佔16bits,0表示不壓縮
        unsigned int compress : 16; 
    } quicklist;
    
  • quicklist節點的定義:

    typedef struct quicklistNode {
        struct quicklistNode *prev;     //前驅節點指標
        struct quicklistNode *next;     //後繼節點指標
        //不設定壓縮資料引數recompress時指向一個ziplist結構
        //設定壓縮資料引數recompress指向quicklistLZF結構
        unsigned char *zl;
        //壓縮列表ziplist的總長度
        unsigned int sz;              
        //ziplist中包的節點數,佔16 bits長度
        unsigned int count : 16;    
        //表示是否採用了LZF壓縮演算法壓縮quicklist節點,1表示壓縮過,2表示沒壓縮,佔2 bits長度
        unsigned int encoding : 2;       
        //表示一個quicklistNode節點是否採用ziplist結構儲存資料,2表示壓縮了,1表示沒壓縮,預設是2,佔2bits長度
        unsigned int container : 2;   
        //標記quicklist節點的ziplist之前是否被解壓縮過,佔1bit長度
        //如果recompress為1,則等待被再次壓縮
        unsigned int recompress : 1;
        //測試時使用
        unsigned int attempted_compress : 1; 
        //額外擴充套件位,佔10bits長度
        unsigned int extra : 10; 
    } quicklistNode;
    

在這裡插入圖片描述

4. 雜湊物件

  • 雜湊物件編碼可以是ziplisthashtable

  • 使用ziplist編碼的條件:

    • 鍵和值的字串長度小於64位元組;
    • 鍵值對數量少於512個;
  • 使用ziplist編碼時:
    使用ziplist編碼時

  • 使用hashtable編碼時:

使用hashtable編碼時


5. 集合物件

  • 集合物件的編碼可以是intsethashtable
  • 使用inset編碼的條件:
    • 所有元素為整數值;
    • 儲存的元素不超過512個;
  • 使用inset編碼時:
    使用inset編碼的集合
  • 使用hashtable編碼時:
    使用hashtable編碼的集合
  • 集合命令請見《Redis常用命令及示例總結》

6. 有序集合物件

  • 有序集合的編碼可以是ziplistskiplist

  • 壓縮列表內的集合元素按分值從小到大排列;

  • 使用ziplist編碼時:
    在這裡插入圖片描述
    使用ziplist編碼的有序集合

  • 使用ziplist編碼的條件:

    • 元素數量少於128個;
    • 元素長度小於64位元組;
  • 使用skiplist編碼時,使用zset結構作為底層實現;

  • zset的定義:

    typedef struct zset{
        //跳躍表
        zskiplist *zsl;
        //字典
        dict *dict;
    } zset;
    
    • 跳躍表和字典使用指標來共享相同的元素和分值,因此不會產生重複,也不會造成記憶體浪費;
      使用skiplist編碼的有序集合
  • 有序集合命令請見《Redis常用命令及示例總結》


7. Redis物件的特點

7.1 型別檢查與命令多型

  • Redis的命令基本上分兩類。一種是可以對任意型別的鍵操作(基於型別的多型),一種是隻能對特定型別的鍵執行(基於編碼的多型);
  • 在執行特定型別命令之前,伺服器會先檢查redisObject結構的type屬性,判斷是否為執行該命令所需的型別。是則執行,否則返回型別錯誤;
  • Redis還會根據值物件的編碼方式選擇正確底層方法,使一個命令可以同時用於處理多種不同編碼方式的資料結構,進而實現多型命令;

型別檢查與命令多型

7.2 記憶體回收

  • C語言不具備自動回收記憶體的功能,Redis構建一個引用計數計數實現記憶體回收機制;

  • 引用計數由redisObject結構的refcount屬性記錄:

    typedef struct redisObject{
        //...
        //引用計數
        int refcount; 
        //...
    }
    
    • 建立新物件時,refcount被初始化為1;
    • 物件被新程式使用時,refcount++
    • 物件不被一個程式使用時,refcount--
    • 物件計數值為0時,物件佔用的記憶體會被釋放;

7.3 物件共享

  • 物件的計數屬性帶有物件共享的作用;
  • 當多個鍵儲存同一個值時,這些鍵的值指標指向同一個值物件,值物件的refcounr為n;
  • Redis在初始化伺服器時,會建立一萬個字串物件(0~9999的字串物件),當伺服器需要用到這些值物件時,伺服器會使用這些共享物件,而不是建立新物件;
  • Redis只對包含整數值的字串物件進行共享,驗證數字的時間複雜度為O(1);

物件共享

7.4 物件的空轉時長

  • redisObject結構裡有個lru屬性,記錄物件最後一次被命令程式訪問的時間;
    typedef struct redisObject{
        //...
        unsigned lru:22;
        //...
    }
    
  • 使用命令OBJECT IDLETIME可以顯示物件的空轉時間,不會改變物件的空轉時間;
  • 如果伺服器開啟maxmemory選項,並且回收記憶體的演算法為volatile-lruallkeys-lru,那麼當伺服器佔用的記憶體數超過maxmemory選項設定的上限值是,空轉時間較高的鍵會優先被伺服器釋放,回收記憶體;


最後

新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公眾號,會分享一些更日常的東西!
如需轉載,請標註出處!
Redis | 第一部分:資料結構與物件 下篇《Redis設計與實現》

相關文章