问题
-
最近在看项目遗留代码的时候, 有一些操作
Redis
代码, 都是没有设置过期时间, 猜测可能因为Hash
结构在使用时不能同时设置过期时间, 导致后面忘记设置过期时间了. -
代码里常用的结构是:
key
是根据广告标识相关,field
是用户的标识,value
是自定义的数据, 有两种使用方式导致内存占用过大- 第一种是拼上了
date
作为key
的Hash
结构HSET xxx:ad_id:20220315 userid value
- 第二种是广告下线了, 但是广告对应的
key
还遗留在库中HSET xxx:ad_id userid value
- 第一种是拼上了
-
操作之前, 数据库的信息如下: 配置是
8*8G
, 但是内存已经占用51G+
-
理论上这个服务不应该使用这么多内存的, 由于遗留代码没有设置过期时间, 导致运维只能通过不断增加内存来让服务正常运行
优化
查询key
的分布
- 通过阿里云的离线分析平台可以查到大
key
https://help.aliyun.com/document_detail/102093.htm?spm=a2c4g.11186623.0.0.58e12542DRK90l#concept-ufz-byl-jgb - 如果没用阿里云服务, 查询大
key
的方案 (如果是访问低峰期, 可以不传递-i
参数)redis-cli --bigkeys -i 0.1
重构代码
- 通过分析得到
key
的分布图, 然后通过代码中模糊搜索, 定位到操作Redis
的相关代码- 相关代码设置过期时间
TTL xxx:ad_id:20220315 86400
- 广告下线事件删除过期
key
UNLINK xxx:ad_id
- 相关代码设置过期时间
删除遗留数据
- 以前删除大
key
, 直接del
会阻塞主线程, 都是写一个脚本Scan
然后再去删除,这样子不会阻塞 - 如果版本大于
4.0
可以直接使用unlink xxx:ad_id:20220315
- 如果要批量删除执行:
redis-cli --scan --pattern xxx:ad_id:* | xargs redis-cli unlink
- 备注:
4.0
之后的Redis
自动删除过期key
也是会进行这个异步删除操作
删除数据之后,used_memory_rss
不释放
used_memory_rss
是程序申请的空间,used_memory
是程序实际使用的内存- 删除大量数据之后, 大概率会看到
used_memory_rss
这个值远大于used_memory
, 这时候就代表有很多内存碎片 mem_fragmentation_ratio = used_memory_rss / used_memory
mem_fragmentation_ratio
大于1.5
就需要处理这个内存碎片- 同步处理方式
memory purge
(不推荐) - 异步处理方式
- 开启自动内存碎片整理
config set activedefrag yes
- 开启自动内存碎片整理
config get activedefrag
1) "activedefrag"
2) "no"
- 设置某项阈值(一定要设置某个选项
Redis
才会开始处理)config set active-defrag-threshold-lower 10
- 开启自动内存碎片整理
- 同步处理方式
# 开启自动内存碎片整理(总开关)
activedefrag yes
# 当碎片达到 100mb 时,开启内存碎片整理
active-defrag-ignore-bytes 100mb
# 当碎片超过 10% 时,开启内存碎片整理
active-defrag-threshold-lower 10
# 内存碎片超过 100%,则尽最大努力整理
active-defrag-threshold-upper 100
# 内存自动整理占用资源最小百分比
active-defrag-cycle-min 25
# 内存自动整理占用资源最大百分比
active-defrag-cycle-max 75
- 经过一个早上的处理, 可以看到
used_memory_rss
已经开始下降了, 不过看着这个进度应该还需要很长的时间来释放内存
Notes
删除数据之前可以手动备份一次数据
- 查看上一次备份的时间戳
LASTSAVE
- 开始备份
BGSAVE
- 之后继续运行
LASTSAVE
看时间戳, 如果大于上一次备份的时间戳, 代表此次备份已经成功, 可以进行删除
过期处理
Redis 密钥以两种方式过期:被动方式和主动方式。
仅当某些客户端尝试访问密钥时,密钥就会被动过期,并且发现密钥已超时。
当然这还不够,因为有过期的密钥将永远不会被再次访问。这些密钥无论如何都应该过期,因此 Redis 会定期在设置过期的密钥中随机测试几个密钥。所有已经过期的键都会从键空间中删除。
具体来说,这是 Redis 每秒执行 10 次的操作:
从具有关联过期时间的密钥集中测试 20 个随机密钥。
删除所有发现过期的密钥。
如果超过 25% 的密钥过期,请从步骤 1 重新开始。
这是一个简单的概率算法,基本上假设我们的样本代表了整个密钥空间,并且我们继续过期直到可能过期的密钥百分比低于 25%
这意味着在任何给定时刻,使用内存的已过期密钥的最大数量等于每秒最大写入操作量除以 4。
- 像
Laravel
默认使用的缓存驱动是File
, 用的就是被动方式删除, 并且默认开启了ThrottleRequests/throttle
中间件, 稍不注意, 这就是一个大坑, 有大量的文件文件写入但是不会删除.