了解Redis如何优化内存开销,总结、学习其思想。
Redis
官方文档对内存优化有专门讲解:Memory optimization。这也是本文主要参考的内容源。
当我们出方案,省内存成为第一要务时,本文就值得参考。
当然,这里的原理只能作为我们做设计权衡的一种参考,具体情况需要我们自己分析CPU
、内存、复杂度、运行效果。
小集合使用紧凑型存储编码
Redis
从2.2版本开始引入了这种策略:针对阈值之下的集合类型,使用紧凑型、连续内存存储的encoding
。
在 译—在Redis中存储亿万级的简单KV数据 中的案例就是用了这种策略。
相关阈值配置项:
|
|
hash
、zset
数据结构会使用ziplist
压缩内存,set
则使用intset
。
ziplist
核心是紧凑、连续内存存储,对CPU
缓存本地性更友好
intset
核心是只存整形数字,没有对象属性的开销特定数据结构详解,我们起别的文章说明
entries
表示集合内元素数量,value
则表示值的内存大小。在阈值内时,使用紧凑型的数据结构,仅需一点CPU
运算即可拿到对应数据,而内存占用则大大减少。
这里配置项如果调大,官方建议多做测试。
同时,SDS
数据结构内部也针对不同元素大小做了编码压缩的处理:
int == 只保存整形数字
:str 为整形数字时embstr == 连续内存
:str < 44byteraw == 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个一组来分区存储:
假设我们的数据是这样的:
|
|
对object:1234
操作时,对应的key
为object:12
,而field
为34
,value
不变。
也就是说:key
==object:12
时,field
应该包含了[01,99]
。
针对object:5
这种前缀小于100的情况,应用层可以特殊处理。
本质上,这里的抽象是做了归类、分区(sharding
)。效果上,与上一条一致,通过共用key
节省内存占用。
从绝对的时间复杂度上,这里需要一些CPU
运算,但是均摊复杂度仍然与hash
的O(1)
一致,同时紧凑型连续内存存储,对CPU
缓存更友好。
共用
key
没有作为全局的通用设计,原因是Redis
需要支持缓存过期等策略,共用key
不好处理这些情况。而例如在Redis
使用整形数字,类似于JDK
中的Integer
,Redis
也会对数字进行缓存共享。
关于内存分配
Redis
会根据maxmemory
阈值配置来分配最大内存,如果没有设置,可能会有内存耗尽的风险。生产环境官方建议配置maxmemory
。
内存惰性释放
常驻内存占用取决于实例峰值内存,也就是说申请Redis
内存资源时,需评估峰值用量。
Redis
同时也会记录有效数据的内存大小。
即使有些key
过期或者被删除了,Redis
也不会主动释放内存,这点由malloc()
实现决定。
Resident Set Size RSS
:进程占用的内存页数
重用内存页
当添加新的KV
时,可以观察到RSS
内存页数是稳定的,这是因为内存分配会重用之前已经free
的内存页。
内存碎片率不准
碎片率 = 进程峰值占用的内存页数 / 当前使用的内存
基于上述逻辑,可得,内存碎片率是不准的。
当峰值内存占用很大时,RSS
很大,而当很多内存实际已经被删除后(未真正释放),当前使用内存很小,RSS/当前使用内存
会比较大。并不能完全体现内存真实的碎片状态。
小结
综上,Redis
优化内存占用的策略有:
- 小集合使用紧凑型存储编码
- 使用32位机器
- 位操作
- 用
hash
结构替代字符串
同时,也简单了解了操作系统内存占用、释放、碎片率的基本逻辑,有助于我们理解Redis
内存使用的真实情况。