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

441 lines
14 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 不是一装就跑——你需要持续监控、定期清理大 Key、合理配置连接池、分析慢查询。本节覆盖日常运维中最常见的问题和调优方法。
## 一、监控指标体系
### 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 感知内存
mem_fragmentation_ratio: 1.09 # RSS / used = 碎片率
# ──── 健康标准 ────
| 指标 | 正常范围 | 警告阈值 |
|------|---------|---------|
| 命中率 | > 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"
```
## 二、慢查询日志
### 配置与分析
```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?
通常指超过 **5KB** 的单个 Key(字符串除外,字符串可达数百 MB)。
> [!WARNING] 大 Key 的危害
> - **网络阻塞**:SET/GET 一个 1MB 的值占满一条 TCP 连接
> - **过期事件堆积**:大量大 key 同时过期时一次性触发删除,阻塞主线程
> - **分片迁移卡顿**:Cluster 迁移 slot 时需要复制整个值
> - **RDB/AOF 膨胀**:持久化文件急剧增大
### 检测工具
```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-rdb-tools (RDB 离线分析)
rdb -c memory dump.rdb | sort -k2 -rn | head -20
# 方式 3: RedisInsight / AnotherRedisDesktopManager GUI
```
### 拆分策略
```
原始大 Key:
order:12345 -> hash{ item1, item2, ..., item5000 } ← 5000 个订单项
拆分后:
order:12345:id -> order_main_12345 ← 订单元数据
order:12345:items:page:1 -> list[ item1~item100 ] ← 按页拆分
order:12345:items:page:2 -> list[ item101~item200 ]
读取时拼接 pages 即可
```
### 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`
## 四、连接池调优
### Go go-redis 连接池配置
```go
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
// ──── 连接池参数(go-redis v9) ────
PoolSize: 100, // 连接池最大连接数(= 旧 MaxActive)
MinIdleConns: 10, // 最小空闲连接数,启动时预热
ConnMaxIdleTime: 5 * time.Minute, // 空闲连接存活上限,超时关闭
ConnMaxLifetime: 0, // 连接最大存活时长,0 = 不限制
// ──── 超时参数 ────
DialTimeout: 5 * time.Second, // 建立 TCP 连接超时
ReadTimeout: 3 * time.Second, // 读超时
WriteTimeout: 3 * time.Second, // 写超时
PoolTimeout: 1 * time.Second, // 获取连接的等待超时
// ──── 重试策略 ────
MaxRetries: 3,
MinRetryBackoff: 200 * time.Millisecond,
MaxRetryBackoff: 1 * time.Second,
})
```
> [!NOTE] go-redis v8 → v9 参数变化
> - `MaxIdle` / `MaxActive` → 合并为 `PoolSize`,`MinIdleConns` 控制预热
> - `IdleTimeout` → `ConnMaxIdleTime`
> - `PingPeriod` 无对应字段,go-redis 内部通过健康检查自动保活
> - `RetryDelay` → `MinRetryBackoff` + `MaxRetryBackoff`(指数退避)
### 连接池选型决策树
```mermaid
graph TD
A["预估 QPS"] -->|"&lt; 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 宕机时请求永远挂起
## 五、内存管理与淘汰策略
### maxmemory 设置
```conf
maxmemory 2gb
maxmemory-policy allkeys-lru
```
### 淘汰策略详解
> [!QUESTION] 没有永不超限的策略?
> 如果你不想数据丢失,应该设 `noeviction`——但这意味着写操作会失败。更好的做法是:**合理设置 maxmemory + 选择合适的淘汰策略**,而不是等到满了再处理。
| 策略 | 作用范围 | 淘汰算法 | 适用场景 |
|------|---------|---------|---------|
| **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
> - **LRU**:基于时间,维护一个候选池近似实现,内存开销小
> - **LFU**:基于访问频率,每 key 额外消耗 2 bit 计数器,更精准但略贵
> - 实际选型:不确定时先用 `allkeys-lru`;如果存在明显"二八定律"(20% 数据占 80% 访问),考虑 `allkeys-lfu`
### 内存碎片治理
```bash
# 查看碎片率
memfragmentationratio = 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% 时全力清理
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 → 立即处理!
> ```
## 六、备份与灾难恢复
### 定时备份方案
```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` |
## 七、安全加固清单
> [!CHECKLIST] 生产环境安全核对
- [ ] `requirepass` 已设置强密码
- [ ] `rename-command FLUSHALL ""` — 重命名危险命令(或设为空禁用)
- [ ] `rename-command DEBUG ""` — 禁用 debug 命令
- [ ] 绑定内网 IP 而非 `0.0.0.0`
- [ ] TLS/SSL 加密传输(Redis 6.0+ 支持)
- [ ] 定期更新版本号,修复已知 CVE
- [ ] ACL 用户权限控制(Redis 6.0+ 支持多用户角色)
```bash
# ACL 示例(Redis 6.0+)
ACL SETUSER readonly on >readOnlyPass123 ~cached:* &* +get +mget +scan +ping
ACL SETUSER writer on >writePass123 ~* &* +set +add +incr +del +ping
ACL LIST # 查看所有用户
ACL WHOAMI # 当前用户
```
## 关联笔记
- [[hhs/Redis/04-RDB持久化]] — RDB 快照与备份
- [[hhs/Redis/05-AOF持久化]] — AOF 文件大小管理
- [[hhs/Redis/07-集群方案]] — 集群运维注意事项
- [[hhs/Redis/README]] — 知识索引总览