Redis實現之對象(一)

來源:互聯網
上載者:User

標籤:在伺服器   跳躍表   刪除   set   效率   lru   ring   car   分配   

對象

前面我們介紹了Redis的主要資料結構,如:簡單動態字串SDS、雙端鏈表、字典、壓縮列表、整數集合等。Redis並沒有直接使用這些資料結構來實現索引值對資料庫,而是基於這些資料結構建立了一個對象系統,這個系統包含字串對象、列表對象、雜湊對象、集合對象和有序集合對象這五種類型的對象,每種對象都用到了至少一種我們之前介紹的資料結構

通過這五種不同類型的對象,Redis可以在執行命令之前,根據對象的類型來判斷一個對象是否可以執行給定的命令。使用對象的另一個好處是,我們可以根據不同的使用情境,為對象設定多種不同的資料結構實現,從而最佳化對象在不同情境下的使用效率

除此之外,Redis的對象系統還實現了基於引用計數的記憶體回收機制,當程式不再使用某個對象時,這個對象所佔用的記憶體就會被自動釋放;另外,Redis還通過引用計數實現了對象共用機制,這一機制可以在適當的條件下,通過讓多個資料庫鍵共用一個對象來節約記憶體

最後,Redis的對象帶有訪問時間記錄資訊,該資訊可以用於計算資料庫鍵的空轉時間,在伺服器啟用了maxmemory功能的情況下,空轉時間較大的那些鍵可能會優先被伺服器刪除

對象的類型編碼

Redis使用對象來表示資料庫中的鍵和值,每次當我們在Redis的資料庫中新建立一個索引值對時,我們至少會建立兩個對象,一個對象用作索引值對的鍵(鍵對象),另一個對象用作索引值對的值(值 對象)

舉個栗子,以下SET命令在資料庫將建立一個新的索引值對,其中索引值對的鍵是一個包含了字串"msg"的對象,而索引值對的值則是一個包含了字串"hello world"的對象

127.0.0.1:6379> SET msg "hello world"OK

  

Redis中的每個對象都由一個redisObject結構表示,該結構中儲存資料相關的三個屬性分別是:type、encoding、ptr

redis.h

typedef struct redisObject {//類型    unsigned type:4;    unsigned notused:2;  //編碼    unsigned encoding:4;    unsigned lru:22;  //引用計數    int refcount;//指向底層實現資料結構的指標    void *ptr;} robj;

  

類型

對象的type屬性記錄了對象的類型,這個屬性的值可以是表1-1列出對的常量中的一個

表1-1   對象的類型
類型常量 對象的名稱
REDIS_STRING 字串對象
REDIS_LIST 列表對象
REDIS_HASH 雜湊對象
REDIS_SET 集合對象
REDIS_ZSET 有序集合對象

對於Redis資料庫儲存的索引值對來說,鍵總是一個字串對象,而值則可以是字串對象、列表對象、雜湊對象、集合對象或者有序集合對象的其中一種,因此:

  • 當我們稱呼一個資料庫鍵為“字串鍵”時,我們指的是“這個資料庫鍵所對應的值為字串對象”
  • 當我們稱呼一個資料庫鍵為“列表鍵”時,我們指的是“這個資料庫鍵所對應的值為列表對象”

TYPE命令的實現方式也與此類似,當我們對一個資料庫鍵執行TYPE命令時,命令返回的結果為資料庫鍵對應的值物件類型,而不是鍵物件類型:

# 鍵為字串對象,值為字串對象127.0.0.1:6379> SET msg "hello world"OK127.0.0.1:6379> TYPE msgstring# 鍵為字串對象,值為列表對象127.0.0.1:6379> RPUSH numbers 1 3 5(integer) 3127.0.0.1:6379> TYPE numberslist# 鍵為字串對象,值為雜湊對象127.0.0.1:6379> HMSET profile name Tome age 25 career ProgrammerOK127.0.0.1:6379> TYPE profilehash# 鍵為字串對象,值為集合對象127.0.0.1:6379> SADD fruits apple banana cherry(integer) 3127.0.0.1:6379> TYPE fruitsset# 鍵為字串對象,值為有序集合對象127.0.0.1:6379> ZADD price 8.5 apple 5.0 banana 6.0 cherry(integer) 3127.0.0.1:6379> TYPE pricezset

  

表1-2列出了TYPE命令在面對不同類型的值對象時所產生的輸出

表1-2   不同類型值對象的TYPE命令輸出
對象 對象type屬性的值 TYPE命令的輸出
字串對象 REDIS_STRING string
列表對象 REDIS_LIST list
雜湊對象 REDIS_HASH hash
集合對象 REDIS_SET set
有序集合對象 REDIS_ZSET zset

編碼和底層實現

對象的ptr指標指向對象的底層實現資料結構,而這些資料結構由對象的encoding屬性決定。encoding屬性記錄了對象使用的編碼,也即是說這個對象使用了什麼資料結構作為對象的底層實現,這個屬性的值可以是表1-3列出的常量的其中一個

表1-3   對象的編碼
編碼常量 編碼所對應的底層資料結構
REDIS_ENCODING_INT long類型的整數
REDIS_ENCODING_EMBSTR embstr編碼的簡單動態字串
REDIS_ENCODING_RAW 簡單動態字串
REDIS_ENCODING_HT 字典
REDIS_ENCODING_LINKEDLIST 雙端鏈表
REDIS_ENCODING_ZIPLIST 壓縮列表
REDIS_ENCODING_INTSET 整數集合
REDIS_ENCODING_SKIPLIST 跳躍表和字典

每種類型的對象都至少使用了兩種不同的編碼,表1-4列出了每種類型的對象可以使用的編碼

表1-4   不同類型和編碼的對象
類型 編碼 對象
REDIS_STRING REDIS_ENCODING_INT 使用整數值實現的字串對象
REDIS_STRING REDIS_ENCODING_EMBSTR 使用embstr編碼的簡單動態字串實現的字串對象
REDIS_STRING REDIS_ENCODING_RAW 使用簡單動態字串實現的字串對象
REDIS_LIST REDIS_ENCODING_ZIPLIST 使用壓縮列表實現的列表對象
REDIS_LIST REDIS_ENCODING_LINKEDLIST 使用雙端鏈表實現的列表對象
REDIS_HASH REDIS_ENCODING_ZIPLIST 使用壓縮列表實現的雜湊對象
REDIS_HASH REDIS_ENCODING_HT 使用字典實現的雜湊對象
REDIS_SET REDIS_ENCODING_INTSET 使用整數集合實現的集合對象
REDIS_SET REDIS_ENCODING_HT 使用字典實現的集合對象
REDIS_ZSET REDIS_ENCODING_ZIPLIST 使用壓縮列表實現的有序集合對象
REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳躍表和字典實現的有序集合對象

使用OBJECT ENCODING命令可以查看一個資料庫鍵的值對象的編碼:

127.0.0.1:6379> SET msg "hello wrold"OK127.0.0.1:6379> OBJECT ENCODING msg"embstr"127.0.0.1:6379> SADD numbers 1 3 5(integer) 3127.0.0.1:6379> OBJECT ENCODING numbers"intset"127.0.0.1:6379> SADD numbers "seven"(integer) 1127.0.0.1:6379> OBJECT ENCODING numbers"hashtable"

  

表1-5列出了不同編碼的對象所對應的OBJECT ENCODING命令輸出:

表1-5   OBJECT ENCODING對不同編碼的輸出
對象所使用的底層資料結構 編碼常量 OBJECT ENCODING命令輸出
整數 REDIS_ENCODING_INT int
embstr編碼的簡單動態字串(SDS) REDIS_ENCODING_EMBSTR embstr
簡單動態字串 REDIS_ENCODING_RAW raw
字典 REDIS_ENCODING_HT hashtable
雙端鏈表 REDIS_ENCODING_LINKEDLIST linkedlist
雙端鏈表 REDIS_ENCODING_ZIPLIST ziplist
整數集合 REDIS_ENCODING_INTSET intset
跳躍表和字典 REDIS_ENCODING_SKIPLIST skiplist

通過encoding屬性來設定對象所使用的編碼,而不是為特定類型的對象關聯一種固定的編碼,極大地提升了Redis的靈活性和效率,因為Redis可以根據不同的使用情境來為一個對象設定不同的編碼,從而最佳化對象在某一情境下的效率。舉個栗子,在列表對象包含的元素比較少時,Redis使用壓縮列表作為列表對象的底層實現:

  • 因為壓縮列表比雙端鏈表更節約記憶體,並且在元素比較少時,在記憶體中以連續塊方式儲存的壓縮列表比起雙端鏈表可以更快被載入到緩衝中
  • 隨著列表對象包含的元素越來越多,使用壓縮列表來儲存元素的優勢逐漸消失時,對象就會將底層實現從壓縮列錶轉換成功能更強、更適合儲存大量元素的雙端鏈表

其他類型的對象也會通過使用多種不同的編碼來進行類似的最佳化,在接下來的內容中,我們將分別介紹Redis中的五種不同類型的對象,說明這些對象底層所使用的編碼方式,列出對象從一種編碼轉換成另一種編碼所需的條件,以及同一個命令在多種不同編碼上的實現方法

字串對象

字串對象的編碼可以是int、raw或者embstr。如果一個字串對象儲存的是整數值,並且這個整數值可以用long類型來表示,那麼字串對象會將整數值儲存在字串結構的ptr屬性中(將void *轉換成long),並將字串對象的編碼設定為int

舉個栗子,如果我們執行以下SET命令,那麼伺服器將建立一個1-1所示的int編碼的字串對象作為number鍵的值:

127.0.0.1:6379> SET number 10086OK127.0.0.1:6379> OBJECT ENCODING number"int"

  

 

圖1-1   int編碼的字串對象

如果字串對象儲存的是一個字串值,並且這個字串值的長度大於32位元組,那麼字串對象將使用一個簡單動態字串(SDS)來儲存這個字串值,並將對象的編碼設定為raw。舉個栗子,如果我們執行以下命令,那麼伺服器將建立一個1-2所示的raw編碼的字串作為store鍵的值 

127.0.0.1:6379> SET story "Long, long, long, long, long ago there lived a king ..."OK127.0.0.1:6379> STRLEN story(integer) 55127.0.0.1:6379> OBJECT ENCODING story"raw"

  

 

圖1-2   raw編碼的字串對象

如果字串對象儲存的是一個字串,並且這個字串長度小於等於44位元組,那麼字串將使用embstr編碼,看下面的樣本

127.0.0.1:6379> SET story "Long, long, long, long, long ago there lived"OK127.0.0.1:6379> STRLEN story(integer) 44127.0.0.1:6379> OBJECT ENCODING story"embstr"127.0.0.1:6379> SET story "Long, long, long, long, long ago there lived "OK127.0.0.1:6379> STRLEN story(integer) 45127.0.0.1:6379> OBJECT ENCODING story"raw"

  

embstr編碼是專門用於儲存短字串的一種最佳化編碼方式,這種編碼方式和raw編碼一樣,都使用redisObject結構和sdshdr結構來表示字串對象,但raw編碼會調用兩次記憶體配置函數來分別建立redisObject結構和sdshdr結構,而embstr編碼則通過調用一次記憶體配置函數來分配一塊連續的空間,空間中依次包含redisObject和sdshdr結構,1-3所示

圖1-3   embstr編碼建立的記憶體塊結構

embstr編碼的字串對象在執行命令時,產生的效果和raw編碼的字串對象執行命令時產生的效果是相同的,但使用embstr編碼的字串來儲存短字串值有以下好處:

  • embstr編碼將建立字串對象所需的記憶體配置次數從raw編碼的兩次降低為一次
  • 釋放embstr編碼的字串對象只需調用一次記憶體釋放函數,而釋放raw編碼的字串對象需要調用兩次記憶體釋放函數
  • 因為embstr編碼的字串對象的所有資料都儲存在一塊連續的記憶體中,所以這種編碼的字串對象比起raw編碼的字串對象能夠更好地利用緩衝帶來的優勢

作為例子,以下命令建立一個embstr編碼的字串對象作為msg鍵的值,值對象的樣子1-4所示

127.0.0.1:6379> SET msg "hello world"OK127.0.0.1:6379> OBJECT ENCODING msg"embstr"

 

 

圖1-4   embstr編碼的字串對象

最後要說的是,可以用long double類型表示的浮點數在Redis中也是作為字串值來儲存的。如果我們要儲存一個浮點數到字串對象裡面,那麼程式先將這個浮點數轉換成字串值,然後將其儲存。舉個栗子,執行以下代碼將建立一個包含3.14的字串對象

127.0.0.1:6379> SET pi 3.14OK127.0.0.1:6379> OBJECT ENCODING pi"embstr"

  

在有需要的時候,程式會將儲存在字串對象中的字串值轉換回浮點數值,執行某些操作,然後再將執行操作所得的浮點數轉換回字串值,並繼續儲存在字串對象裡面。舉個栗子,我們執行以下代碼:

127.0.0.1:6379> SET pi 3.14OK127.0.0.1:6379> INCRBYFLOAT pi 3.0"6.14"127.0.0.1:6379> OBJECT ENCODING pi"embstr

  

 程式首先會取出字串對象中儲存的字串值"3.14",將它轉換回浮點數值3.14,然後把3.14和2.0相加得到5.14後在轉換回字串,並將字串"5.14"儲存到字串對象中。表1-6總結並列出字串對象儲存各種不同類型的值所使用的編碼方式

表1-6   字串對象儲存各類型值的編碼方式
值 編碼
可以用long類型儲存的整數 int
可以用long double類型儲存的浮點數 embstr或者raw
字串值,或者因為長度太大而沒辦法用long類型表示的整數,又或者因為長度太大而沒辦法用long double類型表示的浮點數 embstr或者raw

編碼的轉換

int編碼的字串對象和embstr編碼的字串對象在條件滿足的情況下,會被轉換為raw編碼的字串對象。對於int編碼的字串對象來說,如果我們向對象執行了一些命令,使得對象儲存的不再是整數值,而是一個字串值,那麼字串對象將從int變為raw

下面的樣本中,我們通過APPEND命令,向一個儲存整數值的字串追加一個字串值,因為追加操作只能對字串值執行,所以程式會將之前儲存的整數值轉換為字串,然後再執行追加操作,操作的執行結果就是一個raw編碼的、儲存了字串值的字串對象

127.0.0.1:6379> SET number 10086OK127.0.0.1:6379> OBJECT ENCODING number"int"127.0.0.1:6379> APPEND number " is a good number!"(integer) 23127.0.0.1:6379> GET number"10086 is a good number!"127.0.0.1:6379> OBJECT ENCODING number"raw"

  

另外,因為Redis沒有為embstr編碼的字串對象編寫任何相應的修改程式(只有int編碼的字串對象和raw編碼的字串對象有這些程式),所以embstr編碼的字串對象實際上是唯讀。當我們對embstr編碼的字串對象執行任何修改命令時,程式先將對象的編碼從embstr轉換成raw,然後再執行修改命令。因為這個原因,embstr編碼的字串對象在執行修改命令之後,總會變成一個raw編碼的字串對象

以下代碼展示了一個embstr編碼的字串對象在執行APPEND命令之後,對象的編碼從embstr變為raw的例子:

127.0.0.1:6379> SET msg "hello world"OK127.0.0.1:6379> OBJECT ENCODING msg"embstr"127.0.0.1:6379> APPEND msg " again!"(integer) 18127.0.0.1:6379> OBJECT ENCODING msg"raw"

  

字串命令的實現

因為字串鍵的值為字串對象,所以用於字串鍵的所有命令都是針對字串對象來構建的,表1-7例舉了其中一部分字串命令,以及這些命令在不同編碼的字串對象下的實現方法

表1-7   字串命令的實現
命令 int編碼的實現方法 embstr編碼的實現方法 raw編碼的實現方法
SET   使用int編碼儲存值 使用embstr編碼儲存值 使用raw編碼儲存值
GET 拷貝對象所儲存的整數值,將這個拷貝轉換成字串值,然後向用戶端返回這個字串值 直接向用戶端返回字串值 直接向用戶端返回字串值
APPEND 將對象轉換成raw編碼,然後按raw編碼的方式執行此操作 將對象轉換成raw編碼,然後按raw編碼的方式執行此操作 調用sdscatlen函數,將給定字串追加到現有字串的末尾
INCRBYFLOAT   

取出整數值並將其轉換成long double類型的浮點數,對這個浮點數進行加法計算,然後將得出的浮點數結果儲存起來

取出字串值並嘗試將其轉換成long double類型的浮點數,對這個浮點數進行加法計算,然後將得出的浮點數結果儲存起來。

如果字串值不能被轉換成浮點數,那麼向用戶端返回一個錯誤

取出字串值並嘗試將其轉換成long double類型的浮點數,對這個浮點數進行加法計算,然後將得出的浮點數結果儲存起來。

如果字串值不能被轉換成浮點數,那麼向用戶端返回一個錯誤

INCRBY 對整數值進行加法計算,得出的計算結果會作為整數被儲存起來 embstr編碼不能執行此命令,向用戶端返回一個錯誤 raw編碼不能執行此命令,向用戶端返回一個錯誤
DECRBY 對整數值進行減法計算,得出的計算結果會作為整數被儲存起來 embstr編碼不能執行此命令,向用戶端返回一個錯誤 raw編碼不能執行此命令,向用戶端返回一個錯誤
STRLEN 拷貝對象所儲存的整數值,將這個拷貝轉換成字串值,計算並返回這個字串值的長度 調用sdslen函數,返回字串的長度 調用sdslen函數,返回字串的長度
SETRANGE 將對象轉換成raw編碼,然後按raw編碼的方式執行此命令 將對象轉換成raw編碼,然後按raw編碼的方式執行此命令 將字串特定索引上的值設定為給定的字元
GETRANGE 拷貝對象所儲存的整數值,將這個拷貝轉換成字串值,然後取出並返回字串指定索引上的字元 直接取出並返回字串指定索引上的字元 直接取出並返回字串指定索引上的字元

Redis實現之對象(一)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.