Redis 为何 CPU 使用率过高?调试与优化技巧

调查 Redis(关键的内存数据存储)中突然出现的高 CPU 使用率。本指南详细介绍了如何使用 `SLOWLOG` 和 `INFO` 命令调试负载,以定位低效操作(如 `KEYS *` 或大键删除)。学习实用的优化技术,包括切换到异步 `UNLINK`、利用管道化以及调整持久化设置,从而立即降低服务器负载并恢复最佳的 Redis 性能。

Redis 为何 CPU 使用率过高?调试与优化技巧

Redis CPU 使用率高通常意味着以下三种情况之一:Redis 在其主执行路径上执行了过多的命令工作,后台工作(如持久化)增加了压力,或者客户端以 Redis 无法高效处理的模式发送流量。修复方法取决于具体是哪种情况。

除非服务已经濒临崩溃,否则不要一开始就重启 Redis。重启可能会清除症状并抹掉证据。首先捕获命令延迟、命令组合、客户端数量、持久化状态和主机 CPU。这些信息会告诉你问题出在:糟糕的命令、糟糕的流量模式、过载的单核,还是嘈杂的主机。

理解 Redis 架构与 CPU 负载

Redis 通常被描述为单线程的,这对于命令执行来说基本正确,但现代 Redis 也可以使用后台线程和可选的 I/O 线程。实际要点仍然相同:执行时间过长的命令会延迟其他客户端,并且即使机器在其他地方有空闲 CPU,一个饱和的核心也足以产生可见的延迟。

影响 Redis CPU 负载的关键因素

常见原因包括:昂贵的命令、大值、Lua 脚本、每次往返发送太多小命令、频繁的连接建立与断开、持久化活动,以及迫使内核比 Redis 预期更努力工作的内存压力。

调试高 CPU 使用率

在优化之前,必须准确识别负载来源。监控工具和内置的 Redis 命令对于诊断至关重要。

1. 使用 INFOLATENCY 命令

INFO 命令提供服务器状态的快照。重点关注 CPU 部分和命令统计信息。

redis-cli INFO cpu

关注变化率,而不仅仅是绝对值。used_cpu_user 快速增加通常指向命令处理。used_cpu_sys 快速增加可能指向内核工作,例如网络、内存管理或磁盘相关活动。

延迟工具显示 Redis 观察到的事件类别:

redis-cli LATENCY LATEST
redis-cli LATENCY DOCTOR

2. 使用 SLOWLOG 识别慢命令

Redis 慢日志记录超过指定执行时间的命令。这是查找性能不佳操作的最直接工具。

Redis 慢日志记录执行时间超过阈值的命令。它不包括网络时间或在客户端池中等待的时间,因此最好与应用程序延迟指标一起使用。

配置示例:

slowlog-log-slower-than 1000
slowlog-max-len 1024

检索日志:

redis-cli SLOWLOG GET 10

检查命令名称、键名称和持续时间。如果 KEYS、大型 HGETALL、巨大的 SMEMBERS、广泛的排序集范围或 Lua 脚本在日志中占主导地位,那么 CPU 问题很可能是由应用程序驱动的。

3. 监控网络和客户端活动

在事件期间,MONITOR 很诱人,但在繁忙的服务器上它非常昂贵。优先使用 INFO commandstatsINFO clients、慢日志、客户端库指标,以及如果可用的话,从副本进行采样。

有用的命令:

redis-cli INFO commandstats
redis-cli INFO clients
redis-cli CLIENT LIST

如果部署后命令量翻倍,你可能会在 cmdstat_getcmdstat_hgetall 或类似的计数器中看到。如果客户端不断连接和断开,请在调整 Redis 之前先修复连接池。

常见原因与优化策略

一旦确定了有问题的命令或进程,应用有针对性的优化技术。

1. 消除阻塞命令

最快的改进通常来自移除那些迫使 Redis 遍历巨大键空间或序列化巨大值的命令。

低效命令 导致高 CPU 的原因 优化/替代方案
KEYS * 扫描整个键空间。O(N)。 使用 SCAN 迭代或重构数据访问。
FLUSHALL / FLUSHDB 除非使用异步模式,否则删除每个键。 使用谨慎的范围删除、UNLINK,或仅在适当时使用异步刷新。
HGETALLSMEMBERS(针对非常大的集合) 将整个结构检索到内存中并序列化。 使用 HSCANSSCAN,或将大型结构分解为更小的键。

对于非常大的键,使用 UNLINK 代替 DELDEL 同步释放内存。UNLINK 从键空间中移除键并异步释放内存,这通常可以减少大删除期间的可见延迟。

# 代替 DEL large_key
UNLINK large_key

2. 优化持久化(RDB 和 AOF)

RDB 快照和 AOF 重写使用后台子进程,但仍然可能通过 fork 成本、写时复制内存、磁盘带宽和 CPU 争用来影响父进程。

  • RDB 快照: 如果你频繁保存(例如,每分钟一次),重复的 fork() 调用会导致反复的 CPU 峰值。减少自动保存的频率。
  • AOF 重写: AOF 重写(BGREWRITEAOF)也消耗大量资源。Redis 尝试通过执行最少的 I/O 来优化此过程,但在此过程中 CPU 使用率会上升。

如果持久化与 CPU 峰值时间点吻合,请检查 INFO persistence 和主机磁盘指标。你可以降低 RDB 频率,将繁重的备份安排在流量低谷期,保留更多内存余量,或改进存储。暂停持久化可以减少负载,但也会增加数据丢失风险,因此这应该是一个深思熟虑的操作决策。

3. 处理内存碎片和交换

虽然内存问题通常与高内存使用率相关,但严重的内存碎片化,或者更糟的是,操作系统开始将 Redis 数据交换到磁盘(抖动),会急剧增加 CPU 使用率,因为内核在努力管理内存。

  • 检查交换: 使用操作系统工具(vmstattop)检查系统是否正在主动交换属于 Redis 进程的内存页。
  • 内存碎片比率: 检查 INFO memory 中的 mem_fragmentation_ratio。高比率表明分配器行为可能浪费了内存,但请通过 RSS、数据集大小和主机内存指标进行确认。

如果发生交换,请减少数据集,降低 maxmemory,将工作移出主机,或增加内存。Redis 的设计初衷并非在其热数据集被分页到磁盘时仍能良好运行。

4. 网络优化与管道化

如果 CPU 负载与大量小命令相关,问题可能在于命令开销和网络波动,而不是某个明显缓慢的命令。

管道化允许客户端发送多个命令而无需在每个命令后等待响应。它减少了往返次数,并可以提高批量写入或读取的吞吐量。保持管道批次有界;包含数千个繁重命令的管道可能会产生自身的延迟峰值。

持续性能的最佳实践

为防止未来的 CPU 峰值,请采用以下架构和配置最佳实践:

  1. 对可能很大的键使用 UNLINK
  2. SCAN 替换 KEYS,并用基于游标的读取替换完整集合读取。
  3. 在部署后跟踪 INFO commandstats,这样新的命令模式就不会让你措手不及。
  4. 根据实际的磁盘和内存余量调整持久化设置。
  5. 如果单个 Redis 实例在命令修复后仍然饱和,请使用 Redis 集群、客户端分片、单独的缓存/会话实例,或具有更好单核性能的更大实例来拆分工作负载。

快速事件检查清单

在峰值期间,运行:

redis-cli INFO cpu
redis-cli INFO commandstats
redis-cli INFO clients
redis-cli INFO memory
redis-cli INFO persistence
redis-cli SLOWLOG GET 20
redis-cli LATENCY LATEST

然后将这些结果与应用程序部署、定时任务、流量变化、持久化事件和主机指标进行对比。Redis CPU 使用率高通常是可以修复的,但修复方法是具体的:移除昂贵的命令,批量处理健谈的客户端,停止连接波动,给持久化留出工作空间,或者在单个实例确实达到极限时拆分工作负载。