Redis源码-Hash:Redis String与Hash的区别、Redis Hash存储原理、Redis Hash命令、 Redis Hash存储底层编码、Redis Hash应用场景

用Redis存储一张表的数据,怎么存?

使用Redis存储一张表的数据,比如下表student:

idnameage
12aaa25
13bbb36

你使用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特点:

  1. 节省内存空间: 因为只存一个key
  2. 减少key冲突: 因为只存一个key
  3. 取值减少性能消耗: 一次性就可以取出所有属性,不需要用mget命令,或者get向Redis服务端多次交互进行取值

Hash不适合的场景:

  1. Field不能单独设置过期时间: String可以单独设置过期时间
  2. 需要考虑数据量分布的问题: 内容是不能拆分的,大数据量的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类型的内部底层编码有两种:

根据存储内容的不同大小采取合适的编码,从而达到更好的效果

  1. ziplist,OBJ_ENCODING_ZIPLIST(压缩列表)
  2. 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

  1. 一个hash对象保存的field数量<512个
  2. 一个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应用场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>