深入剖析Redis系列(六) - Redis資料結構之雜湊

零壹技術棧發表於2018-10-14

前言

大部分程式語言都提供了 雜湊hash)型別,它們的叫法可能是 雜湊字典關聯陣列。在 Redis 中,雜湊型別 是指鍵值本身又是一個 鍵值對結構

深入剖析Redis系列(六) - Redis資料結構之雜湊

雜湊 形如 value={ {field1,value1},...{fieldN,valueN} }Redis 鍵值對雜湊型別 二者的關係如圖所示:

深入剖析Redis系列(六) - 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 命令,它們的關係就像 setsetnx 命令一樣,只不過 作用域 變為 field

1.1.2. 獲取值

hget key field

下面操作用於獲取 user:1name 域(屬性) 對應的值。

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:13field

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 ...]

hmsethmget 分別是 批量設定獲取 field-valuehmset 需要的引數是 key多對 field-valuehmget 需要的引數是 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

hincrbyhincrbyfloat,就像 incrbyincrbyfloat 命令一樣,但是它們的 作用域field

1.2.2. 計算value的字串長度

hstrlen key field

例如 hget user:1 namevaluetom,那麼 hstrlen 的返回結果是 3

127.0.0.1:6379> hstrlen user:1 name
(integer) 3
複製程式碼

下面是 雜湊型別命令時間複雜度,開發人員可以參考此表選擇適合的命令。

深入剖析Redis系列(六) - Redis資料結構之雜湊

深入剖析Redis系列(六) - Redis資料結構之雜湊

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系列(六) - Redis資料結構之雜湊

使用 Redis 雜湊結構 儲存 使用者資訊 的示意圖如下:

深入剖析Redis系列(六) - 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系列(六) - Redis資料結構之雜湊

  • 關係型資料庫 可以做複雜的 關係查詢,而使用 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
複製程式碼
  • 優點簡單直觀,如果使用合理可以 減少記憶體空間 的使用。

  • 缺點:要控制和減少 雜湊ziplisthashtable 兩種 內部編碼轉換hashtable 會消耗 更多記憶體

小結

本文介紹了 Redis 中的 雜湊結構 的 一些 基本命令內部編碼適用場景。最後對比了 關係型表雜湊結構 的區別,以及幾種 儲存方式 的優缺點。

參考

《Redis 開發與運維》


歡迎關注技術公眾號: 零壹技術棧

零壹技術棧

本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。

相關文章