使用 SLOWLOG 命令诊断和解决 Redis 慢查询

使用 Redis SLOWLOG 查找慢命令、调整阈值、检查客户端并替换昂贵的访问模式。

使用 SLOWLOG 命令诊断和解决 Redis 慢查询

Redis 是一种极快的内存数据存储,广泛用于缓存、实时分析、会话管理和消息代理。其性能通常对基于它构建的应用程序的响应性至关重要。然而,即使 Redis 速度很快,优化不佳的命令或意外负载也可能导致慢查询,从而造成瓶颈,降低整体应用程序性能。

识别根本原因从 Redis SLOWLOG 开始。它会记录服务器端执行时间超过阈值的命令,帮助您找到诸如 KEYS、宽范围 LRANGE、大型 HGETALL 或慢速 Lua 脚本等昂贵命令。

理解 Redis SLOWLOG 特性

SLOWLOG 是一个记录超过指定执行时间的查询的系统。它本质上是一个内存日志,记录了执行时间超过配置阈值的命令。与传统的基于文件的日志不同,SLOWLOG 直接存储在 Redis 内存中,使其访问和管理快速,而不会产生磁盘 I/O 开销。

每个条目包含一个唯一 ID、Unix 时间戳、执行时间(微秒)、命令参数,以及在现代 Redis 版本中,客户端地址和客户端名称(如果可用)。SLOWLOG 测量 Redis 内部的命令执行时间;它不包括网络延迟、客户端等待时间或在 Redis 开始运行命令之前排队的时间。

SLOWLOG 的工作原理:配置参数

在有效使用 SLOWLOG 之前,了解并配置其两个主要参数非常重要。这些参数控制记录什么以及保留多少条目。

slowlog-log-slower-than

此参数定义命令被记录的阈值(微秒)。只有执行时间超过此指定值的命令才会被记录在 SLOWLOG 中。将此值设置得太低可能会记录太多命令,可能消耗大量内存并使分析变得困难。设置得太高可能会导致您错过真正慢的查询。

  • 默认值10000(10 毫秒)
  • 建议:从默认值开始,根据应用程序的性能要求进行调整。对于高性能系统,您可以将其降低到 1000 微秒(1 毫秒)甚至 100 微秒。
  • 特殊值:设置为 0 将记录每个命令。设置为负值将完全禁用 SLOWLOG

您可以查看此参数的当前值:

redis-cli config get slowlog-log-slower-than

要设置新值(例如,5000 微秒或 5 毫秒):

redis-cli config set slowlog-log-slower-than 5000

要使此更改永久生效,您需要更新 redis.conf 文件,或者如果您的 Redis 版本和设置支持,使用 CONFIG REWRITE

slowlog-max-len

此参数指定 Redis 在 SLOWLOG 中保留的最大条目数。当日志达到最大长度时,新条目将导致最旧的条目自动被移除(FIFO - 先进先出)。

  • 默认值128
  • 建议:对于繁忙的生产系统,默认值通常太小。考虑将其增加到 1024 甚至 4096,以确保您捕获足够的历史记录以进行彻底分析,同时考虑内存影响。

您可以查看当前值:

redis-cli config get slowlog-max-len

要设置新值(例如,1024 条):

redis-cli config set slowlog-max-len 1024

再次提醒,记得在 redis.conf 文件中持久化此更改。

检索和分析 SLOWLOG 条目

配置 SLOWLOG 后,您可以使用一组命令与其交互。

SLOWLOG GET

此命令用于从 SLOWLOG 检索条目。您可以选择指定一个 count 来检索一定数量的最近条目。

  • SLOWLOG GET:检索日志中当前的所有条目。
  • SLOWLOG GET <count>:检索最新的 <count> 条条目。

示例:

# 检索最近的 10 条慢日志条目
redis-cli slowlog get 10

示例输出(为清晰起见简化):

1) 1) (integer) 12345        # 日志条目的唯一 ID
   2) (integer) 1678886400   # Unix 时间戳(例如,2023 年 3 月 15 日,UTC 时间 12:00:00)
   3) (integer) 25000        # 执行时间(微秒)(25 毫秒)
   4) 1) "LRANGE"           # 命令
      2) "mybiglist"       # 参数 1
      3) "0"               # 参数 2
      4) "-1"              # 参数 3
   5) "127.0.0.1:54321"  # 客户端 IP 和端口
   6) "client-name-app" # 客户端名称(如果设置)
...

SLOWLOG LEN

此命令返回 SLOWLOG 中当前的条目数。

redis-cli slowlog len

输出:

(integer) 5

SLOWLOG RESET

此命令清除 SLOWLOG 中的所有条目。这在您分析完现有条目并希望从新日志开始以捕获新的性能数据时非常有用。

redis-cli slowlog reset

输出:

OK

解释 SLOWLOG 输出

每个条目提供关键信息:

  1. 唯一 ID:一个顺序标识符。用于跟踪特定事件。
  2. 时间戳:命令执行的时间。有助于将慢查询与应用程序部署更改或特定负载时段关联起来。
  3. 执行时间(微秒):最重要的指标。这告诉您命令完成所需的确切时间。高值表示潜在瓶颈。
  4. 命令和参数:确切的 Redis 命令及其参数。这对于理解哪个操作缓慢至关重要(例如,KEYS *、在非常大的列表上的 LRANGE 0 -1、没有 LIMITSORT)。
  5. 客户端地址:发出命令的客户端的 IP 地址和端口。有助于追溯到源应用程序或服务。
  6. 客户端名称:如果您的应用程序设置了 CLIENT SETNAME(强烈推荐以获得更好的可观察性),这提供了额外的上下文层,指示应用程序的哪个部分发出了慢查询。

实际示例:识别慢命令

让我们模拟一个慢命令,看看 SLOWLOG 如何捕获它。

首先,为了演示,将 slowlog-log-slower-than 设置为一个较低的值,例如 1000 微秒(1 毫秒):

redis-cli config set slowlog-log-slower-than 1000

接下来,执行一个已知在应用于大数据集时可能较慢的操作,例如 KEYS * 或对具有许多元素的列表执行 LRANGE

让我们创建一个大型列表:

for i in {1..100000}; do redis-cli LPUSH mybiglist $i; done

现在,执行一个 LRANGE 命令,从这个大列表中检索所有元素:

redis-cli LRANGE mybiglist 0 -1

此命令可能需要超过 1 毫秒的时间。

最后,检查 SLOWLOG

redis-cli slowlog get 1

您应该会看到类似于以下的输出(值会有所不同):

1) 1) (integer) 12346
   2) (integer) 1678886450
   3) (integer) 15432 # 这是我们的慢执行时间(微秒)
   4) 1) "LRANGE"
      2) "mybiglist"
      3) "0"
      4) "-1"
   5) "127.0.0.1:54322"
   6) ""

输出清楚地显示了 LRANGE mybiglist 0 -1 命令、其执行时间(15432 微秒或 15.432 毫秒)以及发生时间。这立即告诉我们,获取整个大列表正在消耗大量时间。

解决慢查询的策略

一旦您使用 SLOWLOG 识别出慢查询,下一步就是优化它们。以下是常见策略:

  1. 优化数据结构和访问模式

    • 避免对大数据集使用 O(N) 命令:像 LRANGE 0 -1(获取所有元素)、SMEMBERS(获取所有集合成员)、HGETALL(获取所有哈希字段/值)、SORT(没有 LIMIT)这样的命令可能会很慢。如果您需要处理大型集合,请考虑使用 SCANSSCANHSCANZSCAN 进行迭代,而不是一次性获取所有内容。
    • 使用适当的数据结构:例如,如果您经常需要获取对象的属性,请使用哈希而不是为每个属性存储单独的键。
    • 限制结果:对于列表或有序集合,使用带有合理限制的 LRANGE <start> <end>ZRANGE <start> <end>,而不是获取整个结构。
  2. 管道化:不要逐个发送命令,而是使用管道化将多个命令批量处理到一个请求中。这减少了网络往返时间(RTT)开销,即使单个命令很快,也能显著加快应用程序的速度。

    # 无管道化(由于多次 RTT 较慢)
    r.set('key1', 'value1')
    r.set('key2', 'value2')
    
    # 有管道化(更快,一次 RTT)
    pipe = r.pipeline()
    pipe.set('key1', 'value1')
    pipe.set('key2', 'value2')
    pipe.execute()
    
  3. Lua 脚本(EVAL):对于涉及多个 Redis 命令且需要原子执行或最小 RTT 的复杂操作,请考虑使用 Lua 脚本。脚本直接在 Redis 服务器上执行,减少了网络延迟并确保了原子性。但是,长时间运行的 Lua 脚本可能会阻塞 Redis,因此必须仔细优化。

  4. 避免在生产环境中使用 KEYSKEYS 命令是 O(N)(其中 N 是数据库中的键数),并且可能会长时间阻塞 Redis 服务器,尤其是在大型数据库上。在生产环境中,使用 SCAN 来迭代键。SCAN 提供了类似迭代器的功能,可以暂停和恢复,避免了长时间的阻塞操作。

    # 生产环境中不好
    redis-cli KEYS *
    
    # 生产环境中用于迭代的好方法
    redis-cli SCAN 0 MATCH user:* COUNT 100
    
  5. 连接池:确保您的应用程序使用适当的连接池来高效管理与 Redis 的连接。为每个命令打开和关闭连接可能会消耗大量资源。

  6. 分片和集群:如果您的数据集或工作负载增长到单个 Redis 实例无法处理的程度,请考虑将数据分片到多个 Redis 实例或采用 Redis 集群。这可以分散负载和数据,防止单个实例成为瓶颈。

  7. 读副本:对于读取密集型工作负载,将读取查询卸载到 Redis 读副本。这可以扩展读取吞吐量并减少主实例的负载,使其能够专注于写入。

使用 SLOWLOG 的最佳实践

  • 定期监控:不要设置后就不管了。定期检查 SLOWLOG 条目,尤其是在部署后或高峰负载期间。
  • 适当的阈值:根据应用程序可接受的延迟调整 slowlog-log-slower-than。对一个应用程序来说慢的,对另一个应用程序可能是正常的。
  • 足够的日志长度:将 slowlog-max-len 设置得足够大以保留有意义的历史记录,但不要太大以至于消耗过多内存。
  • 定期清除:在分析条目后使用 SLOWLOG RESET 获取新数据,或者如果您将 SLOWLOG 与监控系统集成,请考虑自动化此过程。
  • 客户端命名:在应用程序代码中使用 CLIENT SETNAME <name>。这为 SLOWLOG 条目增加了有价值的上下文,使追踪慢命令到应用程序的特定部分变得更加容易。

要点

使用 SLOWLOG 捕获昂贵的 Redis 命令,然后修复访问模式,而不是仅仅提高阈值。如果慢条目显示对一个大键进行了宽范围读取,请对结果进行分页、增量扫描、更改数据模型,或将繁重的工作移出请求路径。