数据结构
Redis 的 hash 对象采用了两种方式来实现,前面分析过连续内存和非连续内存的优缺点,这里 hash 列表也折中了两种情况。
两种存储结构(代码位置 src/t_hash.c):
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"
数据结构如下:
压缩列表底层实现(profile key 结构):
hashtable 编码:
编码转换:
同时满足一下两个条件的时候选择 ziplist 编码:
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64 字节;
- 哈希对象保存的简直对数量小于 512 个;不满足这两个条件的哈希对象需要使用 hashtable 编码(可以通过
hash-max-ziplist
和hash-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-ziplist
和hash-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 会消耗更多的内存
常见操作
参考资料
- 《Redis 设计与实现》黄健宏