redis筆記-對象系統篇

2018-1-4 by Atlas

* 簡述

  • redis並沒有直接使用SDS、鏈表、字典、壓縮列表、整數集合、跳躍表這些數據結構來實現鍵值對數據庫,而是基於這些數據結構創建了一個對象系統,這個系統包含字符串對象、列表對象、哈希對象、集合對象、有序集合對象這五種類型的對象,每種對象都至少用到了一種上面提到的數據結構。
  • 使用對象的一個好處是,可以針對不同的使用場景,爲對象設置多種不同的數據結構實現,從而優化對象在不同場景下的使用效率。
  • 對象系統還實現了基於引用計數技術的內存回收機制,釋放不再使用的對象所佔用的內存。
  • 引用計數技術實現了對象共享機制,通過多個數據庫鍵共享同一個對象來節約內存。

* 定義

typeof struct redisObject {
        // 類型
        unsigned type:4;
        // 編碼
        unsigned encoding:4;
        // 指向底層實現數據結構的指針
        void *ptr;
        // 引用計數
        int refcount;
        // 空轉時長
        unsigned lru:22;
} robj;

* 類型

對象的類型

TYPE命令,返回的結果爲數據庫鍵對應的值對象的類型,而不是鍵對象的類型。

redis> SET msg "hello"
ok
redis> TYPE msg
string

不同類型值對象的TYPE命令輸出

* 編碼

對象的編碼

OBJECT ENCODING命令查看數據庫鍵的值對象的編碼。

redis> SET msg "hello"
ok
redis> OBJECT ENCODING msg
"embstr"

不同類型和編碼的對象

OBJECT ENCODING對不同編碼的輸出

* 類型檢查

類型檢查是通過redisObject結構的type屬性來實現的:
1)執行命令前,服務器先檢查輸入數據庫鍵的值對象是否爲執行命令所需的類型,如果是,服務器就對鍵執行命令;
2)否則,服務器拒絕執行命令,並向客戶端返回一個類型錯誤。

LLEN命令執行時的類型檢查過程
LLEN命令執行時的類型檢查過程

* 多態實現

  • 基於編碼的多態--一個命令可以同時用於處理多種不同的編碼。
  • 基於類型的多態--一個命令可以同時用於處理多種不同類型的鍵。

LLEN命令執行過程
LLEN命令執行過程

* 空轉時長

lru屬性記錄了對象最後一次被命令程序訪問的時間。
OBJECT IDLETIME 命令可以打印給定鍵的空轉時長,等於當前時間減去鍵的值對象的lru時間。
如果服務器打開了maxmemory選項,並且內存回收算法爲volatile-lru或者allkeys-lru,那麼服務器佔用的內存數超過maxmemory選項設置的上限空轉時長較高的部分鍵會優先被釋放,回收內存。

* 內存回收

對象系統構建了一個引用計數技術實現的內存回收機制。
對象的引用計數信息會隨着對象的使用狀態而不斷變化:

  • 創建一個新對象時,引用計數值初始化爲1;
  • 當對象被一個新程序使用時,引用計數值增1;
  • 當對象不再被一個程序使用時,引用計數值減1;
  • 當對象的引用計數值變爲0,對象所佔的內存將會被釋放。

* 對象共享

多個鍵共享同一個值對象需要執行以下步驟:
1)將數據庫鍵的值指針指向一個現有的值對象;
2)將被共享的值對象的引用技術增一。

共享對象

* 字符串對象

編碼可以是int、raw或者embstr。

  • 結構

如果字符串對象保存的是整數值,int編碼實現。

int編碼的字符串對象

如果字符串對象保存的是字符串值,並且長度小於等於39字節,embstr編碼實現。

embstr編碼的字符串對象

如果字符串對象保存的是字符串值,並且長度大於39字節,raw編碼實現。

raw編碼的字符串對象

  • 策略

int、embstr、raw三種不同編碼實現字符串對象,就是根據值對象類型優化內存分配。
embstr編碼的字符串對象較raw編碼的字符串對象將內存分配次數從兩次降低爲一次。
embstr編碼的字符串對象較raw編碼的字符串對象內存釋放也從兩次減少爲一次。
embstr編碼的字符串對象數據保存在一塊連續的內存能夠更好地利用緩存的優勢。
但是embstr編碼的字符串對象實際上是隻讀的,int編碼的字符串對象的值變更成字符串值,對象將轉換成raw編碼的字符串對象。

  • 命令

SET:保存值。
GET:取值。
APPEND:追加字符串。
STRLEN:取字符串長度。

* 列表對象

編碼可以是ziplist或者linkedlist。

  • 結構

ziplist編碼列表對象。

ziplist編碼列表對象

linkedlist編碼列表對象。

linkedlist編碼列表對象

  • 策略

同時滿足以下兩個條件時,列表對象使用ziplist編碼:
1)列表對象保存的所有字符串元素的長度都小於64字節;
2)列表對象保存的元素數量小於512個。
任意一個條件不滿足,列表對象使用linkedlist編碼。
配置:list-max-ziplist-value選項和list-max-ziplist-entries選項。

  • 命令

RPUSH:添加列表表頭元素。
LPOP:彈出列表表頭元素。
LINSERT:插入元素。
LLEN:取列表長度。

* 哈希對象

編碼可以是ziplist或者hashtable。

  • 結構

ziplist編碼哈希對象

ziplist編碼哈希對象

hashtable編碼哈希對象

hashtable編碼哈希對象

  • 策略

同時滿足以下兩個條件,哈希對象使用ziplist編碼:
1)哈希對象保存的所有鍵值對對象的鍵和值的字符串長度都小於64字節;
2)哈希對象保存的鍵值對數量小於512個。
任意一個條件不滿足,哈希對象使用hashtable編碼。
配置:hash-max-ziplist-value選項和hash-max-ziplist-entries選項。

  • 命令

HSET:添加元素。
HGET:獲取元素。
HDEL:刪除元素。
HLEN:取哈希元素數量

* 集合對象

編碼可以是intset或者hashtable。

  • 結構

intset編碼集合對象。

intset編碼集合對象

hashtable編碼集合對象。

hashtable編碼集合對象

  • 策略

同時滿足以下兩個條件,集合對象使用intset編碼:
1)集合對象保存的所有元素都是整數值;
2)集合對象保存的元素數量不超過512個。
任意一個條件不滿足,集合對象使用hashtable編碼。

  • 命令

SADD:添加元素。
SPOP:彈出元素。
SCARD:取集合元素數量。

* 有序集合對象

編碼可以是ziplist或者skiplist。

  • 結構

ziplist編碼有序集合對象。

ziplist編碼有序集合對象

skiplist編碼有序集合對象。

skiplist編碼有序集合對象

  • 策略

同時滿足以下兩個條件,有序集合對象使用ziplist編碼:
1)有序集合對象保存的所有成員的長度都小於64字節;
2)有序集合對象保存的元素數量小於128個。
任意一個條件不滿足,序集合對象使用skiplist編碼。

  • 命令

ZADD:添加元素。
ZCARD:取有序集合對象元素數量。
ZRANK:從表頭向表尾遍歷有序集合。
ZSCORE:取給定成員分值。

參考文獻:《redis設計與實現》

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