Redis中的数据类型hash

         把string作为储存的对象,,除了记录实际数据,String 类型还需要额外的内存空间记录数据长度、空间使用等信息,这些信息也叫作元数据。

         当你保存 64 位有符号整数时,String 类型会把它保存为一个 8 字节的 Long 类型整数,这种保存方式通常也叫作 int 编码方式。但是当保存的数据中包含字符的时候,,String类型就会用简单动态字符串(Simple Dynamic String,SDS)结构来保存,包括:

  • buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个“\0”,这就会额外占用 1 个字节的开销。
  • len:占 4 个字节,表示 buf 的已用长度。
  • alloc:也占个 4 字节,表示 buf 的实际分配长度,一般大于 len。

除了SDS基本的额外字段外,因为 Redis 的数据类型有很多,而且,不同数据类型都有些相同的元数据要记录,所以,Redis 会用一个 RedisObject 结构体来统一记录这些元数据,同时指向实际数据。RedisObject包括:

  • 元数据:比如最后一次访问的时间、被引用的次数等。
  • 指针:指向具体的数据

其中对于数据来说

  • 当保存的是 Long 类型整数时,RedisObject 中的指针就直接赋值为整数数据了,这样就不用额外的指针再指向整数了,节省了指针的空间开销。这种布局方式被称为 int 编码模式。
  • 当保存的是字符串数据,并且字符串小于等于 44 字节时,RedisObject 中的元数据、指针和 SDS 是一块连续的内存区域,这样就可以避免内存碎片。
  • 当字符串大于 44 字节时,SDS 的数据量就开始变多了,Redis 就不再把 SDS 和RedisObject 布局在一起了,而是会给 SDS 分配独立的空间,并用指针指向 SDS 结构。这种布局方式被称为 raw 编码模式。

现在有一个key值是10位数的整数,value是log类型,可以直接用int编码模式,那么RedisObject的指针就会直接改成8字节的整数,加上元数据的8字节,再加上long的16字节,加起来是32字节。

此外,还有一个指针指向全局哈希表中的key、value以及下一个键值对,各占用8字节,此时就是56字节。而实际上占用了64字节,因为jemalloc 在分配内存时,会根据我们申请的字节数 N,找一个比 N 大,但是最接近 N 的2 的幂次数作为分配的空间,这样可以减少频繁分配的次数,这样就分配的2的6次方字节。

 

压缩列表

压缩列表表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量,以及列表中的 entry 个数。压缩列表尾还有一个 zlend,表示列表结束。压缩列表之所以能节省内存,就在于它是用一系列连续的 entry 保存数据。每个 entry 的元数据包括下面几部分。

  • prev_len,表示前一个 entry 的长度。prev_len 有两种取值情况:1 字节或 5 字节。取值 1 字节时,表示上一个 entry 的长度小于 254 字节。虽然 1 字节的值能表示的数值范围是 0 到 255,但是压缩列表中 zlend 的取值默认是 255,因此,就默认用 255表示整个压缩列表的结束,其他表示长度的地方就不能再用 255 这个值了。所以,当上一个 entry 长度小于 254 字节时,prev_len 取值为 1 字节,否则,就取值为 5 字节。
  • len:表示自身长度,4 字节;
  • encoding:表示编码方式,1 字节;
  • content:保存实际数据。

这些 entry 会挨个儿放置在内存中,不需要再用额外的指针进行连接,这样就可以节省指针所占用的空间。

现在我们看看上面的例子, prev_len一个字节,len占4字节,编码方式1字节,数据占8字节,总共14字节。

 

Hash 类型的二级编码方法

以上是单个键对应一个集合,如果是单键值对,那么可以用hash类型的二级编码方式。就是把一个单值的数据拆分成两部分,前一部分作为 Hash 集合的 key,后一部分作为Hash 集合的 value,这样一来,我们就可以把单值数据保存到 Hash 集合中了。

以key 1101000060 和value 3302000080 为例,我们可以把key 的前7 位(1101000)作为 Hash 类型的键,把key 的最后 3 位(060)和value 分别作为 Hash 类型值中的 key 和 value。这样子储存的占用内存只有16个字节。

 

二级编码中截取的ID长度

Redis Hash 类型的两种底层实现结构,分别是压缩列表和哈希表。Hash 类型设置了用压缩列表保存数据时的两个阈值,一旦超过了阈值,Hash 类型就会用哈希表来保存数据了。

这两个阈值分别对应以下两个配置项:

  • hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。
  • hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。

如果我们往 Hash 集合中写入的元素个数超过了 hash-max-ziplist-entries,或者写入的单个元素大小超过了 hash-max-ziplist-value,Redis 就会自动把 Hash 类型的实现结构由压缩列表转为哈希表。

一般压缩列表会比哈希更节省空间,所以按照刚刚的例子,最后 3 位作为 Hash 集合的 key,也就保证了 Hash 集合的元素个数不超过 1000。如果我们把hash-max-ziplist-entries设置为1000,那么就会用压缩列表来储存。

 

验证hash的两种底层数据结构

  • hash-max-ziplist-entries = 3
  • hash-max-ziplist-value = 10

表示:属性个数 <= 3 && 每个属性字符长度 <= 10 时使用 ziplist 编码格式存储,反之,使用 hashtable 编码格式进行存储。

两种配置修改的方式

方式一:在命令行使用 config set xxxx 的方式进行设置

修改后立即生效,不过修改的配置仅在当前实例进程中生效,重启失效,

1) "hash-max-ziplist-entries"

2) "3"

3) "hash-max-ziplist-value"

4) "64"

方式二:通过修改配置文件 redis.conf 中的配置参数

修改后,需要重启才会生效,不过配置持久化到配置文件中后,重启配置仍然生效

验证属性字符长度 > 10

插入一个用户,给他一个名称,字符长度不超过 10字节

127.0.0.1:6379> hset user:1 username hh

(integer) 1

127.0.0.1:6379> object encoding user:2

"ziplist

将 user:1 中的 username 变更为长度为10字节的字符串,如下

127.0.0.1:6379> hset user:1 username hhhhhhhhhh

(integer) 1

127.0.0.1:6379> HSTRLEN user:1 username

(integer) 10

127.0.0.1:6379> object encoding user:2

"ziplist"

将 user:1 中的 username 变更为长度为 11字节的字符串,如下

127.0.0.1:6379> hset user:1 username hhhhhhhhhhh

(integer) 1

127.0.0.1:6379> HSTRLEN user:1 username

(integer) 11

127.0.0.1:6379> object encoding user:2

"hashtable"

结论:当属性个数 <=3 但是存在一个属性字符长度大于 10 字节时,hash类型的编码格式为 hashtable

2.3、验证 ziplist -> hashtable 不可逆

在上面的试验中,得到的 user:1 只存在一个属性 username,且 username 是一个长度为 11 的字符串,所以此时 user:1 的编码格式为 hashtable。

此时,尝试将 username 改为长度小于等于 10 的字符串,来看看其编码格式:

127.0.0.1:6379> hset user:1 username hh

(integer) 0

127.0.0.1:6379> HSTRLEN user:1 username

(integer) 2

127.0.0.1:6379> OBJECT encoding user:1

"hashtable"

结论:ziplist->hashtable 的转化过程是不可逆的。

2.4、验证:属性值都是 <= 10 字节的字符串,属性个数 >3

插入用户 user:2 ,拥有 age,username,sex 且是哪个属性的字符长度 <= 10 ,如下

127.0.0.1:6379> hmset user:2 username yy age 18 sex unknown

OK

127.0.0.1:6379> OBJECT encoding user:2

"ziplist"

新增一个属性:weight=18

127.0.0.1:6379> hset user:2 weight 18

(integer) 1

127.0.0.1:6379> OBJECT encoding user:2

"hashtable"

结论:当属性个数大于 3 时,编码格式为 hashtable

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值