Redis 从 0 到 1(二十一):P99 涨到 80ms,慢查询日志 5 步定位法 + Go 采集器

我不会起名字322 · 后端 / 算法 / 数据库

📘 技术栈 | 🧩 力扣 Hot100 | 🐹 Go 项目 | 🟥 Redis | 🐬 MySQL

文章目录

  • [Redis 从 0 到 1(二十一):P99 涨到 80ms,慢查询日志 5 步定位法 + Go 采集器](#Redis 从 0 到 1(二十一):P99 涨到 80ms,慢查询日志 5 步定位法 + Go 采集器)
    • [一、现象:P99 悄悄涨了 40 倍,没人知道为什么](#一、现象:P99 悄悄涨了 40 倍,没人知道为什么)
    • [二、配置与查看(收藏点 1)](#二、配置与查看(收藏点 1))
    • [三、5 步定位法(收藏点 2)](#三、5 步定位法(收藏点 2))
    • [四、Go 实操:slowlog 定时采集器(收藏点 3)](#四、Go 实操:slowlog 定时采集器(收藏点 3))
    • 五、命令清单
    • [六、排查步骤:P99 升高时的检查顺序](#六、排查步骤:P99 升高时的检查顺序)
    • 七、总结

Redis 从 0 到 1(二十一):P99 涨到 80ms,慢查询日志 5 步定位法 + Go 采集器

上一篇《Redis 从 0 到 1(二十):1 亿用户签到一年只占 44MB------Bitmap 的 Go 实现》我们收官了实战弧线,链接:https://blog.csdn.net/2501_92769340/article/details/167399826 。

本篇进入性能优化弧线,第一件事:先学会发现"慢"。

一、现象:P99 悄悄涨了 40 倍,没人知道为什么

监控曲线:Redis 平均延迟 3ms,很健康;但 P99 从 2ms 一路爬到 80ms,已有半个月。平均值被大量快命令稀释,只有尾部分位暴露了真相------有人在单线程里塞了慢命令(第 5 篇:单线程一条慢命令,全局排队)。

要抓它,不需要猜:Redis 自带慢查询日志(slowlog),把超过阈值的命令连参数记下来。问题只在于没人去看它。本篇给出"5 步定位法" + 一个 Go 定时采集器,把 slowlog 从"需要人肉记得查"变成"自动告警"。

二、配置与查看(收藏点 1)

conf 复制代码
# redis.conf
slowlog-log-slower-than 10000   # 阈值:10000 微秒 = 10ms;设 0 记录所有命令(排查期用)
slowlog-max-len 1024            # 最多记 1024 条,环形覆盖;生产别用默认 128,抓两次突刺就没了
bash 复制代码
SLOWLOG GET 10          # 看最近 10 条
SLOWLOG LEN             # 当前积压条数
SLOWLOG RESET           # 清空(采集器消费后可选)

# 每条格式:
# 1) id  2) 时间戳  3) 耗时(微秒)  4) [命令, 参数...]  5) 客户端IP

怎么读数据:耗时 除以 1000 得毫秒;命令+参数 是元凶本身------尤其注意参数:同样 HGETALL,小 Hash 0.1ms,百万 field 的 Hash 能到 500ms(第 22 篇主角)。

三、5 步定位法(收藏点 2)

步骤 动作 产出
1. 采集 定时拉 SLOWLOG(见下一节采集器),阈值告警触发即拉 原始慢命令列表
2. 排序 按出现次数倒序,看"频率 × 耗时"双高项 嫌疑名单(TOP3~5)
3. 归类 按命令类型归类:HGETALL 类全量读?KEYS 类扫描?DEL 大 key? 成因类别(决定治理方向)
4. 复现 在测试库执行同参数命令,MEMORY USAGE 看 key 大小 实锤:该 key 多大、多慢
5. 治理 换命令(HGET→HMGET 指定字段)、拆 key、加索引结构、限流 改动清单 + 回归验证

最常见的三类元凶与治理:

元凶 特征 治理
大 Hash/Set 全量读(HGETALL/SMEMBERS) 命令固定、耗时随数据量涨 改 HSCAN 渐进读或只取需要的字段
KEYS * 类扫描(或 SMEMBERS 大集合) 偶发、耗时极长 禁命令(rename-command)+ 改 SCAN
DEL 大 key 与运维操作时间吻合 改 UNLINK 或分批渐进删(第 22 篇)

四、Go 实操:slowlog 定时采集器(收藏点 3)

go 复制代码
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/redis/go-redis/v9"
	"redis-0to1/redisx"
)

type SlowEntry struct {
	ID       int64
	Time     time.Time
	Duration time.Duration
	Cmd      []string
	Client   string
}

// FetchSlowLog 拉取慢日志并解析(go-redis 返回 []interface{} 嵌套,手动解包)
func FetchSlowLog(ctx context.Context, rdb *redis.Client, n int64) ([]SlowEntry, error) {
	raw, err := rdb.SlowLogGet(ctx, n).Result()
	if err != nil {
		return nil, err
	}
	entries := make([]SlowEntry, 0, len(raw))
	for _, e := range raw {
		// e 结构:[id, timestamp, duration_us, [cmd args], client, name]
		id, _ := e[0].(int64)
		ts, _ := e[1].(int64)
		dur, _ := e[2].(int64)
		cmdArgs := e[3].([]interface{})
		cmd := make([]string, len(cmdArgs))
		for i, a := range cmdArgs {
			cmd[i] = fmt.Sprint(a)
		}
		entries = append(entries, SlowEntry{
			ID: id, Time: time.Unix(ts, 0),
			Duration: time.Duration(dur) * time.Microsecond, Cmd: cmd,
		})
	}
	return entries, nil
}

func main() {
	rdb, err := redisx.NewClient(redisx.Config{Addr: "127.0.0.1:6379", Password: "redis123", PoolSize: 5})
	if err != nil {
		panic(err)
	}
	ctx := context.Background()

	// 每 60 秒拉一次,超过 50ms 的慢命令打告警日志(接告警系统就换成 webhook)
	ticker := time.NewTicker(60 * time.Second)
	defer ticker.Stop()
	for range ticker.C {
		entries, err := FetchSlowLog(ctx, rdb, 20)
		if err != nil {
			log.Printf("拉取 slowlog 失败: %v", err)
			continue
		}
		for _, e := range entries {
			if e.Duration > 50*time.Millisecond {
				log.Printf("⚠️ 慢命令: %v 耗时=%v", e.Cmd, e.Duration)
				// 这里接告警:钉钉/企业微信 webhook,或写入时序库做趋势
			}
		}
		rdb.SlowLogReset(ctx) // 已消费的清掉,防 LEN 顶满丢新数据
	}
}

要点:采集器本身要用独立的、池很小的连接(避免和业务抢连接);消费后 RESET 防止环形缓冲覆盖新数据;按分钟聚合告警而不是按条(同一条慢命令刷 100 次只报一次)。

五、命令清单

命令 作用
SLOWLOG GET/LEN/RESET 查 / 数 / 清
CONFIG SET slowlog-log-slower-than 5000 在线调阈值(不用重启)
LATENCY DOCTOR 服务端延迟事件诊断(比 slowlog 更底层,看系统级卡顿)
INFO commandstats 各命令累计耗时(第 29 篇 CPU 排查的主角)

六、排查步骤:P99 升高时的检查顺序

  1. SLOWLOG GET 10 直接看有没有慢命令(5 秒出结果);
  2. 没有慢命令但 P99 高 → 看 LATENCY DOCTOR,可能是 fork、AOF fsync 等系统级事件(第 29/30 篇);
  3. 有慢命令 → 5 步定位法走一遍,归类治理;
  4. 治理后回归:压测同参数命令,确认 P99 回落。

七、总结

  1. 平均值会骗你,P99 才暴露慢命令;slowlog 是现成的证据库;
  2. 5 步定位法:采集 → 排序 → 归类 → 复现 → 治理;
  3. 慢命令三类元凶:大 key 全量读、扫描类命令、DEL 大 key;
  4. 采集器自动化:定时拉取 + 阈值告警 + 消费后 RESET。

下一篇预告:慢命令里最常见的元凶是 bigkey------下一篇《一个 500MB 的 Hash 拖垮实例------bigkey 的 Go 渐进删除》,我们讲怎么安全地找到它、删掉它、并且永远不再养出它。

相关推荐
挖掘狂人1 小时前
Redis 为什么快?把 "存内存" 这句话拆开看
redis·后端·性能优化
codigger2 小时前
Redis 为什么快?把 "存内存" 这句话拆开看
redis·后端·性能优化·程序员
我不会起名字3222 小时前
Redis 从 0 到 1(十八):List、Pub/Sub、Stream 为什么生产只用 Stream?Go 实操
redis·消息队列·go·stream·pub/sub
你看我名字2 小时前
Redis 高可用、持久化与故障切换:从缓存组件到云上基础设施
数据库·redis·缓存
孔令飞2 小时前
AI 岗位类别、要求、技能及学习路线
云原生·go·ai编程
秋饼2 小时前
Redis 全面解析:从数据结构到主从/哨兵/集群、分布式锁与面试高频考点,代码到原理一次讲透
数据库·redis·分布式·面试·后端开发
二月夜2 小时前
Redis 高频面试题全解:缓存三大问题、大 key 治理与分布式锁之争(2026 招聘 JD 真题复盘)
redis·分布式·后端·缓存
樱花落木兰3 小时前
Redis 消息队列的可靠性演进:从 List、Pub/Sub 到 Stream 消费者组,以及 8.x 版本改变了什么
java·开发语言·经验分享·redis·分布式·python·软件工程