Featured image of post 记一次优化广告服务 Redis 30G+ 内存

记一次优化广告服务 Redis 30G+ 内存

从开始学习 Redis 到现在已经很多年了, 是时候快速更新知识了

问题

  • 最近在看项目遗留代码的时候, 有一些操作Redis代码, 都是没有设置过期时间, 猜测可能因为Hash结构在使用时不能同时设置过期时间, 导致后面忘记设置过期时间了.

  • 代码里常用的结构是: key是根据广告标识相关, field是用户的标识, value是自定义的数据, 有两种使用方式导致内存占用过大

    • 第一种是拼上了date作为keyHash结构
      • HSET xxx:ad_id:20220315 userid value
    • 第二种是广告下线了, 但是广告对应的key还遗留在库中
      • HSET xxx:ad_id userid value
  • 操作之前, 数据库的信息如下: 配置是8*8G, 但是内存已经占用51G+

  • 理论上这个服务不应该使用这么多内存的, 由于遗留代码没有设置过期时间, 导致运维只能通过不断增加内存来让服务正常运行

优化

查询key的分布

重构代码

  • 通过分析得到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中间件, 稍不注意, 这就是一个大坑, 有大量的文件文件写入但是不会删除.