1.优化内存占用
了解redis的内存模型,对优化redis内存占用有很大帮助。下面介绍几种优化场景。
- 1)利用jemalloc特性进行优化
上一小节所讲述的90000个键值便是一个例子。由于jemalloc分配内存时数值是不连续的,因此key/value字符串变化一个字节,可能会引起占用内存很大的变动;在设计时可以利用这一点。
例如,如果key的长度如果是8个字节,则SDS为17字节,jemalloc分配32字节;此时将key长度缩减为7个字节,则SDS为16字节,jemalloc分配16字节;则每个key所占用的空间都可以缩小一半。
- 2)使用整型/长整型
如果是整型/长整型,Redis会使用int类型(8字节)存储来代替字符串,可以节省更多空间。因此在可以使用长整型/整型代替字符串的场景下,尽量使用长整型/整型。
- 3)共享对象
利用共享对象,可以减少对象的创建(同时减少了redisObject的创建),节省内存空间。目前redis中的共享对象只包括10000个整数(0-9999);可以通过调整REDIS_SHARED_INTEGERS
参数提高共享对象的个数;例如将REDIS_SHARED_INTEGERS调整到20000,则0-19999之间的对象都可以共享。
考虑这样一种场景:论坛网站在redis中存储了每个帖子的浏览数,而这些浏览数绝大多数分布在0-20000之间,这时候通过适当增大REDIS_SHARED_INTEGERS参数,便可以利用共享对象节省内存空间。
- 4)避免过度设计
然而需要注意的是,不论是哪种优化场景,都要考虑内存空间与设计复杂度的权衡;而设计复杂度会影响到代码的复杂度、可维护性。
如果数据量较小,那么为了节省内存而使得代码的开发、维护变得更加困难并不划算;还是以前面讲到的90000个键值对为例,实际上节省的内存空间只有几MB。但是如果数据量有几千万甚至上亿,考虑内存的优化就比较必要了。
2.关注内存碎片率
内存碎片率是一个重要的参数,对redis 内存的优化有重要意义。
如果内存碎片率过高(jemalloc在1.03左右比较正常),说明内存碎片多,内存浪费严重;这时便可以考虑重启redis服务,在内存中对数据进行重排,减少内存碎片。
如果内存碎片率小于1,说明redis内存不足,部分数据使用了虚拟内存(即swap);由于虚拟内存的存取速度比物理内存差很多(2-3个数量级),此时redis的访问速度可能会变得很慢。因此必须设法增大物理内存(可以增加服务器节点数量,或提高单机内存),或减少redis中的数据。
要减少redis中的数据,除了选用合适的数据类型、利用共享对象等,还有一点是要设置合理的数据回收策略(maxmemory-policy),当内存达到一定量后,根据不同的优先级对内存进行回收。
3.内存增长排查
3.1 Redis内存分析
3.1.1 内存组成
redis> info memory
"# Memory
used_memory:29020136
used_memory_human:27.68M
used_memory_rss:15560704
used_memory_peak:45295152
used_memory_peak_human:43.20M
used_memory_lua:36864
mem_fragmentation_ratio:0.54
mem_allocator:jemalloc-3.6.0
"
属性名 | 属性说明 |
---|---|
used_memory Redis | 分配器分配的内存量,也就是实际存储数据的内存总量 |
used_memory_human | 以可读格式返回 Redis 使用的内存总量 |
used_memory_rss | 从操作系统的角度,Redis进程占用的总物理内存 |
used_memory_peak | 内存分配器分配的最大内存,代表used_memory的历史峰值 |
used_memory_peak_human | 以可读的格式显示内存消耗峰值 |
used_memory_lua | Lua引擎所消耗的内存 |
mem_fragmentation_ratio | used_memory_rss /used_memory比值,表示内存碎片率 |
mem_allocator | Redis 所使用的内存分配器。默认: jemalloc |
计算公式如下:
used_memory = 自身内存+对象内存+缓冲内存+lua内存
used_rss = used_memory + 内存碎片
如下图所示:
3.1.2 内存分析
- 1) 自身内存:一个空的Redis占用很小,可以忽略不计
- 2) kv内存:key对象 + value对象
- 3) 缓冲区:客户端缓冲区(普通 + slave伪装 + pubsub)以及aof缓冲区(比较固定,一般没问题)
- 4) Lua:Lua引擎所消耗的内存
3.1.3. 内存突增常见问题
- 1) kv内存:bigkey、大量写入
- 2) 客户端缓冲区:一般常见的有普通客户端缓冲区(例如monitor命令)或者pubsub客户端缓冲区
3.2 可能出现的问题排查
- 1) bigkey?
redis --bigkeys:可以对redis整个 keyspace 进行统计(数据量大时采样,调用 scan 命令),寻找每种数据类型较大的 keys,给出数据统计
redis-cli --bigkeys -i 0.1 -h 127.0.0.1
- 2) 键值个数增加?
- 3) 客户端缓冲区
如果是因为缓冲区问题,会从
info clients
找到明显问题。重点观察是否明显的omem
大于0的情况。
- 4) Redis的kv哈希表做了 rehash
3.4 rehash
3.4.1 Redis的kv存储结构
如下图所示,Redis的所有kv保存在dict中,其中ht对应两个哈希表ht[0]和ht[1],平时一个空闲,一个用于存储数据,只有当需要rehash时,ht[1]才会用到。
3.4.2 Redis的字典rehash
为了保证哈希表的负载,当哈希表的元素个数等于哈希表槽数时候,会进行rehash扩容。扩容后h[1]的容量等于第一个大于等于ht[0].size*2的2n,例如hash表的初始化容量是4,那么下一次扩容就是8,以此类推。
3.5 总结
由于哈希表的特性,Redis 中键值数量大,不会对存取造成性能影响,但是会出现内存增长的情况。
控制键个数有几个建议:
- 无用的键值设置过期时间或者定期删除
- 优化键值设计:例如可以使用 ziplist hash合并优化部分字符串类型。-
- 未来改进:内核层面支持 rehash 的审计日志以及增强 rehash 的速度
评论区