Redis源码-Hash:Redis String与Hash的区别、Redis Hash存储原理、Redis Hash命令、 Redis Hash存储底层编码、Redis Hash应用场景
用Redis存储一张表的数据,怎么存?
使用Redis存储一张表的数据,比如下表student:
id | name | age |
---|---|---|
12 | aaa | 25 |
13 | bbb | 36 |
你使用String数据结构也可以实现:
key特殊处理一下(key前缀+“自定义分隔符”+“表的唯一键约束”+“自定义分隔符”+属性名),然后获取值使用mget来批量获取属性
# 自定义分隔符_:key名(student_12_id:key前缀+"自定义分隔符"+"表的唯一键约束"+"自定义分隔符"+属性名)
127.0.0.1:6379> mset student_12_id 12 student_12_name aaa student_12_age 25
OK
127.0.0.1:6379> mget student_12_id student_12_name student_12_age
1) "12"
2) "aaa"
3) "25"
那么如果这样做很好,还要Hash干嘛?
String这样做,key已经占了很大的存储空间(真实场景你拼接的这个key很长),还有value,有点得不偿失,于是Redis提供了存储表格式的数据类型Hash。
Hash 哈希
存储多个无序的键值对,最大存储数量2^32-1(40亿个左右)
Redis本身就是KV存储的,这个存储方式是Redis外层的Hash,那么Hash数据结构存储KV结构的数据,这个叫做Redis内层的Hash。
内层的Hash中的value是不能够再次嵌套其它的数据类型(比如list、set、hash等),它的value只能是String。
String与Hash的区别
Hash特点:
- 节省内存空间: 因为只存一个key
- 减少key冲突: 因为只存一个key
- 取值减少性能消耗: 一次性就可以取出所有属性,不需要用mget命令,或者get向Redis服务端多次交互进行取值
Hash不适合的场景:
- Field不能单独设置过期时间: String可以单独设置过期时间
- 需要考虑数据量分布的问题: 内容是不能拆分的,大数据量的key会使Redis多个节点的内存分布不均衡。比如一个key有10亿个Field,但是一个key只能存储在Redis的一个服务节点上,你想拆成两个5亿的,Hash做不到;而String每个key是单独存储,因此可以更方便的拆分,所以如果一个key的数据量很大,可以考虑用String来接
1.Hash存储原理
首先需要搞明白Redis外层的Hash表是什么。
Redis中的Hash结构,在Redis中,所有的KV存储方式都是基于dict(外层Hash表,HashTable)这个结构去存存储的,那么如果value的编码类型也是hashtable,应该怎么存储,其实就是hashtable的嵌套结构。
不理解dict结构和什么是外层Hash可以去看:
Redis源码:Redis源码怎么查看、Redis源码查看顺序、Redis外部数据结构到Redis内部数据结构查看源码顺序、Redis KV存储方式都是基于dict
2.Redis-Hash数据类型:操作命令
与String的区别是前面加了h,get hget
新增修改key,单个KV子数据
# hset key field value:新增修改key值:hset key名 属性名(你操作的KV数据中的key) value(你操作的KV数据中的value)
# 返回值:新增key返回1,修改key返回0
127.0.0.1:6379> hset stu2 id 55
(integer) 1
# hget key field:获得key值:hget key名 属性名(你操作的KV数据中的key)
127.0.0.1:6379> hget stu2 id
"55"
返回key的所有K
# hkeys key:返回key的所有K
127.0.0.1:6379> hkeys stu2
1) "id"
2) "name"
3) "age"
4) "size"
返回key的所有V
# hvals key:返回key的所有V
127.0.0.1:6379> hvals stu2
1) "66"
2) "qiuqiu"
3) "33"
4) "36D"
返回key的所有KV
# hgetall key:返回key的所有KV
127.0.0.1:6379> hgetall stu2
1) "id"
2) "66"
3) "name"
4) "qiuqiu"
5) "age"
6) "33"
7) "size"
8) "36D"
删除key的KV
# hdel key field [field ...]:删除key的一个KV,field位K,可是多个,后面拼接即可
127.0.0.1:6379> hdel stu2 age
(integer) 1
查看key的KV长度
# hlen key:查看key的KV长度
127.0.0.1:6379> hlen stu2
(integer) 3
新增修改key,多个KV子数据
# hmset key field value [field value ...]:后面拼接多个KV即可,成功返回ok
127.0.0.1:6379> 127.0.0.1:6379> hmset stu2 id 66 name qiuqiu age 33 size 36D
OK
# hmget key field [field ...]:后面拼接多个K即可
127.0.0.1:6379> hmget stu2 id name age size
1) "66"
2) "qiuqiu"
3) "33"
4) "36D"
删除key,任意数据类型key通用的命令
# del key [key ...]
del mykey1 mykey2(支持删除多个,返回删除的个数,0,1,2…)
查看Redis内部的数据结构类型
当你插入Redis一个数据类型时,Redis总是会将value指向RedisObject,根据你插入的值,Redis再进行内部的数据转换。
可以先看本文目录:3.存储原理(底层编码)
可以用object encoding key名查看key的value再Redis的内部数据类型。
# 查看key在Redis的对外数据类型
127.0.0.1:6379> type stu2
hash
# 查看key在Redis的对内数据类型
127.0.0.1:6379> object encoding stu2
"ziplist"
3.存储原理(底层编码)
Redis源码:Redis源码怎么查看入门、Redis外部数据结构到Redis内部数据结构查看源码顺序、Redis源码查看顺序
Redis Hash两种存储底层编码类型:ziplist、hashtable
Redis Hash类型的内部底层编码有两种:
根据存储内容的不同大小采取合适的编码,从而达到更好的效果
- ziplist,OBJ_ENCODING_ZIPLIST(压缩列表)
- hashtable,OBJ_ENCODING_HT(哈希表,和Redis外层的Hash是一个)
Redis Hash两种存储底层编码类型①:ziplist
ziplist是一个经过特殊编码的,由连续内存块组成的双向链表。
它不存储指向上一个链表节点和指向下一个链表节点的指针,而是存储上一个节点长度和当前节点长度。
ziplist.c源码文件:
ziplist.c源码文件在解压后的src目录下
ziplist是什么:特殊编码的,由连续内存块组成的双向链表
ziplist结构:ziplist.c文件
看它的注释,这个代表了它的数据结构组成。
# ziplist数据结构组成
<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
<zlbytes> :占用内存字节数
<zltail> :列表末尾偏移量
<zllen> :节点数
<entry> <entry> ... <entry>
<zlend> :标记末端
ziplist的几种编码方式
ziplist.c文件的zlentry的源码截取
/* 并不代表实际存储方式 */
typedef struct zlentry {
unsigned int prevrawlensize; /* Bytes used to encode the previous entry len*/
/* 存储上一个链表节点的长度值所需要的字节数*/
unsigned int prevrawlen; /* Previous entry len. */
/* 上一个链表节点占用的长度 */
unsigned int lensize; /* Bytes used to encode this entry type/len.
For example strings have a 1, 2 or 5 bytes
header. Integers always use a single byte.*/
/* 存储当前链表节点长度数值所需要的字节数 */
unsigned int len; /* Bytes used to represent the actual entry.
For strings this is just the string length
while for integers it is 1, 2, 3, 4, 8 or
0 (for 4 bit immediate) depending on the
number range. */
/* 当前链表节点占用的长度 */
unsigned int headersize; /* prevrawlensize + lensize. */
/* 当前链表节点的头部大小(prevrawlensize + lensize),即非数据域的大小 */
unsigned char encoding; /* Set to ZIP_STR_* or ZIP_INT_* depending on
the entry encoding. However for 4 bits
immediate integers this can assume a range
of values and must be range-checked. */
/* 编码方式 */
unsigned char *p; /* Pointer to the very start of the entry, that
is, this points to prev-entry-len field. */
/* 压缩链表以字符串的形式保存,该指针指向当前节点起始位置 */
} zlentry;
Hash数据类型的存储底层编码类型在什么时候选用ziplist
- 一个hash对象保存的field数量<512个
- 一个hash对象中所有的field和value的字符串长度都<64byte
以上条件,超过之后,使用hashtable编码类型存储。
Redis Hash两种存储底层编码类型②:hashtable
首先需要搞明白Redis外层的Hash表是什么。
Redis中的Hash结构,在Redis中,所有的KV存储方式都是基于dict(外层Hash表,HashTable)这个结构去存存储的,那么如果value的编码类型也是hashtable,应该怎么存储,其实就是hashtable的嵌套结构。
不理解dict结构、外层Hash、dict、dictht、dictEntry可以去看:
Redis源码:Redis源码怎么查看、Redis源码查看顺序、Redis外部数据结构到Redis内部数据结构查看源码顺序、Redis KV存储方式都是基于dict
Redis的KV存储方式-dict、dictht、dictEntry关系图
在Redis中,所有的KV存储方式都是基于dict(外层Hash表,HashTable)这个结构去存储的。
外层的KV存储的HashTable(dict),结构如下
Redis 底层编码类型hashtable嵌套结构图
总体存储结构为:【dictEntry有序链表】→【放在dictEntry数组里面】→【dictEntry数组放在dictht(哈希表ht[0])里面】→【dictht又放在dict结构里面】
Hash的扩容
扩容怎么扩容的?
判断的条件:
扩容比例(源码dict.c中定义):
static unsigned int dict_force_resize_ratio = 5; /*
dictEntry数组后面的dictEntry链表,
所有节点的链表平均值超过一定程度(会计算dictEntry链表长度和dictEntry的长度的比例,这个值为5),
也就表示hash碰撞很严重,就会触发扩容。
*/
dictEntry数组后面的dictEntry链表,所有节点的链表平均值超过一定程度(会计算dictEntry链表长度和dictEntry的长度的比例,源码中这个值为5),也就表示hash碰撞很严重,就会触发扩容。
扩容的量级是原dictht的2的n次方,扩容之后,会把原表的数据重新分配到新表(rehash,重新计算hash),然后用ht[0]标记为当前使用的是哪个表,清空原先的dictht来释放空间。
4.应用场景
String能做的,Hash都可以做,较常用的是存储对象类型的数据
用Hash实现购物车
key:用户id(一个用户一个购物车)
field(Hash的key):商品id(购物车有很多商品)
value:商品数量(每种商品数量不同)
商品数量+1:hincr
商品数量-1(hash里面没有String递减的decr操作,想实现递减要借助hincrby):hincrby key field -1
删除商品:hdel
全选商品:hgetall
购物车商品种类数:hlen
Redis各数据结构命令、源码分析、应用场景
Redis源码-String:Redis String命令、Redis String存储原理、Redis String三种编码类型、Redis字符串SDS源码解析、Redis String应用场景
Redis源码-Hash:Redis String与Hash的区别、Redis Hash存储原理、Redis Hash命令、 Redis Hash存储底层编码、Redis Hash应用场景
Redis源码-List:Redis List存储原理、Redis List命令、 Redis List存储底层编码quicklist、Redis List应用场景
Redis源码-Set:Redis Set存储原理、Redis Set命令、 Redis Set集合操作命令(差集、并集、交集)、Redis Set存储底层编码、Redis Set应用场景