Redis 源码分析哈希对象(z_hash)

「这是我参与2022首次更文挑战的第38天,活动详情查看:2022首次更文挑战」。

数据结构

Redis 的 hash 对象采用了两种方式来实现,前面分析过连续内存和非连续内存的优缺点,这里 hash 列表也折中了两种情况。

两种存储结构(代码位置 src/t_hash.c):

image-20220220165117783.png ziplist 编码:

ziplist 编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入压缩列表表尾,然后在将保存了值的压缩列表表尾,因此:

1、保存了同一个键值对的两个节点总是紧挨在一起,保存键的节点在前面,保存值的节点在后面。

2、先添加到哈希对象的键值对会被放在压缩列表的表头方向,而后添加到哈希对象中的键值对会被放在压缩列表的表尾方向。

举个例子:

127.0.0.1:6379> hset profile name "Tom"
(integer) 1
127.0.0.1:6379> hset profile age 25
(integer) 1
127.0.0.1:6379> hset profile career "Programmer"
(integer) 1
127.0.0.1:6379> object encoding profile
"ziplist"
复制代码

数据结构如下:

image-20220224001946051.png

压缩列表底层实现(profile key 结构):

image-20220224002902505.png

hashtable 编码:

image-20220224003447749.png

编码转换:

同时满足一下两个条件的时候选择 ziplist 编码:

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64 字节;
  • 哈希对象保存的简直对数量小于 512 个;不满足这两个条件的哈希对象需要使用 hashtable 编码(可以通过 hash-max-ziplisthash-maxziplist-value 修改)。

举个例子(profile key 结构)::

127.0.0.1:6379> hset profile desc "Programmer 11111112121v121kl lldklakdkalgam fsfdslkgkskgsklgklsklgklsklgsdkgkskgdsklmvm,,vm,vm,,maafaklglkaklsfakslkf"
(integer) 1
127.0.0.1:6379> object encoding profile
"hashtable"
复制代码

注意点

  • 由 ziplist 转换为 dict 的操作是不可逆的
  • 尽可能的使用 ziplist 来作为 hash 底层实现。长度尽可能控制在 1000 以内,否则由于存取操作复杂度 O(n) , 长列表会导致 CPU 消耗亚种,对象也不要太大
  • 两个参数 hash-max-ziplisthash-maxziplist-value 可在配置文件中修改
  • ziplist 底层存储对象时, 查找时间负载度为 O(n)

使用场景

  • 存储对象(定长或者不定长字符串)

    set user:1:name Mack
    set user:1:age 23
    set user:1:city shanghai
    复制代码
  • 序列化字符串(结合 pb 一起使用,最为广泛)

    set user:1 serialize(userinfo)
    复制代码
    • 优点:简单直观,每个属性都支持单独更新
    • 缺点:占用过多的键,内存占用量大,同时用户信息内聚性差,盛昌环境比较少使用
  • 哈希类型

    hmset user:1 name tom age 23 city shanghai
    复制代码
    • 优点:简化编程,合理运用序列化,可以提高内存的使用效率。
    • 缺点:要控制在 ziplist 和 hashtable 两种内部编码的转换, hashtable 会消耗更多的内存

常见操作

image.png

参考资料

  • 《Redis 设计与实现》黄健宏

猜你喜欢

转载自juejin.im/post/7067946313735733284