大部分編程語言都提供了 哈希(hash
)類型,它們的叫法多是 哈希、字典、關聯數組。在 Redis
中,哈希類型 是指鍵值自己又是一個 鍵值對結構。java
哈希 形如 value={ {field1,value1},...{fieldN,valueN} }
,Redis
鍵值對 和 哈希類型 兩者的關係如圖所示:mysql
哈希類型中的 映射關係 叫做
field-value
,這裏的value
是指field
對應的 值,不是 鍵 對應的值。redis
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
。
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)
複製代碼
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
複製代碼
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
複製代碼
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"
複製代碼
hexists key field
例如 user:1
包含 name
域,因此返回結果爲 1
,不包含時返回 0
:
127.0.0.1:6379> hexists user:1 name
(integer) 1
複製代碼
hkeys key
hkeys
命令應該叫 hfields
更爲恰當,它返回指定 哈希鍵 全部的 field
,例如:
127.0.0.1:6379> hkeys user:1
1) "name"
2) "age"
3) "city"
複製代碼
hvals key
下面操做獲取 user:1
的所有 value
:
127.0.0.1:6379> hvals user:1
1) "tom"
2) "12"
3) "chengdu"
複製代碼
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
命令,該命令會 漸進式遍歷 哈希類型。
hincrby key field hincrbyfloat key field
hincrby
和 hincrbyfloat
,就像 incrby
和 incrbyfloat
命令同樣,可是它們的 做用域 是 field
。
hstrlen key field
例如 hget user:1 name
的 value
是 tom
,那麼 hstrlen
的返回結果是 3
。
127.0.0.1:6379> hstrlen user:1 name
(integer) 3
複製代碼
下面是 哈希類型命令 的 時間複雜度,開發人員能夠參考此表選擇適合的命令。
哈希類型 的 內部編碼 有兩種:
當 哈希類型 元素個數 小於 hash-max-ziplist-entries
配置(默認 512
個)、同時 全部值 都 小於 hash-max-ziplist-value
配置(默認 64
字節)時,Redis
會使用 ziplist
做爲 哈希 的 內部實現,ziplist
使用更加 緊湊的結構 實現多個元素的 連續存儲,因此在 節省內存 方面比 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"
複製代碼
如圖所示,爲 關係型數據表 的兩條 用戶信息,用戶的屬性做爲表的列,每條用戶信息做爲行。
使用 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;
}
複製代碼
須要注意的是 哈希類型 和 關係型數據庫 有兩點不一樣之處:
field
,而 關係型數據庫 一旦添加新的 列,全部行 都要爲其 設置值(即便爲 NULL
),如圖所示:Redis
去模擬關係型複雜查詢 開發困難,維護成本高。到目前爲止,咱們已經可以用 三種方法 緩存 用戶信息,下面給出三種方案的 實現方法 和 優缺點分析。
給用戶信息的每個屬性分配 一個鍵。
set user:1:name tom
set user:1:age 23
set user:1:city beijing
複製代碼
優勢:簡單直觀,每一個屬性都支持 更新操做。
缺點:佔用 過多的鍵,內存佔用量 較大,同時用戶信息 內聚性比較差,因此此種方案通常不會在生產環境使用。
將用戶信息 序列化 後用 一個鍵 保存。
set user:1 serialize(userInfo)
複製代碼
優勢:簡化編程,若是合理的使用 序列化 能夠 提升內存利用率。
缺點:序列化 和 反序列化 有必定的開銷,同時每次 更新屬性 都須要把 所有數據 取出進行 反序列化,更新後 再 序列化 到 Redis
中。
每一個用戶屬性使用 一對 field-value
,可是隻用 一個鍵 保存。
hmset user:1 name tom age 23 city beijing
複製代碼
優勢:簡單直觀,若是使用合理能夠 減小內存空間 的使用。
缺點:要控制和減小 哈希 在 ziplist
和 hashtable
兩種 內部編碼 的 轉換,hashtable
會消耗 更多內存。
本文介紹了 Redis
中的 哈希結構 的 一些 基本命令、內部編碼 和 適用場景。最後對比了 關係型表 和 哈希結構 的區別,以及幾種 存儲方式 的優缺點。
《Redis 開發與運維》
歡迎關注技術公衆號: 零壹技術棧
本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。