Redis内存优化细节

每一片条凋零的落叶,每一个残破的鸟窝,每一寸苍白的头发.... 都是为了生命的延续。

本文就是接着上文的延续:

罗政:Redis 内存全面详解

一. 从编码上来优化内存

几种redis数据编码介绍(不全介绍)

1. embstr

172.29.2.12:7000> set a 11111111222222333331212222233333333332333333
OK
172.29.2.12:7000> OBJECT encoding a
"embstr"

上面 设置a的value长度为44个字节。发现采用编码为embstr,小于44也是embstr 。

embstr编码是专门用于保存 短字符串 的一种优化编码方式,跟正常的字符编码相比,字符编码会调用两次内存分配函数来分别创建redisObject和sdshdr结构,而embstr编码则通过调用 一次内存分配函数来分配一块连续的空间 ,空间中一次包含redisObject和sdshdr两个结构。

通俗易懂的讲法

因为redis是按页分配的,44以内加上额外的指针开销可能 就是一页内存, 大于44的话加上额外指针开销 可能大于一页内存了。

2. raw

当大于44后采用raw 编码

172.29.2.12:7000> set a 111111112222223333312122222333333333323333333
OK
172.29.2.12:7000> OBJECT encoding a
"raw"

raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,就是普通的简单字符串(SDS)。

总结:embstr ,raw 都是 string类型编码。(set key 12 为 INI编码)

3. ziplist(压缩列表)

我们先看下 ziplist 的配置

172.29.2.12:7000> config get *ziplist*
 1) "hash-max-ziplist-entries"
 2) "512"
 3) "hash-max-ziplist-value"
 4) "64"
 5) "list-max-ziplist-size"
 6) "-2"
 7) "zset-max-ziplist-entries"
 8) "128"
 9) "zset-max-ziplist-value"
10) "64"
  • -entries表示ziplsit对多保存数据项的个数,超出之后不能再使用ziplist
  • -value表示每个数据项的最大字节数,超出之后不能再使用ziplist

我们发现key的长度超过64字节后就不是ziplist,于是我们这样做:

172.29.2.12:7000> hset ad aa d
(integer) 1
172.29.2.12:7000> OBJECT encoding ad
"ziplist"

压缩列表的原理就是按照一定的编码规则存储在一块连续内存里, 很节约内存 ,但是修改代价大,容易引起内存重新分配。

压缩列表可以 O(1) 的时间复杂度访问 首尾元素 。因为 entry 长度不确定,可以向前或向后顺序访问,不能随机访问。因为级联更新的现象的存在,添加、修改、删除元素操作的复杂度在 O(n) 到 O(n^2) 之间。

4. hashtable

当字节数超过64后(或者键与值对数据小于512个)

172.29.2.12:7000> hset ad aa dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa22222222222222222222222222222222222222222222222222222222222222222eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
172.29.2.12:7000> OBJECT encoding ad
"hashtable"

ziplist 就转换成 hashtable编码 存储了。hashtable就类似HashMap , 总所周知特点:高效查找,时间复杂度O(1),但是内存使用量比ziplist大很多。

hashtable 与 ziplist 比较

  • ziplist会比hashtable节省更多的内存。ziplist本质上就是以时间换空间的一种优化(但是时间消耗的也不大)。
  • 当ziplist的长度比较小的时候,从ziplist读写数据的效率比hashtable的差异并不大。

阶段总结

  • 尽量使redis采用embstr 编码字符串。
  • 尽量用hash数据结构代替string。
  • 尽量在hash结构中,保持ziplist编码 。
  • 如果value比较大,建议自行压缩下。


二. 从数据结构上优化

这里举个例子,有个需求如下:

一个dev-id(设备id)对应 一个用户id(uid),格式如下:
dev-id:2379889  , uid:67567
我们现在有三亿个这样的不同的设备id需要存储,都是通过设备id来查找,如何通过redis实现?

传统的方式:

set 2379889  67567
## 取值
get 2379889

我们计算下这样的key 到3亿后需要花多少内存

这个计算内存的网址给大家分享下

Redis容量预估-极数云舟

没错我们花了26G ,但是你老板又没钱加机器,这时你只能提出辞职,但是又舍不得前台的妹妹(我的其它文章已经详解描述过前台妹妹,感兴趣的可以了解下),于是我只能这样想:

上面我们知道hash的ziplist 结构会压缩内存,于是我们可以想到以这种方式存储,我们以设备id的前4位作为hash的key ,于是这样存储:

>>hset 2379 889 67567
##hget时,把设备id分为2段: 2379 889
>>hget 2379 889

由于是是前4位作为key,只留后3位不同,所以hash的个数最大就是999,于是我们设置:

hash-max-ziplist-entries 1000

这样当我们hash里的个数没有超过1000时,就使用ziplist 。

测试后,内存使用量是5GB左右。读者可以自行测试。注意一定要看编码是不是使用的ziplist。

那为什么是1000呢?因为前文已经提到过,如果数量太多会导致查询效率低下,且消耗的cpu特别大。


三. Redis的管道技术

之所以介绍这个,是因为实在是词穷了。

什么是redis的管道技术?

客户端发出一条命令,然后服务器收到,处理然后返回给客户端。但是如果有一批这样的命令要处理,且不要求及时性 ,就会消耗很多网络资源 。

为了解决这种问题,Redis在很早就支持了管道技术。也就是说客户端可以一次发送多条命令,不用逐条等待命令的返回值,而是到最后一起读取返回结果,这样只需要一次网络开销,速度就会得到明显的提升。

Jedis怎么使用管道,读者可以自行搜索。毕竟这个使用场景较少。

四. 一些我都不想讲的内存优化

  • redis必须保存近期,有可能被大量访问的数据,那些千万年前的数据请不要放到redis了。
  • redis的key要设置过期时间,不然会残留很多你不知道的key。


强烈推荐一个 进阶 JAVA架构师 的博客


Java架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者