Redis 慢查询告警与抓包分析排障脚本原创
下面这个脚本定期读取 Redis 的 SLOWLOG,把超过阈值的慢命令整理后推送告警,适合挂在定时任务里做常态巡检。核心逻辑是记录上次处理过的慢日志 id,避免同一条重复告警。
在实际生产环境中,慢查询告警只能看到"命令执行慢",但真正拖慢请求的原因可能是:网络传输慢(大 value 在客户端和服务端之间来回传输的时间,不计入执行耗时)、排队等待(Redis 主线程被其他命令阻塞)、或 客户端本身的问题(请求发出去很久才收到响应)。这些信息 slowlog 看不到,必须靠抓包来补位。慢查询告警和抓包分析是同一件事的两个时间尺度——告警做常态监控,抓包做现场诊断。
版本说明
本文写于 2023-01,2026-09 复核。
Redis 协议(RESP)抓包分析方法未变;如集群已启用 RESP3 协议,部分命令的响应格式会有差异,脚本解析逻辑需相应调整。Slowlog 相关参数在 Redis 各个版本中保持稳定。
# 1. 慢查询告警脚本
redis_alarm.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import redis
import sys
import time
import os
import configparser
import requests
import json
def push_telegram(msg):
request_header = {"content-type": "application/json; charset=UTF-8", "Authorization": "<YOUR_BOT_TOKEN>"}
push_url = "<YOUR_PUSH_URL>"
push_data = {"targetname": "<YOUR_TARGET_NAME>", "text": msg}
push_data = json.dumps(push_data)
requests.post(url=push_url, headers=request_header, data=push_data, verify=False)
names = {}
hosts = ["<PUBLIC_IP>", "<PUBLIC_IP>", "<PUBLIC_IP>"]
ports = {"<PUBLIC_IP>": [8001, 9001, 9002, 8002], "<PUBLIC_IP>": [8001, 8002, 9001, 9002], "<PUBLIC_IP>": [8001, 8002, 9001, 9002]}
# 获取文件的当前路径(绝对路径)
cur_path = os.path.dirname(os.path.realpath(__file__))
config_path = os.path.join(cur_path, "config.conf")
conf = configparser.ConfigParser()
conf.read(config_path)
for host in hosts:
for port in ports[host]:
names["max_" + host + "_" + str(port)] = conf.get("slowlog", "max_" + host + "_" + str(port))
print(names)
while True:
start_time = time.perf_counter()
for host in hosts:
tmpMax = 0
for port in ports[host]:
pool = redis.ConnectionPool(host=host, port=port, password="<YOUR_REDIS_PASSWORD>", decode_responses=False)
r = redis.Redis(connection_pool=pool)
tList = r.slowlog_get(num=500)
tmpMax = int(names["max_" + host + "_" + str(port)])
while len(tList) > 0:
slowContent = tList.pop()
if slowContent["id"] > tmpMax:
errmsg = "redis集群主机:{}端口:{}发生慢查询\n执行时间:{}\n执行耗时:{}ms\n执行语句:{}\n客户端ip:{}".format(
host, port, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(slowContent["start_time"])), slowContent["duration"] / 1000, slowContent["command"], slowContent["client_address"]
)
names["max_" + host + "_" + str(port)] = slowContent["id"]
if (slowContent["duration"] / 1000) >= 20:
try:
push_telegram(errmsg)
except Exception as e:
print("An exception occurred:", e)
with open("check_rediscluster.log", "a+") as file_obj:
file_obj.write(errmsg)
tmpMax = int(slowContent["id"])
r.close()
conf.set("slowlog", "max_" + host + "_" + str(port), str(tmpMax))
with open("config.conf", "w+") as f:
conf.write(f)
tmpMax = 0
end_time = time.perf_counter()
print("Calculation takes {} seconds".format(end_time - start_time))
time.sleep(600)
print("sleep over")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
config.conf 配置文件用于记录当前记录已发送慢语句的数据统计,下次从该点往下加1,初始位置从1起
[slowlog]
max_<PUBLIC_IP>_8001 = 1
max_<PUBLIC_IP>_8002 = 1
max_<PUBLIC_IP>_9001 = 1
max_<PUBLIC_IP>_9002 = 1
max_<PUBLIC_IP>_9002 = 1
max_<PUBLIC_IP>_9001 = 1
max_<PUBLIC_IP>_8002 = 1
max_<PUBLIC_IP>_9001 = 1
max_<PUBLIC_IP>_8001 = 1
max_<PUBLIC_IP>_8001 = 1
max_<PUBLIC_IP>_9002 = 1
max_<PUBLIC_IP>_8002 = 1
2
3
4
5
6
7
8
9
10
11
12
13
14
该脚本里用到了redis包
默认pip3下载的版本较低,使用该版本安装redis-4.5.5
wget https://files.pythonhosted.org/packages/53/30/128c5599bc3fa61488866be0228326b3e486be34480126f70e572043adf8/redis-4.5.5.tar.gz
tar zxvf redis-4.5.5.tar.gz
cd redis-4.5.5/
python3 setup.py install
2
3
4
5
使用方法
python3 redis_alarm.py
systemctl 启动
[Unit]
Description=redisalarm
After=network.target
[Service]
Type=simple
PIDFile=/var/run/redisalarm.pid
WorkingDirectory=/data/script/
ExecStart=python3 /data/script/redis_alarm.py
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2. 抓包分析脚本
现场诊断时,如果 slowlog 告警触发但看不出明显原因(比如执行时间只有 20ms 刚过阈值,但客户端反馈"很慢"),就需要抓包看真实的消息流动。
redis-faina.py 点击下载 (opens new window)
# 2.1 抓包命令
# 在 Redis 服务器上执行,用 tcpdump 抓取 6379 端口的流量,写入日志文件
tcpdump -i eth0 port 6379 -s 0 -w /tmp/redis-capture.pcap &
# 或者用 redis-cli monitor 方式(注意:生产环境慎用,会显著拖慢实例)
redis-cli -h <REDIS_HOST> -p <REDIS_PORT> -a <REDIS_PASSWORD> -c monitor | head -n 99999 >> /tmp/1201-redis.log
2
3
4
5
# 2.2 使用 redis-faina.py 分析
python redis-faina.py /tmp/1201-redis.log
# 输出示例
Overall Stats
========================================
Lines Processed 99999
Commands/Sec 9302.46
Top Prefixes
========================================
spring 26850 (26.85%)
blacklist 21755 (21.76%)
agentstatus 15040 (15.04%)
agentuser 12378 (12.38%)
Top Keys
========================================
spring:session:expirations:1638353760000 7075 (7.08%)
topic_agent 3984 (3.98%)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.3 抓包的代价与限制
生产环境限时限量:在业务高峰期抓包,tcpdump 本身会消耗 CPU 和内存;如果磁盘 I/O 跟不上,抓包文件会快速膨胀甚至打满磁盘。建议用
-G参数限制抓包时长(如-G 60表示 60 秒后自动停止),用-C限制文件大小(如-C 100表示 100MB 后轮转)。-s截断长度影响:tcpdump -s 0抓完整包,但在还原命令时,如果 value 很大,redis-faina.py可能只能看到部分内容。分析大 value 相关问题时,需要结合INFO命令看客户端的resp版本或直接看十六进制。
# 3. 为什么 slowlog + 抓包需要配合使用
# 3.1 slowlog 的单位是微秒不是毫秒
slowlog-log-slower-than 的值是微秒(μs),不是毫秒(ms)。这是最常见的配置事故:设成 1000 以为记录"超过 1 秒"的查询,实际上只记录超过 1 毫秒的查询,瞬间触发大量日志。
# 查看当前阈值(单位:微秒)
CONFIG GET slowlog-log-slower-than
# 设成 10000 微秒 = 10 毫秒
CONFIG SET slowlog-log-slower-than 10000
2
3
4
5
# 3.2 slowlog-max-len 只是缓冲区大小
这个参数控制慢查询日志的最大条数,不是时间范围。它是一个环形缓冲区,新记录覆盖最旧的。设得太小会导致慢查询在环形 buffer 里还没被脚本读走就被覆盖;设得太大则消耗内存。
# 查看当前最大条数
CONFIG GET slowlog-max-len
# 设为 10000 条
CONFIG SET slowlog-max-len 10000
2
3
4
5
# 3.3 slowlog 只记录命令执行耗时
这是 slowlog 最大的盲区:不包含网络传输时间和排队等待时间。
- 网络传输:大 value 从 Redis 到客户端需要时间(比如一个 10MB 的 string,网卡传输可能耗时 50ms),这 50ms 不在 slowlog 里体现
- 排队等待:如果 Redis 主线程正在执行一个耗时命令,后面的命令会排队,这个排队时间也不计入单个命令的执行耗时
所以"slowlog 干净"不等于"客户端不慢"——这正是抓包的价值所在。
# 3.4 MONITOR 命令的坑
有人想用 redis-cli MONITOR 代替抓包,这是生产环境的坑。MONITOR 会把每个命令实时输出到连接里,对 Redis 实例的性能影响非常大(会显著拖慢主线程),生产环境绝对不要用。抓包是旁路采集,MONITOR 是请求处理的一部分,两者对性能的影响不是一个量级。
# 4. 验证:脚本部署完怎么确认真的在工作
# 4.1 验证慢查询告警链路
模拟慢查询:
CONFIG SET slowlog-log-slower-than 1000 # 设成 1000 微秒 = 1 毫秒 DEBUG SLEEP 0.1 # 故意沉睡 100 毫秒1
2检查 slowlog 有记录:
SLOWLOG GET 11应该有新记录,
duration显示约 100000 微秒。检查脚本输出: 运行脚本,观察日志或 Telegram/企微推送,应该能看到这条慢查询被捕获。
验证递增记录: 检查
config.conf,对应节点的max_<host>_<port>应该已经更新为刚才 slowlog 的 id。
# 4.2 验证抓包脚本
检查抓包文件有内容:
tcpdump -r /tmp/redis-capture.pcap | head -n 101或者如果用的是 redis-cli monitor 方式:
ls -lh /tmp/1201-redis.log1运行分析脚本:
python redis-faina.py /tmp/1201-redis.log1应该有命令统计输出,如果没有,说明抓到的数据为空或格式不对。
确认脚本在生成期间 Redis 响应正常:抓包时顺带观察
redis-cli PING的响应时间,确认抓包没有显著拖慢实例。
# 5. 坑与边界
slowlog 是内存里的环形队列,重启即失:slowlog 数据存在 Redis 内存中,没有持久化。Redis 重启后 slowlog 会清空,
id归零。脚本里的last_id可能远大于新产生的 id,导致漏报。解决:启动脚本时检测 Redis 启动时间,或把last_id重置为 0。slowlog 满环覆盖导致漏告:如果慢查询产生速度超过脚本检查频率(默认 600 秒一轮询),部分记录会被环形缓冲区覆盖而漏报。建议把
slowlog-max-len设大(如 10000),或缩短脚本检查间隔。config.conf 并发写冲突:如果脚本多实例运行,或手动编辑配置文件时脚本恰好在写,可能导致文件损坏。建议用文件锁(
flock)保护写操作,或把状态存到 Redis 本身。MONITOR 命令会显著拖慢实例:不要用 MONITOR 代替抓包,它是请求处理路径的一部分,会阻塞主线程。抓包是旁路采集,对业务影响可控。
抓包脚本跑在高并发实例上要限时限量:
tcpdump本身消耗 CPU 和 I/O,生产环境务必加-G(时间限制)和-C(文件大小限制)参数,否则磁盘会被打满。slowlog-log-slower-than单位是微秒:设值时注意量级差异,1000 = 1 毫秒,不是 1 秒。
# Agent 可直接解析的元数据块
{
"runbook": {
"task": "redis-slowlog-and-pcap-troubleshooting",
"permalinks": ["/pages/xxxsd2/"],
"category": "database/Redis",
"tags": ["redis", "slowlog", "troubleshooting", "pcap", "monitoring"],
"redis_versions": ["5.0", "6.0", "7.0"],
"verified_date": "2026-09"
}
}
2
3
4
5
6
7
8
9
10