上一章講解了Redis中的數據結構,這一章對Redis中的對象做簡要介紹。本篇文章是對Redis對象做一個全局性的概覽,每個對象的代碼實現部分以後會一一解析。
Redis對象
Redis中有五大對象,分別是:字符串對象,列表對象,哈希對象,集合對象,有序集合對象。每一個對象都需要存儲鍵值對,而key必須是字符串對象,value可能是五種類型對象之一。所以瞭解對象底層的存儲編碼方式有助於加深對Redis底層的瞭解,同時也是Redis設計思想中的重中之重,屬於瞭解Redis的第一步。
Redis中的每個對象都是redisObject結構體表示的。
// 對象類型
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
// 對象編碼
#define REDIS_ENCODING_RAW 0
#define REDIS_ENCODING_INT 1
#define REDIS_ENCODING_HT 2
#define REDIS_ENCODING_ZIPMAP 3 //後面的版本已經摒棄,這裏不再考慮
#define REDIS_ENCODING_LINKEDLIST 4
#define REDIS_ENCODING_ZIPLIST 5
#define REDIS_ENCODING_INTSET 6
#define REDIS_ENCODING_SKIPLIST 7
#define REDIS_ENCODING_EMBSTR 8
//redis對象結構體
typedef struct redisObject {
// 類型
unsigned type:4;
// 編碼
unsigned encoding:4;
// 對象最後一次被訪問的時間
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用計數
int refcount;
// 指向實際值的指針
void *ptr;
} robj;
字符串對象
字符串對象底層實現可能編碼有三種,分別是raw SDS、整數、和embstr編碼的SDS。當字符串對象保存的是一個整數值,則可以直接用整型編碼來表示,即ptr指向一個long;當字符串對象保存的是一個字符串且字符串值的長度大於39字節,則用raw編碼方式保存這個字符串的值,即ptr指向一個SDS類型;當字符串對象保存的是一個字符串且字符串值的長度小於39字節,則用embstr編碼方式保存字符串。
int編碼和raw編碼都很簡單,這裏主要詳細說一下embstr編碼。embstr編碼主要適合於短字符串的編碼實現,通過調用一次內存分配函數分配一個連續的內存空間,用來存放redisobject和sdshdr對象。相比於raw編碼方式,其有三個優點:
- 內存分配時候從raw編碼的兩次(分別申請redisobject和sdshdr)變成一次。
- 內存釋放時候從raw編碼的兩次(分別釋放redisobject和sdshdr)變成一次。
- embstr編碼中字符串對象是在連續的內存中,有利於緩存。
還有一點需要注意,字符串對象的三種編碼方式之間可以轉換。關係如下圖。int編碼當時當用append命令加上字符串的時候就變成raw編碼方式了,embstr編碼方式也是隻讀方式,當嘗試對其做任何修改,也會將其轉換成raw編碼方式。
字符串對象常用的命令如下:
命令 | 作用 |
---|---|
SET | 插入字符串對象的值 |
GET | 根據鍵返回字符串值 |
APPEND | 根據鍵將字符串插入原值後面 |
INCRBY | 將整數值加法(只有整型編碼纔可以執行) |
DECRBY | 將整數值減法(只有整形編碼纔可以執行) |
STRLEN | 返回字符串的長度 |
SETRANGE | 將索引上的值設定爲給定的字符(只有raw編碼可以執行) |
GETRANGE | 返回字符串索引上的值(只有raw編碼可以執行) |
列表對象
列表對象的底層編碼是linkedlist和ziplist。當列表對象保存的元素數小於512且字符串元素小於64字節的時候用ziplist編碼,不滿足的時候用linkedlist編碼。兩種編碼可以轉換。下圖是兩種編碼類型的圖示。
列表常用的命令如下:
命令 | 作用 |
---|---|
LPUSH | 列表中頭部插入值 |
RPUSH | 列表尾部插入值 |
LPOP | 列表頭部彈出值 |
RPOP | 列表尾部彈出值 |
LLEN | 返回列表的長度 |
LINDEX | 返回列表索引的值 |
LINSERT | 在列表指定索引的位置中插入值 |
LSET | 在列表指定索引位置更新節點的值 |
LREM | 在列表中刪除給定值的節點 |
LTRIM | 在列表上刪除指定索引之外的節點 |
哈希對象
哈希對象的底層編碼有兩種ziplist和hashtable。當哈希對象保存的鍵值對數小於512且鍵和值字符串長度小於64字節的時候用ziplist編碼,不滿足的時候用hashtable編碼。兩種編碼可以轉換。
命令 | 作用 |
---|---|
HSET | 將新節點添加到字典中 |
HGET | 根據給定鍵返回相對應的值 |
HEXITS | 根據給定鍵判斷是否字典中存在 |
HDEL | 根據給定鍵刪除字典中的鍵值對 |
HLEN | 字典中鍵值對的數量 |
HGETALL | 返回字典中所有的鍵值對 |
集合對象
集合對象的編碼方式也有兩種:intset和hashtable。當集合中所有對象都是整數並且元素數量不超過512個就爲intset編碼,否則用hashtable編碼。
命令 | 作用 |
---|---|
SADD | 添加新元素 |
SCARD | 返回集合中的元素數量 |
SISMEMBER | 集合中查找是否存在元素 |
SMEMBERS | 返回整個集合元素 |
SRANDMEMBER | 集合中隨機返回一個元素 |
SPOP | 集合中隨機刪除一個元素 |
SREM | 集合中刪除所有的元素 |
有序集合對象
有序集合對象的底層編碼是ziplist或skiplist。當有序集合中元素數量小於128且所有元素長度小於64字節時候用ziplist編碼,否則用skiplist編碼。當使用ziplist編碼時,壓縮列表中對象按照分值從小到大排列,每個對象先存放對象內容,再存放對象分值。當使用skiplist編碼時,其實是同時使用了字典和跳錶。字典中存儲了對象的元素和分值匹配關係,跳錶根據分值從小到大有序存儲。
這樣設計的主要原因是同時利用跳錶和字典的優勢,比如ZSCORE命令可以返回指定元素的分值,如果只有跳錶則查詢的複雜度爲O(logn),但是加上字典後查找的複雜度變成O(1)。但是如ZRANGE這類根據範圍進行查找的命令就可以有效利用,
命令 | 作用 |
---|---|
ZADD | 插入新元素 |
ZCARD | 返回有序集合中的元素數量 |
ZCOUNT | 有序集合給定分值範圍內的元素數量 |
ZRANGE | 從表頭到表尾遍歷,返回索引範圍內的元素數量 |
ZREVRANGE | 從表尾到表頭遍歷,返回索引範圍內的元素數量 |
ZSCORE | 有序集合中返回給定成員的分值 |
ZREM | 有序集合中刪除所有包含給定成員的元素 |
常用作用於所有對象的命令
命令 | 作用 |
---|---|
TYPE | 查看鍵值對中值對象的類型 |
OBJECT ENCODING | 查看鍵值對中值對象的編碼類型 |
KEY + 模式 | 查找數據庫中滿足模式的KEY |
參考
- 《Redis設計與實現》
- Redis命令