前言
大部分程式語言都提供了 雜湊(hash
)型別,它們的叫法可能是 雜湊、字典、關聯陣列。在 Redis
中,雜湊型別 是指鍵值本身又是一個 鍵值對結構。
雜湊 形如 value={ {field1,value1},...{fieldN,valueN} }
,Redis
鍵值對 和 雜湊型別 二者的關係如圖所示:
雜湊型別中的 對映關係 叫作
field-value
,這裡的value
是指field
對應的 值,不是 鍵 對應的值。
其他文章
正文
1. 相關命令
1.1. 基本命令
1.1.1. 設定值
hset key field value
下面為 user:1
新增一對 field-value
,如果設定成功會返回 1
,反之會返回 0
。
127.0.0.1:6379> hset user:1 name tom
(integer) 1
複製程式碼
此外 Redis
提供了 hsetnx
命令,它們的關係就像 set
和 setnx
命令一樣,只不過 作用域 由 鍵 變為 field
。
1.1.2. 獲取值
hget key field
下面操作用於獲取 user:1
的 name
域(屬性) 對應的值。
127.0.0.1:6379> hget user:1 name
"tom"
複製程式碼
如果 鍵 或 field
不存在,會返回 nil
:
127.0.0.1:6379> hget user:2 name
(nil)
127.0.0.1:6379> hget user:1 age
(nil)
複製程式碼
1.1.3. 刪除field
hdel key field [field ...]
hdel
會刪除 一個或多個 field
,返回結果為 成功刪除 field
的個數,例如:
127.0.0.1:6379> hdel user:1 name
(integer) 1
127.0.0.1:6379> hdel user:1 age
(integer) 0
複製程式碼
1.1.4. 計算field個數
hlen key
例如鍵 user:1
有 3
個 field
:
127.0.0.1:6379> hset user:1 name tom
(integer) 1
127.0.0.1:6379> hset user:1 age 23
(integer) 1
127.0.0.1:6379> hset user:1 city chengdu
(integer) 1
127.0.0.1:6379> hlen user:1
(integer) 3
複製程式碼
1.1.5. 批量設定或獲取field-value
hmget key field [field ...] hmset key field value [field value ...]
hmset
和 hmget
分別是 批量設定 和 獲取 field-value
,hmset
需要的引數是 key
和 多對 field-value
,hmget
需要的引數是 key
和 多個 field
。例如:
127.0.0.1:6379> hmset user:1 name tom age 12 city chengdu
OK
127.0.0.1:6379> hmget user:1 name city
1) "tom"
2) "chengdu"
複製程式碼
1.1.6. 判斷field是否存在
hexists key field
例如 user:1
包含 name
域,所以返回結果為 1
,不包含時返回 0
:
127.0.0.1:6379> hexists user:1 name
(integer) 1
複製程式碼
1.1.7. 獲取所有field
hkeys key
hkeys
命令應該叫 hfields
更為恰當,它返回指定 雜湊鍵 所有的 field
,例如:
127.0.0.1:6379> hkeys user:1
1) "name"
2) "age"
3) "city"
複製程式碼
1.1.8. 獲取所有value
hvals key
下面操作獲取 user:1
的全部 value
:
127.0.0.1:6379> hvals user:1
1) "tom"
2) "12"
3) "chengdu"
複製程式碼
1.1.9. 獲取所有的field-value
hgetall key
下面操作獲取 user:1
所有的 field-value
:
127.0.0.1:6379> hgetall user:1
1) "name"
2) "tom"
3) "age"
4) "12"
5) "city"
6) "chengdu"
複製程式碼
在使用
hgetall
時,如果 雜湊元素 個數比較多,會存在 阻塞Redis
的可能。如果開發人員只需要獲取 部分field
,可以使用hmget
,如果一定要獲取 全部field-value
,可以使用hscan
命令,該命令會 漸進式遍歷 雜湊型別。
1.2. 不常用命令
1.2.1. 鍵值自增
hincrby key field hincrbyfloat key field
hincrby
和 hincrbyfloat
,就像 incrby
和 incrbyfloat
命令一樣,但是它們的 作用域 是 field
。
1.2.2. 計算value的字串長度
hstrlen key field
例如 hget user:1 name
的 value
是 tom
,那麼 hstrlen
的返回結果是 3
。
127.0.0.1:6379> hstrlen user:1 name
(integer) 3
複製程式碼
下面是 雜湊型別命令 的 時間複雜度,開發人員可以參考此表選擇適合的命令。
2. 內部編碼
雜湊型別 的 內部編碼 有兩種:
2.1. ziplist(壓縮列表)
當 雜湊型別 元素個數 小於 hash-max-ziplist-entries
配置(預設 512
個)、同時 所有值 都 小於 hash-max-ziplist-value
配置(預設 64
位元組)時,Redis
會使用 ziplist
作為 雜湊 的 內部實現,ziplist
使用更加 緊湊的結構 實現多個元素的 連續儲存,所以在 節省記憶體 方面比 hashtable
更加優秀。
2.2. hashtable(雜湊表)
當 雜湊型別 無法滿足 ziplist
的條件時,Redis
會使用 hashtable
作為 雜湊 的 內部實現,因為此時 ziplist
的 讀寫效率 會下降,而 hashtable
的讀寫 時間複雜度 為 O(1)
。
下面的示例演示了 雜湊型別 的 內部編碼,以及相應的變化。
當 field
個數 比較少,且沒有大的 value
時,內部編碼 為 ziplist
:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
OK
127.0.0.1:6379> object encoding hashkey
"ziplist"
複製程式碼
- 當有
value
大於64
位元組時,內部編碼 會由ziplist
變為hashtable
:
127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64 byte...忽略..."
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
複製程式碼
- 當
field
個數 超過512
,內部編碼 也會由ziplist
變為hashtable
:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2 f3 v3 ... f513 v513
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
複製程式碼
3. 適用場景
如圖所示,為 關係型資料表 的兩條 使用者資訊,使用者的屬性作為表的列,每條使用者資訊作為行。
使用 Redis
雜湊結構 儲存 使用者資訊 的示意圖如下:
相比於使用 字串序列化 快取 使用者資訊,雜湊型別 變得更加 直觀,並且在 更新操作 上會 更加便捷。可以將每個使用者的 id
定義為 鍵字尾,多對 field-value
對應每個使用者的 屬性,類似如下虛擬碼:
public UserInfo getUserInfo(long id) {
// 使用者id作為key字尾
String userRedisKey = "user:info:" + id;
// 使用hgetall獲取所有使用者資訊對映關係
Object userInfoMap = redis.hgetAll(userRedisKey);
UserInfo userInfo;
if (userInfoMap != null) {
// 將對映關係轉換為UserInfo
userInfo = transferMapToUserInfo(userInfoMap);
} else {
// 從MySQL中獲取使用者資訊
userInfo = mysql.get(id);
// 將userInfo變為對映關係使用hmset儲存到Redis中
redis.hmset(userRedisKey, transferUserInfoToMap(userInfo));
// 新增過期時間
redis.expire(userRedisKey, 3600);
}
return userInfo;
}
複製程式碼
3.1. 雜湊結構與關係型表
需要注意的是 雜湊型別 和 關係型資料庫 有兩點不同之處:
- 雜湊型別 是 稀疏的,而 關係型資料庫 是 完全結構化的,例如 雜湊型別 每個 鍵 可以有不同的
field
,而 關係型資料庫 一旦新增新的 列,所有行 都要為其 設定值(即使為NULL
),如圖所示:
- 關係型資料庫 可以做複雜的 關係查詢,而使用
Redis
去模擬關係型複雜查詢 開發困難,維護成本高。
3.2. 幾種快取方式
到目前為止,我們已經能夠用 三種方法 快取 使用者資訊,下面給出三種方案的 實現方法 和 優缺點分析。
3.2.1. 原生字串型別
給使用者資訊的每一個屬性分配 一個鍵。
set user:1:name tom
set user:1:age 23
set user:1:city beijing
複製程式碼
-
優點:簡單直觀,每個屬性都支援 更新操作。
-
缺點:佔用 過多的鍵,記憶體佔用量 較大,同時使用者資訊 內聚性比較差,所以此種方案一般不會在生產環境使用。
3.2.2. 序列化字串型別
將使用者資訊 序列化 後用 一個鍵 儲存。
set user:1 serialize(userInfo)
複製程式碼
-
優點:簡化程式設計,如果合理的使用 序列化 可以 提高記憶體利用率。
-
缺點:序列化 和 反序列化 有一定的開銷,同時每次 更新屬性 都需要把 全部資料 取出進行 反序列化,更新後 再 序列化 到
Redis
中。
3.2.3. 雜湊型別
每個使用者屬性使用 一對 field-value
,但是隻用 一個鍵 儲存。
hmset user:1 name tom age 23 city beijing
複製程式碼
-
優點:簡單直觀,如果使用合理可以 減少記憶體空間 的使用。
-
缺點:要控制和減少 雜湊 在
ziplist
和hashtable
兩種 內部編碼 的 轉換,hashtable
會消耗 更多記憶體。
小結
本文介紹了 Redis
中的 雜湊結構 的 一些 基本命令、內部編碼 和 適用場景。最後對比了 關係型表 和 雜湊結構 的區別,以及幾種 儲存方式 的優缺點。
參考
《Redis 開發與運維》
歡迎關注技術公眾號: 零壹技術棧
本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。