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

624 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [Redis, 缓存, 运维, 性能调优]
create time: 2026-05-15 18:16
---
# Redis 运维与性能调优
## 概述
你把 Redis 装好、数据灌进去、代码跑通了——然后呢?
想象一下:某天凌晨 3 点,告警响了,接口 P99 从 2ms 飙到 2 秒。你登上服务器一看,Redis 进程还活着,`redis-cli ping` 也返回 PONG,但业务就是慢。问题出在哪?**如果你不知道该看哪些指标、不知道大 Key 的危害、不知道连接池怎么配,你只能靠猜。**
本节就是帮你建立"Redis 运维直觉"——从监控什么、怎么看,到常见问题怎么治。内容按照"先能发现问题 → 再会分析问题 → 最后能解决问题"的顺序组织。
## 一、监控指标体系
> [!QUESTION] 面试中常被问到:你怎么监控线上的 Redis?
> 一个好的回答不是背出 `INFO` 命令的所有字段,而是说出"我关注哪几个指标、为什么关注、异常了怎么处理"。这一节帮你建立这个体系。
### INFO 命令分级
```bash
# ──── 核心指标 ────
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)
```
### 关键指标解读
```bash
# 从 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 = 全部分离 |
### 副本延迟监控
```bash
# 从节点查看主从状态
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+)禁止从库写入
### 实时监控脚本
```bash
#!/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 服务端执行这条命令本身花了多久"。两者差异越大,说明问题越可能出在网络或客户端。
### 配置与分析
```bash
# 默认配置
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 索引 |
```bash
# 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
// 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 并不繁忙的场景:
```bash
# ──── 实时延迟采样(排查首选) ────
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 有多种方式,从轻到重依次是:
```bash
# 方式 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 的风险
```bash
# ⛔ 直接 DEL 大 key 会阻塞主线程!
DEL big_key
# ✅ 渐进式删除(SCAN + UNLINK)
UNLINK big_key # 异步删除(Redis 4.0+),立即返回
# 后台线程回收内存
```
> [!TIP] UNLINK vs DEL
> - `DEL`:同步删除,大 key 可能阻塞数十毫秒甚至秒级
> - `UNLINK`:异步删除,立即返回,后台回收(类似 GC)
> - 生产环境优先使用 `UNLINK`
## 四、连接池调优
### 为什么需要连接池?
没有连接池时,每个请求都要"三次握手 → 发命令 → 四次挥手"——就像每次去银行都要重新取号、排队、开户。连接池相当于你开了个 VIP 账户,提前建好一批连接"待命",请求来了直接复用,用完归还。
```mermaid
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 连接池配置
```go
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`(指数退避)
### 连接池选型决策树
```mermaid
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 设置
```conf
maxmemory 2gb
maxmemory-policy allkeys-lru
```
### 淘汰策略详解
> [!QUESTION] 没有永不超限的策略?
> 如果你不想丢数据,可以设 `noeviction`——但代价是写操作会直接报错。就像冰箱满了还硬塞,食物会掉地上。更好的做法是:**合理设置 maxmemory + 选择合适的淘汰策略**,提前决定"扔哪道菜"。
**淘汰策略选型——该扔哪道菜?**
```mermaid
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` 更合理。
### 内存碎片治理
什么是碎片?想象你往书架上放书、取书,取走后留下空隙。时间久了,书架明明有空位,但因为每段空位都太小,新书放不进去——这就是碎片。
```bash
# 查看碎片率(来自 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 的物理内存换出到磁盘,灾难性后果——延迟飙升到秒级。
>
> ```bash
> # 检查是否开启了 swap
> free -m
> # 如果 swapped > 0 → 立即处理!
> ```
## 六、备份与灾难恢复
> [!QUESTION] Redis 需要备份吗?不是有主从复制吗?
> 主从复制能防"机器挂了",但防不了"误删数据"。如果有人执行了 `FLUSHALL`,这个命令会同步到所有从库——数据瞬间全没了。**备份是最后一道防线。**
### 定时备份方案
```bash
#!/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
```
### 灾难恢复流程
```mermaid
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。
```bash
# 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 做体检"——先测一下各项指标的正常值,之后才知道哪里不好。
### 常用命令
```bash
# ──── 基础测试(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 应用中的压测替代方案
```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 本身没问题,再用应用级压测定位瓶颈
## 关联笔记
- [[hhs/Redis/04-RDB持久化]] — RDB 快照与备份
- [[hhs/Redis/05-AOF持久化]] — AOF 文件大小管理
- [[hhs/Redis/06-主从与哨兵]] — 主从复制与 Sentinel 高可用
- [[hhs/Redis/07-集群方案]] — 集群运维注意事项
- [[hhs/Redis/10-缓存架构模式]] — 缓存策略与淘汰策略选型
- [[hhs/Redis/README]] — 知识索引总览