Redis 使用對象來表示數據庫中的鍵和值, 每次當我們在 Redis 的數據庫中新創建一個鍵值對時, 我們至少會創建兩個對象, 一個對象用作鍵值對的鍵(鍵對象), 另一個對象用作鍵值對的值(值對象)。
舉個例子, 以下?SET?命令在數據庫中創建了一個新的鍵值對, 其中鍵值對的鍵是一個包含了字符串值?`"msg"`?的對象, 而鍵值對的值則是一個包含了字符串值?`"hello?world"`?的對象:
~~~
redis> SET msg "hello world"
OK
~~~
Redis 中的每個對象都由一個?`redisObject`?結構表示, 該結構中和保存數據有關的三個屬性分別是?`type`?屬性、?`encoding`?屬性和?`ptr`?屬性:
~~~
typedef struct redisObject {
// 類型
unsigned type:4;
// 編碼
unsigned encoding:4;
// 指向底層實現數據結構的指針
void *ptr;
// ...
} robj;
~~~
## 類型
對象的?`type`?屬性記錄了對象的類型, 這個屬性的值可以是表 8-1 列出的常量的其中一個。
* * *
表 8-1 對象的類型
| 類型常量 | 對象的名稱 |
| --- | --- |
| `REDIS_STRING` | 字符串對象 |
| `REDIS_LIST` | 列表對象 |
| `REDIS_HASH` | 哈希對象 |
| `REDIS_SET` | 集合對象 |
| `REDIS_ZSET` | 有序集合對象 |
* * *
對于 Redis 數據庫保存的鍵值對來說, 鍵總是一個字符串對象, 而值則可以是字符串對象、列表對象、哈希對象、集合對象或者有序集合對象的其中一種, 因此:
* 當我們稱呼一個數據庫鍵為“字符串鍵”時, 我們指的是“這個數據庫鍵所對應的值為字符串對象”;
* 當我們稱呼一個鍵為“列表鍵”時, 我們指的是“這個數據庫鍵所對應的值為列表對象”,
諸如此類。
TYPE?命令的實現方式也與此類似, 當我們對一個數據庫鍵執行?TYPE?命令時, 命令返回的結果為數據庫鍵對應的值對象的類型, 而不是鍵對象的類型:
~~~
# 鍵為字符串對象,值為字符串對象
redis> SET msg "hello world"
OK
redis> TYPE msg
string
# 鍵為字符串對象,值為列表對象
redis> RPUSH numbers 1 3 5
(integer) 6
redis> TYPE numbers
list
# 鍵為字符串對象,值為哈希對象
redis> HMSET profile name Tome age 25 career Programmer
OK
redis> TYPE profile
hash
# 鍵為字符串對象,值為集合對象
redis> SADD fruits apple banana cherry
(integer) 3
redis> TYPE fruits
set
# 鍵為字符串對象,值為有序集合對象
redis> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
redis> TYPE price
zset
~~~
表 8-2 列出了?TYPE?命令在面對不同類型的值對象時所產生的輸出。
* * *
表 8-2 不同類型值對象的?TYPE?命令輸出
| 對象 | 對象?`type`?屬性的值 | TYPE?命令的輸出 |
| --- | --- | --- |
| 字符串對象 | `REDIS_STRING` | `"string"` |
| 列表對象 | `REDIS_LIST` | `"list"` |
| 哈希對象 | `REDIS_HASH` | `"hash"` |
| 集合對象 | `REDIS_SET` | `"set"` |
| 有序集合對象 | `REDIS_ZSET` | `"zset"` |
* * *
## 編碼和底層實現
對象的?`ptr`?指針指向對象的底層實現數據結構, 而這些數據結構由對象的?`encoding`?屬性決定。
`encoding`?屬性記錄了對象所使用的編碼, 也即是說這個對象使用了什么數據結構作為對象的底層實現, 這個屬性的值可以是表 8-3 列出的常量的其中一個。
* * *
表 8-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` | 跳躍表和字典 |
* * *
每種類型的對象都至少使用了兩種不同的編碼, 表 8-4 列出了每種類型的對象可以使用的編碼。
* * *
表 8-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?命令可以查看一個數據庫鍵的值對象的編碼:
~~~
redis> SET msg "hello wrold"
OK
redis> OBJECT ENCODING msg
"embstr"
redis> SET story "long long long long long long ago ..."
OK
redis> OBJECT ENCODING story
"raw"
redis> SADD numbers 1 3 5
(integer) 3
redis> OBJECT ENCODING numbers
"intset"
redis> SADD numbers "seven"
(integer) 1
redis> OBJECT ENCODING numbers
"hashtable"
~~~
表 8-5 列出了不同編碼的對象所對應的?OBJECT ENCODING?命令輸出。
* * *
表 8-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 中的五種不同類型的對象, 說明這些對象底層所使用的編碼方式, 列出對象從一種編碼轉換成另一種編碼所需的條件, 以及同一個命令在多種不同編碼上的實現方法。
- 介紹
- 前言
- 致謝
- 簡介
- 第一部分:數據結構與對象
- 簡單動態字符串
- SDS 的定義
- SDS 與 C 字符串的區別
- SDS API
- 重點回顧
- 參考資料
- 鏈表
- 鏈表和鏈表節點的實現
- 鏈表和鏈表節點的 API
- 重點回顧
- 字典
- 字典的實現
- 哈希算法
- 解決鍵沖突
- rehash
- 漸進式 rehash
- 字典 API
- 重點回顧
- 跳躍表
- 跳躍表的實現
- 跳躍表 API
- 重點回顧
- 整數集合
- 整數集合的實現
- 升級
- 升級的好處
- 降級
- 整數集合 API
- 重點回顧
- 壓縮列表
- 壓縮列表的構成
- 壓縮列表節點的構成
- 連鎖更新
- 壓縮列表 API
- 重點回顧
- 對象
- 對象的類型與編碼
- 字符串對象
- 列表對象
- 哈希對象
- 集合對象
- 有序集合對象
- 類型檢查與命令多態
- 內存回收
- 對象共享
- 對象的空轉時長
- 重點回顧
- 第二部分:單機數據庫的實現
- 數據庫
- 數據庫鍵空間
- 重點回顧
- RDB 持久化
- RDB 文件結構
- 重點回顧
- AOF 持久化
- AOF 持久化的實現
- 重點回顧
- 事件
- 文件事件
- 重點回顧
- 參考資料
- 客戶端
- 客戶端屬性
- 重點回顧
- 服務器
- 命令請求的執行過程
- 重點回顧
- 第三部分:多機數據庫的實現
- 復制
- 舊版復制功能的實現
- 重點回顧
- Sentinel
- 啟動并初始化 Sentinel
- 重點回顧
- 參考資料
- 集群
- 節點
- 重點回顧
- 第四部分:獨立功能的實現
- 發布與訂閱
- 頻道的訂閱與退訂
- 重點回顧
- 參考資料
- 事務
- 事務的實現
- 重點回顧
- Lua 腳本
- 創建并修改 Lua 環境
- 重點回顧
- 排序
- SORT <key> 命令的實現
- 重點回顧
- 二進制位數組
- GETBIT 命令的實現
- 重點回顧
- 慢查詢日志
- 慢查詢記錄的保存
- 慢查詢日志的閱覽和刪除
- 添加新日志
- 重點回顧
- 監視器
- 成為監視器
- 向監視器發送命令信息
- 重點回顧
- 源碼、相關資源和勘誤