Files
cs-note/hhs/Redis/11-运维与性能调优.md
T
2026-06-08 23:08:57 +08:00

27 KiB
Raw Blame History

tags, create time
tags create time
Redis
缓存
运维
性能调优
2026-05-15 18:16

Redis 运维与性能调优

概述

你把 Redis 装好、数据灌进去、代码跑通了——然后呢?

想象一下:某天凌晨 3 点,告警响了,接口 P99 从 2ms 飙到 2 秒。你登上服务器一看,Redis 进程还活着,redis-cli ping 也返回 PONG,但业务就是慢。问题出在哪?如果你不知道该看哪些指标、不知道大 Key 的危害、不知道连接池怎么配,你只能靠猜。

本节就是帮你建立"Redis 运维直觉"——从监控什么、怎么看,到常见问题怎么治。内容按照"先能发现问题 → 再会分析问题 → 最后能解决问题"的顺序组织。

一、监控指标体系

[!QUESTION] 面试中常被问到:你怎么监控线上的 Redis? 一个好的回答不是背出 INFO 命令的所有字段,而是说出"我关注哪几个指标、为什么关注、异常了怎么处理"。这一节帮你建立这个体系。

INFO 命令分级

# ──── 核心指标 ────
INFO server          # 版本、进程 ID、启动时间、运行天数
INFO clients         # 连接数、阻塞客户端数、写入缓冲区
INFO memory          # 内存使用量、峰值、碎片率 ← **必看**
INFO stats           # 命中率、命令数、fork 耗时 ← **必看**
INFO persistence     # RDB/AOF 状态 ← **必看**
INFO replication     # 主从关系、延迟 ← **必看**
INFO commandstats    # 各命令调用量和耗时排名

# ──── 辅助指标 ────
INFO keyspace        # 每个 DB 的 key 数量和 TTL 统计
INFO cpu             # CPU 消耗
INFO errorstats      # 错误统计
INFO cluster         # 集群拓扑(仅 Cluster)
INFO sentinel        # Sentinel 信息(仅 Sentinel)

关键指标解读

# 从 INFO stats 提取的核心指标
keyspace_hits: 987654       # 缓存命中次数
keyspace_misses: 12346      # 缓存未命中次数
# → 命中率 = 987654 / (987654 + 12346) = 98.76%

used_memory_human: 1.23G    # 已用内存
used_memory_rss_human: 1.35G # OS 分配给 Redis 进程的物理内存(含碎片)
mem_fragmentation_ratio: 1.09 # used_memory_rss / used_memory = 碎片率

指标详解:

  • 命中率 = keyspace_hits / (keyspace_hits + keyspace_misses):请求在 Redis 中找到目标 Key 的比例。命中率低于 80% 说明缓存层未能有效拦截请求,大量请求直接打到后端数据库。排查方向:TTL 是否设太短、热点 Key 是否分布不均、缓存预热是否已执行。
  • mem_fragmentation_ratio = used_memory_rss / used_memory:操作系统为 Redis 分配的物理内存(RSS)与 Redis 自身申请的内存之比。> 1.5 表示内存碎片严重,物理内存浪费明显,建议开启 activedefrag;< 1.0 表示 RSS 小于申请量,说明操作系统将部分 Redis 内存换出到了磁盘(swap),访问时会产生磁盘 I/O,延迟从微秒级飙升到毫秒甚至秒级。
  • rejected_connections:被 Redis 拒绝的连接数。正常值始终为 0;一旦大于 0,说明当前连接数已超过 maxclients 上限,需要调大该配置或排查连接泄漏问题。
  • total_commands_processed:Redis 累计处理的命令总数,除以运行秒数可得平均 QPS;也可直接查看 instantaneous_ops_per_sec 获取实时 QPS。数值平稳增长说明服务正常;若突然断崖式下跌,通常是主线程被阻塞所致,常见原因包括大 Key 操作、fork 子进程、AOF fsync 等。
  • connected_slaves:当前连接的从节点数量。例如配了一主两从,此处应为 2;若变为 0,说明所有副本失联,读写分离和高可用机制均失效,需立即排查主从复制链路。

[!QUESTION] 碎片率 1.2 和 0.8,哪个更危险? 很多人直觉会觉得 1.2(碎片多)更严重,但实际上 0.8 才是最危险的信号——它意味着 Redis 进程的内存已经被操作系统换出到磁盘(swap),性能会断崖式下降。碎片高只是浪费内存,swap 却是"要命"的事。

健康标准速查

指标 正常范围 警告阈值
命中率 > 90% < 80% 需排查
mem_fragmentation_ratio 1.0 ~ 1.5 > 1.5 内存浪费,< 1.0 swap 风险
rejected_connections 0 > 0 说明 maxclients 不够
total_commands_processed/s 平稳增长 断崖式下跌说明异常
connected_slaves ≥ 预期 0 = 全部分离

副本延迟监控

# 从节点查看主从状态
INFO replication
# role:slave / master_host / master_port / master_link_status(up/down)
# master_last_io_seconds_ago, master_sync_in_progress

# 关键指标:master_repl_offset - slave_repl_offset
# → 差值即为延迟字节数,超过 1MB 需关注

[!WARNING] 副本延迟常见原因

  • 单线程写放大:主库高并发写入时,从库网络 IO 成为瓶颈
  • 大 Key 持久化:BGSAVE/BGREWRITEAOF 期间从库全量同步会短暂中断服务
  • 解决方案:配置多个从库分担读取流量 + 使用 replica-read-only yes(Redis 5.0+)禁止从库写入

实时监控脚本

#!/bin/bash
HOST=${1:-127.0.0.1}
PORT=${2:-6379}

echo "=== Memory ==="
redis-cli -h $HOST -p $PORT INFO memory \
  | grep -E "used_memory_human|maxmemory_human|mem_fragmentation_ratio"

echo "=== Stats ==="
redis-cli -h $HOST -p $PORT INFO stats \
  | grep -E "keyspace_hits|keyspace_misses|instantaneous_ops_per_sec"

echo "=== Clients ==="
redis-cli -h $HOST -p $PORT INFO clients \
  | grep -E "connected_clients|blocked_clients"

echo "=== Persistence ==="
redis-cli -h $HOST -p $PORT INFO persistence \
  | grep -E "aof_enabled|rdb_last_bgsave_status"

二、慢查询日志

上一节的监控指标能告诉你"Redis 整体状态如何",但如果你想定位"是哪条命令拖慢了 Redis",就需要慢查询日志了。它的原理很简单:Redis 在主线程执行每条命令,如果某条命令的执行时间超过阈值,就记录下来。

[!QUESTION] 慢查询日志 vs 应用日志? 应用日志记录的是"你的代码花了多久调用 Redis"(包含网络延迟、序列化开销),慢查询日志记录的是"Redis 服务端执行这条命令本身花了多久"。两者差异越大,说明问题越可能出在网络或客户端。

配置与分析

# 默认配置
CONFIG GET slowlog-log-slower-than    # 默认 10000 μs (10ms)
CONFIG GET slowlog-max-len            # 默认 128 条

# 调整为 5ms 阈值
CONFIG SET slowlog-log-slower-than 5000

# 扩大存储容量
CONFIG SET slowlog-max-len 2048

# 查看慢查询
SLOWLOG GET 10

# 重置
SLOWLOG RESET

典型慢查询根因

慢查询 根因 修复方案
KEYS * 全库扫描 O(N) 改用 SCAN
HGETALL large_hash 大 Hash 全量返回 HSCAN 分页
SMEMBERS huge_set 大集合全量遍历 SSCAN 分页
LRANGE 0 -1 (百万条 List) 全列表拉取 LRANGE 指定范围
无索引的 DB 查询(在 Lua 中) DB 层问题 加 DB 索引
# SLOWLOG GET 输出字段说明
1) (integer) 14              # 慢查询 ID(递增)
2) (integer) 1716620400      # 发生时间(Unix 时间戳)
3) (integer) 13456           # 耗时(微秒)
4) 1) "KEYS"                 # 命令及参数
   2) "*"
5) "127.0.0.1:53412"         # 客户端地址
6) ""                        # 客户端名称
// go-redis 中的 SlowLog 接口
entries, err := rdb.SlowLogGet(ctx, 10).Result()
if err != nil {
    log.Fatal(err)
}
for _, e := range entries {
    fmt.Printf("ID: %d, Time: %s, Cmd: %s, Duration: %v\n",
        e.ID, e.Time.Format(time.RFC3339), e.Args, e.Duration)
}

延迟诊断工具

Redis 提供了内建的延迟诊断能力,定位"客户端感知慢"但服务端 CPU 并不繁忙的场景:

# ──── 实时延迟采样(排查首选) ────
redis-cli --latency                    # 持续输出 min/avg/max(ms)
redis-cli --latency-history -i 5       # 每 5 秒输出一次统计

# ──── 延迟分布直方图 ────
redis-cli --latency-dist               # 交互式彩色直方图,快速定位延迟区间

# ──── 事件驱动延迟监控(需服务端配置) ────
# 在 redis.conf 中配置延迟阈值(微秒)
# latency-monitor-threshold 10

# 之后查看:
LATENCY LATEST              # 最近的延迟事件
LATENCY HISTORY command     # 某命令的延迟历史
LATENCY RESET               # 重置统计数据

[!TIP] 延迟排查三板斧

  1. redis-cli --latency — 先看基线是否正常(正常 P99 < 1ms)
  2. LATENCY LATEST — 看是否有异常事件(fork、大 Key 删除、AOF fsync)
  3. INFO commandstats — 按命令耗时排序,定位慢命令

三、大 Key 治理

如果慢查询是"某一顿饭做得太慢",大 Key 就是"你端了一整头牛上来"——不管厨师多快,处理这道菜都要比小菜慢得多。而且大 Key 不光影响自己,还会堵住后面所有命令(因为 Redis 是单线程的)。

什么是大 Key?

简单说就是 value 太大的 Key。业界的经验阈值:字符串 value ≥ 1MB,集合类(Hash / Set / ZSet / List)元素数 > 5000 或总大小 > 10MB。

[!QUESTION] 大 Key 是怎么产生的? 大多数大 Key 不是一开始就设计成这样的,而是"长"出来的——比如一个 List 本来只有几十条记录,业务不断 append 从不清理,半年后变成几百万条。这就像你家阳台的杂物间,"就放一点",最后堆满了。

[!WARNING] 大 Key 的危害

  • 网络阻塞:SET/GET 一个 1MB 的值占满一条 TCP 连接
  • 过期事件堆积:大量大 key 同时过期时一次性触发删除,阻塞主线程
  • 分片迁移卡顿:Cluster 迁移 slot 时需要复制整个值
  • RDB/AOF 膨胀:持久化文件急剧增大

检测工具

发现大 Key 有多种方式,从轻到重依次是:

# 方式 1: redis-cli --bigkeys(按元素数量采样扫描)
redis-cli --bigkeys -n 1000

# 输出示例:
# --- SUMMARY ---
# Size       Count  Type        Keys
# 1.23MB     1      string      user:avatar:big_image
# 456KB      3      hash        order:items:*
# ...

# 方式 2: redis-cli --memkeys(按实际内存占用扫描,更精确)
redis-cli --memkeys -n 1000
# → 会输出每个 key 的内存大小(字节),适合发现大 value 的 string key

# 方式 3: redis-rdb-tools(RDB 离线分析,最全面)
rdb -c memory dump.rdb | sort -k2 -rn | head -20

# 方式 4: RedisInsight / AnotherRedisDesktopManager GUI

[!NOTE] --bigkeys vs --memkeys

  • --bigkeys:按元素数量排序,擅长发现超大 Hash / Set / ZSet
  • --memkeys:按实际内存占用排序,能精准发现大 value 的 String key
  • 两者都基于 SCAN,不会阻塞生产环境;建议配合使用覆盖所有场景

拆分策略

核心思路:把一头牛切成小块上桌。常见的拆分方式是按业务维度分片:

原始大 Key:
order:12345 -> hash{ item1, item2, ..., item5000 }  ← 5000 个订单项塞在一个 Hash 里

拆分后:
order:12345:meta  -> hash{ status, amount, ... }     ← 订单元数据(小)
order:12345:items:0 -> list[ item1~item100 ]         ← 按 100 个一组分片
order:12345:items:1 -> list[ item101~item200 ]
...

读取时先读 meta,再按需读取对应分片——大多数场景只需要第一页数据

DEL 大 Key 的风险

# ⛔ 直接 DEL 大 key 会阻塞主线程!
DEL big_key

# ✅ 渐进式删除(SCAN + UNLINK)
UNLINK big_key        # 异步删除(Redis 4.0+),立即返回
                        # 后台线程回收内存

[!TIP] UNLINK vs DEL

  • DEL:同步删除,大 key 可能阻塞数十毫秒甚至秒级
  • UNLINK:异步删除,立即返回,后台回收(类似 GC)
  • 生产环境优先使用 UNLINK

四、连接池调优

为什么需要连接池?

没有连接池时,每个请求都要"三次握手 → 发命令 → 四次挥手"——就像每次去银行都要重新取号、排队、开户。连接池相当于你开了个 VIP 账户,提前建好一批连接"待命",请求来了直接复用,用完归还。

flowchart LR
    subgraph APP["应用进程"]
        G1["Goroutine 1"] --> Pool["连接池"]
        G2["Goroutine 2"] --> Pool
        G3["Goroutine 3"] --> Pool
    end
    subgraph POOL["连接池内部"]
        C1["连接 1 (忙碌)"]
        C2["连接 2 (空闲)"]
        C3["连接 3 (空闲)"]
    end
    Pool --> POOL
    POOL --> Redis["Redis Server"]

    style C1 fill:#fdd,stroke:#900
    style C2 fill:#dfd,stroke:#090
    style C3 fill:#dfd,stroke:#090

[!QUESTION] 连接池设多大才合适? 不是越大越好。每个连接都要占一个文件描述符和内存。连接太多 = 排队的人比窗口多,反而增加上下文切换开销。一般的经验值:PoolSize = CPU 核心数 × 2 ~ 5,大多数 Web 应用 50~100 就够了。

Go go-redis 连接池配置

rdb := redis.NewClient(&redis.Options{
    Addr:     "localhost:6379",
    Password: "",
    DB:       0,

    // ──── 连接池参数(go-redis v9) ────
    // 类比:连接池就像餐厅的桌子——PoolSize 是最大桌数,MinIdleConns 是
    // 每天开业前至少要摆好的桌子数,ConnMaxIdleTime 是桌子空闲多久后收掉
    PoolSize:        100,              // 最多同时维持 100 个连接(并发 Goroutine 很多时调大)
    MinIdleConns:    10,               // 启动时预先建 10 个连接,避免冷启动延迟
    ConnMaxIdleTime: 5 * time.Minute,  // 空闲超过 5 分钟的连接自动关闭,释放资源
    ConnMaxLifetime: 0,                // 连接最大存活时间,0 = 不限制(通常保持默认即可)

    // ──── 超时参数 ────
    // 这些超时是你的"止损线"——如果 Redis 出问题,最多等多久就放弃
    DialTimeout:  5 * time.Second,     // 建连超时:Redis 宕机时不会永远卡住
    ReadTimeout:  3 * time.Second,     // 读超时:普通命令 3s 足够,大 Key 操作需调大
    WriteTimeout: 3 * time.Second,     // 写超时:同上
    PoolTimeout:  1 * time.Second,     // 从池里拿连接的等待时间:超时说明池子太小或 Redis 慢

    // ──── 重试策略 ────
    MaxRetries:      3,                // 最多重试 3 次(网络抖动时自动恢复)
    MinRetryBackoff: 200 * time.Millisecond, // 第一次重试等 200ms
    MaxRetryBackoff: 1 * time.Second,        // 最长等 1s(指数退避,不会一直刷)
})

[!NOTE] go-redis v8 → v9 参数变化

  • MaxIdle / MaxActive → 合并为 PoolSize,MinIdleConns 控制预热
  • IdleTimeout → ConnMaxIdleTime
  • PingPeriod 无对应字段,go-redis 内部通过健康检查自动保活
  • RetryDelay → MinRetryBackoff + MaxRetryBackoff(指数退避)

连接池选型决策树

flowchart TD
    A["预估 QPS"] -->|"< 500"| S["PoolSize: 20, MinIdleConns: 5"]
    A -->|"500 ~ 5000"| M["PoolSize: 50, MinIdleConns: 20"]
    A -->|"\> 5000"| L["PoolSize: 100\~200, MinIdleConns: 30~50"]

    B{"有无 Pipeline?"}
    B -->|"是"| C["增加 30% 连接数"]
    B -->|"否"| D["保持上述配置"]

    style L fill:#fdd,stroke:#900

[!TIP] PoolSize 计算公式

PoolSize = N × (1 + CPU等待时间/CPU计算时间)

其中 N 为 Goroutine 数量。对于大多数 Web 应用,50~100 已经足够——如果频繁出现 PoolTimeout,优先考虑排查慢查询或大 Key 问题。

[!WARNING] 连接池常见问题

  • PoolSize 太小:context deadline exceeded(PoolTimeout)频繁出现
  • PoolSize 太大:创建过多 TCP 连接,文件描述符耗尽
  • ReadTimeout 太短:大数据量操作(如 HGETALL 大 hash)被误杀
  • 不设 DialTimeout:Redis 宕机时请求永远挂起

五、内存管理与淘汰策略

前面讲了怎么发现大 Key、怎么配置连接池,现在来说说一个更根本的问题:Redis 内存用完了怎么办?

打个比方:你的 Redis 就像一个固定大小的冰箱。maxmemory 是冰箱的容量,淘汰策略决定了"新菜放不下的时候,扔掉哪道旧菜"。

maxmemory 设置

maxmemory 2gb
maxmemory-policy allkeys-lru

淘汰策略详解

[!QUESTION] 没有永不超限的策略? 如果你不想丢数据,可以设 noeviction——但代价是写操作会直接报错。就像冰箱满了还硬塞,食物会掉地上。更好的做法是:合理设置 maxmemory + 选择合适的淘汰策略,提前决定"扔哪道菜"。

淘汰策略选型——该扔哪道菜?

flowchart TD
    Start["冰箱满了, 该扔哪道菜?"] --> Q1{"所有数据都能丢吗?"}
    Q1 -->|"不能丢, 如配置中心"| Noevict["noeviction<br/>满了就报错, 不扔"]
    Q1 -->|"可以丢"| Q2{"数据都设了 TTL 吗?"}
    Q2 -->|"是, 都可以过期"| TTL["volatile-ttl<br/>优先扔最快过期的"]
    Q2 -->|"否, 混合存储"| Q3{"访问频率差异大吗?<br/>二八定律明显"}
    Q3 -->|"是, 有明显冷热分离"| LFU["allkeys-lfu<br/>优先扔访问次数最少的"]
    Q3 -->|"否, 访问比较均匀"| LRU["allkeys-lru<br/>优先扔最久没被访问的, 默认推荐"]

    style LRU fill:#dfd,stroke:#090
    style Noevict fill:#fdd,stroke:#900

完整策略对照:

策略 作用范围 淘汰算法 适用场景
allkeys-lru 所有 key LRU(近似) 通用缓存 ✅ 最常用,拿不准就选它
allkeys-lfu 所有 key LFU(频次统计) Redis 4.0+,热点数据语义更强的场景
volatile-lru 仅设 TTL 的 key LRU(近似) 混合存储:永不过期的配置 + 可淘汰的缓存
volatile-lfu 仅设 TTL 的 key LFU(频次统计) 混合存储 + 频次敏感淘汰
volatile-ttl 仅设 TTL 的 key TTL 最短优先 让数据自然过期的场景
noeviction — — 数据不可丢失,满了返回错误(如配置中心)

[!NOTE] LRU vs LFU——"最久没用" vs "最少被用"

  • LRU(Least Recently Used):图书馆里"最久没被借走的书先下架"。只要最近被借过就不会被淘汰,哪怕只借了一次。实现简单,内存开销小。
  • LFU(Least Frequently Used):图书馆里"被借次数最少的书先下架"。一本冷门书即使昨天刚被借过,如果历史总次数少也可能被淘汰。每 key 多消耗 16 bits 记录频率,更精准但略贵。
  • 实际选型:不确定就先用 allkeys-lru;如果存在明显"二八定律"(20% 数据占 80% 访问),allkeys-lfu 更合理。

内存碎片治理

什么是碎片?想象你往书架上放书、取书,取走后留下空隙。时间久了,书架明明有空位,但因为每段空位都太小,新书放不进去——这就是碎片。

# 查看碎片率(来自 INFO memory 输出)
# mem_fragmentation_ratio = used_memory_rss / used_memory
# 正常范围: 1.0 ~ 1.5

# 配置激活在线碎片整理(Redis 4.0+,仅在 Linux 上有效)
# 相当于请了一个"图书管理员",后台慢慢把书架缝隙整理好
activedefrag yes
active-defrag-threshold-lower 10    # 碎片率 > 10% 时启动整理
active-defrag-threshold-upper 50    # 碎片率 > 50% 时全力整理(CPU 占用也会升高)
active-defrag-cycle-min 5           # 每次整理最多占 CPU 5%(不影响正常服务)
active-defrag-cycle-max 25          # 全力整理时最高占 CPU 25%

[!QUESTION] 为什么 fragmentation ratio < 1.0? 表示 RSS < used_memory——这意味着发生了 swap。操作系统把 Redis 的物理内存换出到磁盘,灾难性后果——延迟飙升到秒级。

# 检查是否开启了 swap
free -m
# 如果 swapped > 0 → 立即处理!

六、备份与灾难恢复

[!QUESTION] Redis 需要备份吗?不是有主从复制吗? 主从复制能防"机器挂了",但防不了"误删数据"。如果有人执行了 FLUSHALL,这个命令会同步到所有从库——数据瞬间全没了。备份是最后一道防线。

定时备份方案

#!/bin/bash
# backup.sh — 每天凌晨 2 点执行
BACKUP_DIR="/backup/redis"
DATE=$(date +%Y%m%d_%H%M%S)

# 触发 BGSAVE
redis-cli BGSAVE

# 等待完成(轮询 LASTSAVE)
sleep 5
cp /var/lib/redis/dump.rdb "$BACKUP_DIR/dump_$DATE.rdb"
gzip "$BACKUP_DIR/dump_$DATE.rdb"

# 保留最近 30 天的备份
find $BACKUP_DIR -mtime +30 -delete

# 上传到对象存储
aws s3 cp "$BACKUP_DIR/dump_$DATE.rdb.gz" "s3://your-bucket/redis-backup/"

Crontab:

0 2 * * * /path/to/backup.sh >> /var/log/redis-backup.log 2>&1

灾难恢复流程

flowchart TD
    A["数据异常或丢失"] --> B{"可用最近的备份?"}
    B -->|"是"| C["停止写入,防止覆盖"]
    C --> D["恢复 RDB/AOF 备份"]
    D --> E["检查数据完整性"]
    E --> F{"数据是否完整?"}
    F -->|"是"| G["恢复写入,观察指标"]
    F -->|"否"| H{"有增量日志?"}
    H -->|"AOF"| I["附加修复 AOF 文件<br/>redis-check-aof --fix"]
    H -->|"无"| J["从主库全量同步"]
    I --> E
    J --> E
    B -->|"否"| K["从主库重新同步"]
    K --> E
    
    style A fill:#fdd,stroke:#900
    style G fill:#dfd,stroke:#090

云厂商迁移工具

来源 目标 工具
自建 Redis AWS ElastiCache elasticache-migration-tool
自建 Redis Aliyun ApsaraDB redis-shake
Redis Cluster 云 Redis 云控制台导入功能
Redis to Redis 任意 redis-shake

七、安全加固清单

Redis 默认配置是"裸奔"的——没有密码、绑定所有网卡、所有命令都能执行。上线前必须加固,就像搬进新家要换锁、装窗帘一样。

[!todo] 生产环境安全核对

  • requirepass 已设置强密码
  • rename-command FLUSHALL "" — 重命名危险命令(或设为空禁用)
  • rename-command DEBUG "" — 禁用 debug 命令
  • 绑定内网 IP 而非 0.0.0.0
  • TLS/SSL 加密传输(Redis 6.0+ 支持)
  • 定期更新版本号,修复已知 CVE
  • ACL 用户权限控制(Redis 6.0+ 支持多用户角色)

[!WARNING] rename-command 已在 Redis 6.2+ 中废弃 新版本推荐使用 ACL 精细控制每个用户的命令权限(见下方示例),rename-command 仅作为旧版本的兼容方案保留。新建项目应直接使用 ACL。

# ACL 示例(Redis 6.0+)
ACL SETUSER readonly on >readOnlyPass123 ~cached:* &* +get +mget +scan +ping
ACL SETUSER writer on >writePass123 ~* &* +set +hset +lpush +sadd +incr +del +ping
ACL LIST   # 查看所有用户
ACL WHOAMI # 当前用户

八、性能基准测试

[!QUESTION] 为什么要跑压测?调完参数凭感觉不行吗? 不行。没有数据的调优就是瞎猜。压测的价值在于:给你一个基线数字,之后所有优化都能对比。"改了连接池大小后 QPS 从 5 万涨到 8 万"——这才是有说服力的调优。

redis-benchmark 是 Redis 自带的压测工具,不需要安装任何依赖,能快速摸底单实例或集群的吞吐上限。把它想成"给 Redis 做体检"——先测一下各项指标的正常值,之后才知道哪里不好。

常用命令

# ──── 基础测试(100 并发,50 请求,只测 SET/GET) ────
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 50000 -t set,get

# ──── 模拟真实 Pipeline 场景 ────
redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 100000 -P 16 -t set,get
# -P 16 表示每个连接一次发 16 条命令(Pipeline),大幅提升吞吐

# ──── 测试特定命令和数据大小 ────
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 50000 \
  -t set,get \
  -d 256 \        # value 大小 256 bytes(默认 3 bytes,偏小)
  -r 100000 \     # 使用 10 万个随机 key,避免热点
  --csv           # CSV 输出,方便脚本采集

输出字段解读

====== SET ======
  50000 requests completed in 0.45 seconds   ← 总耗时
  100 parallel clients                       ← 并发数
  3 bytes payload                            ← 数据大小
  keep alive: 1                              ← 长连接
  multi-thread: no                           ← 单线程模式

111111.11 requests per second               ← 吞吐量(QPS)
Latency by percentile distribution:
0.000% <= 0.103 milliseconds               ← P0(最快)
50.000% <= 0.199 milliseconds               ← P50
99.000% <= 0.511 milliseconds               ← P99
99.900% <= 0.831 milliseconds               ← P999
100.000% <= 1.247 milliseconds              ← P100(最慢)

[!TIP] 压测注意事项

  • 别在生产环境跑:压测会把 Redis 跑满,影响线上请求
  • -r 参数很重要:默认所有请求打同一个 key,数据不真实;加 -r 让 key 随机化
  • -d 参数要贴近真实:默认 3 bytes,你的业务 value 可能是几百字节到几 KB,差异很大
  • Pipeline 是最大加速器:-P 16 可以让 QPS 提升 5~10 倍,生产环境务必启用 Pipeline

Go 应用中的压测替代方案

// 用 vegeta 或 wrk 压测你的 HTTP 接口,比 redis-benchmark 更贴近真实场景
// redis-benchmark 只测 Redis 本身,不包含:连接池等待、序列化开销、业务逻辑

// 如果想精确测 go-redis 连接池表现,可以用 benchmark 测试函数:
func BenchmarkRedisSet(b *testing.B) {
    rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
    defer rdb.Close()
    ctx := context.Background()

    b.ResetTimer()
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            rdb.Set(ctx, "bench:key", "value", 0)
        }
    })
}

[!NOTE] redis-benchmark vs 应用级压测

  • redis-benchmark:测 Redis 服务端极限吞吐,排除网络因素,适合摸底
  • 应用级压测(vegeta / wrk / k6):测端到端链路,包含连接池、序列化、业务逻辑,更贴近真实
  • 建议两者结合:先用 redis-benchmark 确认 Redis 本身没问题,再用应用级压测定位瓶颈

关联笔记