一、hash 简介
Redis Hashes are maps between string fields and string values, so they are the perfect data type to represent objects (e.g. A User with a number of fields like name, surname, age, and so forth):
中文译文如下
redis hash 是 字符 filed 和 value 之间的映射表,所以非常适合用于存储对象(比如:一个用户有多个属性字段,如:名称,年龄等等。)
Hash 在存储对象值后的逻辑图如下:
这里是两个用户对象存储的值,左边的是 key ,右边的是 value ,而 value 是以映射的关系来进行存储。Hash 适合存储对象结构,或者数据库表的字段结构。
二、hash 简单命令操作
关于 hash 的操作命令,同样的在 参考文档中都能够获取到,这边演示几个操作命令。
单个属性的设置和获取,以及多个属性到的设值和获取。
Hset 和 Hget 单个属性操作
Hmset 和 Hmget 多个属性操作
三、hash 的编码格式
hash 在 redis 内部的编码格式
hash 内部有两种编码格式: ziplist和hashtable。
3.1、ziplist
翻到 ziplist的c源码文件(ziplist.c),来看一下官方的 doc 描述
中文译文大意如下
ziplist 是一种特殊编码的双链表,设计用于提高内存效率,不过内存利用率提高了,相关的查询操作速度自然会降低,因为多了编码解码的操作。可以说是,ziplist 是时间换空间的设计。
所以 ziplist 适用于key-value键值对的个数比较少的时候。
它存储字符串和整数值,其中整数被编码为实际的整数,而不是一系列字符。
push 和 pop 的复杂度是 O(1) 的,其他操作会根据具体使用的内存而使得其他操作的复杂度提升,比如说 HMset 批量操作,操作的属性越多,复杂度也就越高,n个属性,复杂度就是O(n)。
在 doc 文件中能够知道 ziplist 的抽象结构定义
结构定义的抽象图如下
ziplist 是以一种特殊编码进行存储的,所以redis并没有提供一个结构体来保存压缩列表的信息,而是提供了一组宏来定位每个成员,部分宏定义如下:
重点关注 ziplist entry 的宏定义,代码和 doc 注释如下
zlentry 结构体是用来管理节点信息的。
ziplist 是一个特殊编码的双向链表,它的特殊性就在于对节点进行了压缩处理。而压缩前后 zlentry 就是 entry 节点信息的一个承载对象,换句话说,在存储前 zilentry 用来表示当前数据的信息,在将数据信息读取解压后也是用的 zlentry 来存储表示。
来看一下从 ziplist 中获取 entry 是相关的解码操作。
hset 为入口(t_hash.c),设置单个hash的操作命令处理入口。
下面是一张简单的时序图
hashTypeSet 相关代码如下
查看 ziplistFind方法(ziplist.c 中),部分代码如下
查看其中一个解密操作 ZIP_DECODE_LENGTH,代码如下
3.2、hashtable
来看一下 hashtable,hashtable 结构图如下
Redis 中 hash表被称为字典 (dict),Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点保存了字典中的一个键值对
Redis中的哈希采用了典型的挂链解决冲突的方式,当有多个key-value键值对的键名key映射值相同时,系统会将这些键值value以单链表的形式保存,同时为了控制哈希表占用内存大小,Redis采用了双哈希表ht[2]结构,并逐步扩大哈希表容量的策略。
通常情况下只有 h[0] 被使用,当数据量多的时候会进行扩容,在 Redis 中存在扩容阈值 `dict_force_resize_ratio=5`