昨晚 10 点,一个网站的负载飙高,触发了 uptime 的宕机报警。从 top 命令结果里看,redis 进程 cpu 飙高,但是我起初以为是被恶意攻击了,把 cloudflare 防护规则严格了一些,临时负载降下来了。由于太困,明天还有事情,就没有继续观察。
早上 6 点起来,又看了,负载还是高。但是并不影响 golang 相关的服务,只有 php 的旧站点受到了影响。
哎,PHP 这些破玩意真糟心。牛马还是要上班,就只能寄希望于晚上回来解决了。
晚上捣鼓了两个小时,算是解决了。
查看 redis 运行状态
redis 放在 docker 里,所以登录 redis 使用:
docker compose exec redis redis-cli
查看运行状态:
> INFO stats
total_connections_received:168608
total_commands_processed:11273966
instantaneous_ops_per_sec:326
total_net_input_bytes:203350837911
total_net_output_bytes:26614034188264
total_net_repl_input_bytes:0
total_net_repl_output_bytes:0
instantaneous_input_kbps:31.01
instantaneous_output_kbps:453887.44
instantaneous_input_repl_kbps:0.00
instantaneous_output_repl_kbps:0.00
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:189148
expired_stale_perc:0.36
expired_time_cap_reached_count:0
expire_cycle_cpu_milliseconds:22144
evicted_keys:0
evicted_clients:0
total_eviction_exceeded_time:0
current_eviction_exceeded_time:0
keyspace_hits:7151109
keyspace_misses:2783287
pubsub_channels:0
pubsub_patterns:0
pubsubshard_channels:0
latest_fork_usec:11041
total_forks:245
migrate_cached_sockets:0
slave_expires_tracked_keys:0
active_defrag_hits:0
active_defrag_misses:0
active_defrag_key_hits:0
active_defrag_key_misses:0
total_active_defrag_time:0
current_active_defrag_time:0
tracking_total_keys:0
tracking_total_items:0
tracking_total_prefixes:0
unexpected_error_replies:0
total_error_replies:3546
dump_payload_sanitizations:0
total_reads_processed:11920639
total_writes_processed:18558028
io_threaded_reads_processed:0
io_threaded_writes_processed:0
reply_buffer_shrinks:123859
reply_buffer_expands:375511
- 输入带宽 (instantaneous_input_kbps):31.01 KB/s(请求很小)
- 输出带宽 (instantaneous_output_kbps):453887.44 KB/s ≈ 443 MB/s(响应巨大)
结论:输入和输出的差距高达 1.4万倍。这说明客户端只发了一个很小的请求(比如 HGETALL 或 SMEMBERS),但Redis返回了一个极其庞大的数据给客户端。
查看当前哪个客户端的输出缓冲区占用最大
CLIENT LIST
输出:
id=112411 addr=[2400:8901::7]:41764 laddr=[2400:8901::4]:6379 fd=21 name= age=28381 idle=0 flags=N db=2 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=20474 argv-mem=0 multi-mem=0 rbs=4096 rbp=4096 obl=0 oll=1 omem=20971544 tot-mem=20996888 events=rw cmd=get user=default redir=-1 resp=2
id=112430 addr=[2400:8901::7]:52090 laddr=[2400:8901::4]:6379 fd=26 name= age=28366 idle=0 flags=N db=2 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=20474 argv-mem=0 multi-mem=0 rbs=16384 rbp=16384 obl=0 oll=1 omem=20971544 tot-mem=21009176 events=rw cmd=get user=default redir=-1 resp=2
有 3个客户端连接 正在拉取 超大Key,每个连接的输出缓冲区(omem)都达到了 20,971,544 字节(约 20 MB).这三个连接都在执行 cmd=get,且都在 db=2(数据库2),说明它们都在 反复读取同一个超大字符串类型的Key(或者内容完全相同的大Key)。每个响应包约 20MB,三个连接同时输出,瞬时输出带宽自然飙到 450MB/s。
找到这个大 KEY
连 redis db 2:
执行完后,它会告诉你哪个数据类型的Key最大(比如最大的Hash有多大)。
> docker compose exec redis redis-cli -n 2 --bigkeys -i 0.1
Sampled 4658 keys in the keyspace!
Total key length in bytes is 297586 (avg len 63.89)
Biggest string found '"9c6a2e0bcbb03f32fb0382d38fa00bbe-cache-com_modules-5cdbee63315b6b81941ea5c4844ffd8c"' has 26627066 bytes
-i 0.1 表示每扫描100个Key就休息0.1秒,防止在高负载时加剧CPU抖动。
删除这个大 KEY
docker compose exec redis redis-cli -n 2 DEL "9c6a2e0bcbb03f32fb0382d38fa00bbe-cache-com_modules-5cdbee63315b6b81941ea5c4844ffd8c"
cpu 就正常了。但是只正常了半个小时,然后这个 key 又变大到了 15M。。。
删了几次,还是反复。我无语了。。。
gemini 救命
改问 gemini 之后,找到了根源:
joomla 的 redis 缓存中,这个 key : cache-com_modules 一直暴涨,有 20 多M,这是为什么
在 Joomla 中,cache-com_modules 这个 key 专门用于存储网站所有模块(Modules)的缓存数据(即模块渲染后的 HTML 代码及相关参数)。
这个 key 的体积暴涨到 20 多兆(在 Redis 这种内存数据库中,对于单一文本 key 来说已经非常庞大),通常是由以下几个核心原因导致的:
开启了“渐进式缓存”(Progressive Caching)—— 最常见原因
Joomla 的全局缓存分为两种模式:保守式(Conservative)和渐进式(Progressive)。
- 保守式缓存: 按模块生成缓存。1000 个未登录访客访问同一个页面,共用同一份模块缓存。
- 渐进式缓存: 为每一个独立访客(基于 Session)单独生成一份模块缓存。
如果你的网站开启了“渐进式缓存”,当有大量的搜索引擎爬虫(蜘蛛)、恶意扫描机器人或大量真实散客访问时,Joomla 会为每一个全新的 Session 生成一份完全独立的模块缓存。这些缓存全部堆积在 com_modules 下,导致该 key 的体积呈指数级暴涨。
切换回“保守式缓存”. 这是最立竿见影的解决方法:
- 进入 Joomla 后台 -> 系统 (System) -> 全局设置 (Global Configuration)。
- 点击 系统 (System) 选项卡。
- 在 缓存设置 (Cache Settings) 中,将“系统缓存”修改为 保守式缓存 (Conservative caching)。
- 保存设置。
这个 key 的过期时间
# docker compose exec redis redis-cli -n 2 TTL "9c6a2e0bcbb03f32fb0382d38fa00bbe-cache-com_jmap-9667e9aa5d8b93f1cb1835bd4315b7c9"
(integer) 3456
返回结果 (integer) 3456 表示该 Key 的剩余存活时间为 3456 秒,换算一下大约是 57.6 分钟(接近 1 小时)。
看来不需要我加定时任务自动清理了。
关于作者 🌱
我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式