文章目录
Redis内存和数据淘汰策略
- 下面是基于Redis 4.0.13版本,不同版本可以略有差异。
一、命令和配置
1.1 命令
- info memory。该命令可以输出Redis内存相关信息。(info可以输出全部信息)
192.168.13.52:6379>
192.168.13.52:6379> info memory
# Memory
used_memory:1917336
used_memory_human:1.83M
used_memory_rss:11747328
used_memory_rss_human:11.20M
used_memory_peak:1918312
used_memory_peak_human:1.83M
used_memory_peak_perc:99.95%
used_memory_overhead:1901848
used_memory_startup:786696
used_memory_dataset:15488
used_memory_dataset_perc:1.37%
total_system_memory:135071277056
total_system_memory_human:125.79G
used_memory_lua:37888
used_memory_lua_human:37.00K
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
mem_fragmentation_ratio:6.13
mem_allocator:jemalloc-4.0.3
active_defrag_running:0
lazyfree_pending_objects:0
- 参数含义如下:
输出参数 | 含义 |
---|---|
used_memory | 由Redis分配器分配的内存总量,包含了redis进程内部开销和数据占用的内存,以字节(byte)为单位 |
used_memory_human | 可读展示used_memory |
used_memory_rss | 向操作系统申请的内存大小。与 top 、 ps等命令的输出一致 |
used_memory_rss_human | 可读展示used_memory_rss |
used_memory_peak | redis的内存消耗峰值(以字节为单位) |
used_memory_peak_human | 可读展示used_memory_peak |
used_memory_peak_perc | 使用内存达到峰值内存的百分比,(used_memory/ used_memory_peak) *100% |
used_memory_overhead | Redis为了维护数据集的内部机制所需的内存开销,包括所有客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制的backlog |
used_memory_startup | Redis服务器启动时消耗的内存 |
used_memory_dataset | 数据占用的内存大小,即used_memory-used_memory_overhead |
used_memory_dataset_perc | 数据占用的内存大小的百分比(used_memory_dataset/(used_memory-used_memory_startup))*100% |
total_system_memory | 整个系统内存 |
total_system_memory_human | 可读展示total_system_memory |
used_memory_lua | Lua脚本存储占用的内存 |
used_memory_lua_human | 可读展示Lua脚本存储占用的内存 |
maxmemory | Redis实例的最大内存配置 |
maxmemory_human | 可读展示Redis实例的最大内存配置 |
maxmemory_policy | 当达到maxmemory时的淘汰策略 |
mem_fragmentation_ratio | 碎片率,used_memory_rss/ used_memory |
mem_allocator | 内存分配器 |
active_defrag_running | 表示是否有内存碎片整理活动 0表示没有,1表示有 |
lazyfree_pending_objects | 0表示不存在延迟释放的挂起对象 |
1.2 配置
- 阅读redis.conf配置文件,找到MEMORY MANAGEMENT部分,可以看到关于内存管理的配置,默认都没有放开,主要配置是maxmemory,maxmemory-policy和maxmemory-samples。
############################## MEMORY MANAGEMENT ################################
# Set a memory usage limit to the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# according to the eviction policy selected (see maxmemory-policy).
# 限制内存使用,如果使用内存达到了限制会按照eviction策略来移除对应的key
#
# If Redis can't remove keys according to the policy, or if the policy is
# set to 'noeviction', Redis will start to reply with errors to commands
# that would use more memory, like SET, LPUSH, and so on, and will continue
# to reply to read-only commands like GET.
#如果不能移除key或者策略是noeviction,当操作set,lpush这样需要内存的命令时,redis会给命令返回错误提示需要更多的内存,
#如果是get这样的只读命令则会继续响应
#
# This option is usually useful when using Redis as an LRU or LFU cache, or to
# set a hard memory limit for an instance (using the 'noeviction' policy).
# 这个配置在降Redis当做LRU或者LFU缓存的时候非常有用,或者使用noeviction配置给redis限制一个硬性的内存限制
#
# WARNING: If you have slaves attached to an instance with maxmemory on,
# the size of the output buffers needed to feed the slaves are subtracted
# from the used memory count, so that network problems / resyncs will
# not trigger a loop where keys are evicted, and in turn the output
# buffer of slaves is full with DELs of keys evicted triggering the deletion
# of more keys, and so forth until the database is completely emptied.
# 如果你开起了maxmemory并对实例设置了slave,那么为了满足slave而需要的output buffers会从使用内存中扣减,这样
# 网络问题或者resyncs不会触发key的循环驱逐,反过来如果output buffers已满,删除键会触发删除更多的键,直到数据库清空
#
#
# In short... if you have slaves attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for slave
# output buffers (but this is not needed if the policy is 'noeviction').
# 如果你对redis示例设置了slave,那么建议你设置一个小一点的maxmemory来保证系统有足够的RAM作为slave output buffers
#
#
# maxmemory <bytes>
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
# 内存策略,当maxmemory到达之后,如何选择key来删除,是依据此策略,下面是可选的策略
#
#
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
# 对过期的key使用最近最少使用的策略淘汰
# allkeys-lru -> Evict any key using approximated LRU.
# 对全部的key使用最近最少使用的策略淘汰
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# 对过期的key使用最近最不常用的策略淘汰
# allkeys-lfu -> Evict any key using approximated LFU.
# 对全部的key使用最近最不常用的策略淘汰
# volatile-random -> Remove a random key among the ones with an expire set.
# 对过期的key使用随机策略淘汰
# allkeys-random -> Remove a random key, any key.
# 对全部的key使用随机策略淘汰
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# 淘汰ttl最短的key
# noeviction -> Don't evict anything, just return an error on write operations.
# 不淘汰任何key。内存满时,可进行读操作(get等),写操作将返回错误。这是Redis的默认模式,该模式下内存达到最大的时候,Redis就只能读不能写了。
# LRU means Least Recently Used
# LFU means Least Frequently Used
#
# LRU, LFU 和 volatile-ttl都是使用的近似算法
# Both LRU, LFU and volatile-ttl are implemented using approximated randomized algorithms.
#
# Note: with any of the above policies, Redis will return an error on write
# operations, when there are no suitable keys for eviction.
#
# At the date of writing these commands are: set setnx setex append
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
# getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy noeviction
# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
# algorithms (in order to save memory), so you can tune it for speed or
# accuracy. For default Redis will check five keys and pick the one that was
# used less recently, you can change the sample size using the following
# configuration directive.
# LRU, LFU 和 volatile-ttl都是使用的近似算法,不是精确算法(为了节约内存),你可以调整它的速度或准确性。默认Redis会选择5个key在其中选一个最近最少使用的,这个样本大小可以通过 maxmemory-samples设置,
#
#
# The default of 5 produces good enough results. 10 Approximates very closely
# true LRU but costs more CPU. 3 is faster but not very accurate.
# 默认值为5会产生足够好的结果, 10非常接近真正的LRU但成本更高的CPU, 3更快但不是很准确。
#
# maxmemory-samples 5
配置 | 解析 | 默认值 | 配置示例 |
---|---|---|---|
maxmemory | 最大内存限制 | 0,表示不限制 | maxmemory 1GB/maxmemory 100MB |
maxmemory-policy | 内存满后淘汰策略 | noeviction | volatile-lru |
maxmemory-samples | 淘汰算法样本 | 5 | 5 |
- maxmemory限制的是info mempry中used_memory统计的值,因此Redis实际使用的内存值是可以超过maxmemory限制的内存值的。
1.3 修改配置
- 方式一:修改配置文件,重启实例
- 方式二:没有禁用config的情况下,使用config修改
192.168.13.53:6379> config set maxmemory 500MB
192.168.13.53:6379> info memory
maxmemory:524288000
maxmemory_human:500.00M
192.168.13.53:6379> config get maxmemory
1) "maxmemory"
2) "524288000"
192.168.13.53:6379> config rewrite
OK
192.168.13.53:6379>
//配置文件已经同步修改,追加在配置文件的最后
intellif@segment2:/opt/redis/redis-4.0.13$ tail -fn 2./redis.conf
# Generated by CONFIG REWRITE
maxmemory 500mb
二、内存分析
2.1 参数分析
- 根据info memory的输出,我们通常可以简单的分析。
输出参数 | 含义 |
---|---|
used_memory | 由Redis分配器分配的内存总量,包含了redis进程内部开销和数据占用的内存,以字节(byte)为单位 |
used_memory_rss | 向操作系统申请的内存大小。与 top 、 ps等命令的输出一致 |
mem_fragmentation_ratio | 碎片率,used_memory_rss/ used_memory |
- 当mem_fragmentation_ratio稍大于1时,是比较合理的,表示内存碎片率比较低,如果大于1.5说明Redis使用了实际需要内存的1.5倍,碎片接近50%,可以考虑稍微优化。
- 当mem_fragmentation_ratio稍小于1时,需要的内存大于分配的内存,说明OS存在内存交换,可能会引起明显的延迟。
2.2 内存占用分析
内存占用 | 描述 | 备注 |
---|---|---|
Redis数据占用内存 | K-V对占用内存 | used_memory_dataset和used_memory_dataset_perc查看数据内存大小和占比 |
Redis进程其他开销 | 客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制的backlog | used_memory_overhead查看该部分大小 |
Redis进程其他开销 | 如:Lua脚本存储占用的内存,子进程开销等 | used_memory_lua |
内存碎片 | 操作系统分配了但是Redis无法利用 | (碎片会导致mem_fragmentation_ratio>1) |
三、内存优化
3.1 redisObject对象
- Redis中五大数据类型都是使用redisObject来封装的,在其中加入了type、编码、LRU、等信息。
- 如果对象是String且长度<=39,则只需要进行一次内存操作,过大则会增加内存分配的次数。
//可以清理空闲的key,Object命令可以查看redisObject对象的内部属性,比如idletime,refcount等
192.168.13.53:6379> set key1 val1
OK
192.168.13.53:6379> OBJECT idletime key1
(integer) 5
192.168.13.53:6379> OBJECT idletime key1
(integer) 6
3.2 缩短k-v
- 设计上考虑尽量缩短key的长度
- 可以考虑使用压缩算法,减小值占用空间
3.3 共享对象池
- Redis内部对于0-9999的整数维护了整数对象池,(因为创建一个redisObject保存一个整数的开销比一个整数自身还要大)。在可以的情况下尽量使用整数对象,可以节约内存。
PS:共享对象池和与maxmemory+LRU策略冲突,因为在使用了maxmemory+LRU的策略时,Redis需要统计每个key的LRU并保存到redisObject的LRU字段,此时如果对象共享那么该字段也会共享,这样就无法做LRU淘汰,因此该模式下会禁用共享对象池
如果没有设置maxmemory,那么共享对象池是可以使用的,因为此时Redis不会做任何内存限制直到内存用尽,因此共享对象池可以正常工作。
另外只将整数做共享对象池也是性能和开销的折中,其他类型的判断相等复杂度是O(n)甚至O(N2),整数是O(1)。
3.4 字符串优化
- 避免字符串的频繁修改操作,而是直接使用set修改字符串,修改可能会因为预分配机制造成空间的浪费和内存碎片
- 类似于json这样的键值对可以考虑采用hash存储,效率更高,获取时也支持hmget/hmset,而不是整体存取,性能更好(使用ziplist编码方式)
3.5 编码优化
//查看键值对的内部编码
192.168.13.53:6379> set addr shenzhen
OK
192.168.13.53:6379> OBJECT encoding addr
"embstr"
192.168.13.53:6379>
//查看命令平均耗时:info Commandstats
- 关于编码优化这一块,有很多内容,详情阅读参考文章[3]的8.3小节
3.6 减少键的数量
- 比如100万个k-v对,可以考虑在业务上控制,使用1000个Hash存储,每一个Hash存储1000个k-v对,这样减少了外层键的数量可以节约内存,具体方法可以考虑散列,或者对key做一些拆分来实现。Hash使用ziplist编码,但是需要将长度控制在1000以内。
ziplist是连续内存结构,比较节约内存,适合存储小对象,查询复杂度是O(N2),因为存储数据比较短,所以性能能够满足。
四、参考
- [1] Redis性能问题排查解决手册
- [2] Redis学习-内存优化
- [3] Redis开发与运维