了解Redis如何优化内存开销,总结、学习其思想。

Redis官方文档对内存优化有专门讲解:Memory optimization。这也是本文主要参考的内容源。

当我们出方案,省内存成为第一要务时,本文就值得参考。

当然,这里的原理只能作为我们做设计权衡的一种参考,具体情况需要我们自己分析CPU、内存、复杂度、运行效果。

小集合使用紧凑型存储编码

Redis从2.2版本开始引入了这种策略:针对阈值之下的集合类型,使用紧凑型、连续内存存储的encoding

译—在Redis中存储亿万级的简单KV数据 中的案例就是用了这种策略。

相关阈值配置项:

1
2
3
4
5
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
set-max-intset-entries 512

hashzset数据结构会使用ziplist压缩内存,set则使用intset

ziplist核心是紧凑、连续内存存储,对CPU缓存本地性更友好

intset核心是只存整形数字,没有对象属性的开销

特定数据结构详解,我们起别的文章说明

entries表示集合内元素数量,value则表示值的内存大小。在阈值内时,使用紧凑型的数据结构,仅需一点CPU运算即可拿到对应数据,而内存占用则大大减少。

这里配置项如果调大,官方建议多做测试。

同时,SDS数据结构内部也针对不同元素大小做了编码压缩的处理:

  • int == 只保存整形数字:str 为整形数字时
  • embstr == 连续内存:str < 44byte
  • raw == SDS:44byte < str < 512 MB

使用32位机器

ES为什么建议使用32或者26GB的堆? 中关于指针压缩的说明与此处同理。

用32位机器来运行Redis,每个key的指针相比64位,会占用更少的内存。但是有总内存4GB的限制。

当然,如果Redis实现了类似于指针压缩的技术,理论上这里可以突破4GB的限制。

位操作

从2.2版本开始,Redis引入了位操作,原理可参考 BitMap结构实现及使用场景

位图非常适用于一些二值统计。其变体BloomFilter这种概率性结构也适用于数据判重。而完成巨量数据的操作,只需极小的内存占用。

用hash结构替代字符串

hash:

  • key
  • field
  • value

业务内聚性

我们用Redis很多时候直接会用字符串结构来存储value,但实际上有些KV从业务上来说就是面向对象的,有其内聚性。比如User信息,我们直接将User需要的KV都存到hash结构即可。

这样会共用key,也节省了内存占用。

面向省内存抽象建模

简单来说,除了上一条中的本身业务上就内聚、有关联的情况,我们可以考虑在应用层做一些抽象的设计。

这里抽象应该是指提取共性,共用hash中的key,以达到省内存的目的

官方文档举了个例子,效果上很像为数据做了一层分区,例子对数据按100个一组来分区存储:

假设我们的数据是这样的:

1
2
3
object:102393
object:1234
object:5

object:1234操作时,对应的keyobject:12,而field34value不变。

也就是说:key==object:12时,field应该包含了[01,99]

针对object:5这种前缀小于100的情况,应用层可以特殊处理。

本质上,这里的抽象是做了归类、分区(sharding)。效果上,与上一条一致,通过共用key节省内存占用。

从绝对的时间复杂度上,这里需要一些CPU运算,但是均摊复杂度仍然与hashO(1)一致,同时紧凑型连续内存存储,对CPU缓存更友好。

共用key没有作为全局的通用设计,原因是Redis需要支持缓存过期等策略,共用key不好处理这些情况。而例如在Redis使用整形数字,类似于JDK中的IntegerRedis也会对数字进行缓存共享。

关于内存分配

Redis会根据maxmemory阈值配置来分配最大内存,如果没有设置,可能会有内存耗尽的风险。生产环境官方建议配置maxmemory

内存惰性释放

常驻内存占用取决于实例峰值内存,也就是说申请Redis内存资源时,需评估峰值用量。

Redis同时也会记录有效数据的内存大小。

即使有些key过期或者被删除了,Redis也不会主动释放内存,这点由malloc()实现决定。

Resident Set Size RSS:进程占用的内存页数

重用内存页

当添加新的KV时,可以观察到RSS内存页数是稳定的,这是因为内存分配会重用之前已经free的内存页。

内存碎片率不准

碎片率 = 进程峰值占用的内存页数 / 当前使用的内存

基于上述逻辑,可得,内存碎片率是不准的。

当峰值内存占用很大时,RSS很大,而当很多内存实际已经被删除后(未真正释放),当前使用内存很小,RSS/当前使用内存 会比较大。并不能完全体现内存真实的碎片状态。

小结

综上,Redis优化内存占用的策略有:

  • 小集合使用紧凑型存储编码
  • 使用32位机器
  • 位操作
  • hash结构替代字符串

同时,也简单了解了操作系统内存占用、释放、碎片率的基本逻辑,有助于我们理解Redis内存使用的真实情况。

Ref