Redis | 第3章 對象《Redis設計與實現》



前言

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

本篇筆記按照書里的脈絡,將知識點分為四個部分。其中第一部分數據結構與對象分為上中下篇,上篇包括:SDS鏈表字典;中篇包括跳躍表整數集合壓縮列表;下篇為對象

上篇的鏈接:https://www.cnblogs.com/dlhjw/p/15569578.html

中篇的鏈接:https://www.cnblogs.com/dlhjw/p/15582039.html

與本章相關的 Redis 命令總結在下篇文章,歡迎點擊收藏,本篇將不再重復:

《Redis常用命令及示例總結(API)》https://www.cnblogs.com/dlhjw/p/15639773.html


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選項設置的上限值是,空轉時間較高的鍵會優先被服務器釋放,回收內存;


最后

新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公眾號,會分享一些更日常的東西!
如需轉載,請標注出處!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM