vault backup: 2026-06-08 23:08:57
This commit is contained in:
+235
-52
@@ -7,10 +7,17 @@ create time: 2026-05-15 18:16
|
||||
|
||||
## 概述
|
||||
|
||||
生产环境的 Redis 不是一装就跑——你需要持续监控、定期清理大 Key、合理配置连接池、分析慢查询。本节覆盖日常运维中最常见的问题和调优方法。
|
||||
你把 Redis 装好、数据灌进去、代码跑通了——然后呢?
|
||||
|
||||
想象一下:某天凌晨 3 点,告警响了,接口 P99 从 2ms 飙到 2 秒。你登上服务器一看,Redis 进程还活着,`redis-cli ping` 也返回 PONG,但业务就是慢。问题出在哪?**如果你不知道该看哪些指标、不知道大 Key 的危害、不知道连接池怎么配,你只能靠猜。**
|
||||
|
||||
本节就是帮你建立"Redis 运维直觉"——从监控什么、怎么看,到常见问题怎么治。内容按照"先能发现问题 → 再会分析问题 → 最后能解决问题"的顺序组织。
|
||||
|
||||
## 一、监控指标体系
|
||||
|
||||
> [!QUESTION] 面试中常被问到:你怎么监控线上的 Redis?
|
||||
> 一个好的回答不是背出 `INFO` 命令的所有字段,而是说出"我关注哪几个指标、为什么关注、异常了怎么处理"。这一节帮你建立这个体系。
|
||||
|
||||
### INFO 命令分级
|
||||
|
||||
```bash
|
||||
@@ -40,10 +47,23 @@ 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 = 碎片率
|
||||
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% 需排查 |
|
||||
@@ -51,7 +71,6 @@ mem_fragmentation_ratio: 1.09 # RSS / used = 碎片率
|
||||
| rejected_connections | 0 | > 0 说明 maxclients 不够 |
|
||||
| total_commands_processed/s | 平稳增长 | 断崖式下跌说明异常 |
|
||||
| connected_slaves | ≥ 预期 | 0 = 全部分离 |
|
||||
```
|
||||
|
||||
### 副本延迟监控
|
||||
|
||||
@@ -96,6 +115,11 @@ redis-cli -h $HOST -p $PORT INFO persistence \
|
||||
|
||||
## 二、慢查询日志
|
||||
|
||||
上一节的监控指标能告诉你"Redis 整体状态如何",但如果你想定位"**是哪条命令拖慢了 Redis**",就需要慢查询日志了。它的原理很简单:Redis 在主线程执行每条命令,如果某条命令的执行时间超过阈值,就记录下来。
|
||||
|
||||
> [!QUESTION] 慢查询日志 vs 应用日志?
|
||||
> 应用日志记录的是"你的代码花了多久调用 Redis"(包含网络延迟、序列化开销),慢查询日志记录的是"Redis 服务端执行这条命令本身花了多久"。两者差异越大,说明问题越可能出在网络或客户端。
|
||||
|
||||
### 配置与分析
|
||||
|
||||
```bash
|
||||
@@ -178,9 +202,14 @@ LATENCY RESET # 重置统计数据
|
||||
|
||||
## 三、大 Key 治理
|
||||
|
||||
如果慢查询是"某一顿饭做得太慢",大 Key 就是"你端了一整头牛上来"——不管厨师多快,处理这道菜都要比小菜慢得多。而且大 Key 不光影响自己,还会堵住后面所有命令(因为 Redis 是单线程的)。
|
||||
|
||||
### 什么是大 Key?
|
||||
|
||||
通常指超过 **5KB** 的单个 Key(字符串除外,字符串可达数百 MB)。
|
||||
简单说就是 value **太大**的 Key。业界的经验阈值:**字符串 value ≥ 1MB**,**集合类(Hash / Set / ZSet / List)元素数 > 5000 或总大小 > 10MB**。
|
||||
|
||||
> [!QUESTION] 大 Key 是怎么产生的?
|
||||
> 大多数大 Key 不是一开始就设计成这样的,而是"长"出来的——比如一个 List 本来只有几十条记录,业务不断 append 从不清理,半年后变成几百万条。这就像你家阳台的杂物间,"就放一点",最后堆满了。
|
||||
|
||||
> [!WARNING] 大 Key 的危害
|
||||
> - **网络阻塞**:SET/GET 一个 1MB 的值占满一条 TCP 连接
|
||||
@@ -190,8 +219,10 @@ LATENCY RESET # 重置统计数据
|
||||
|
||||
### 检测工具
|
||||
|
||||
发现大 Key 有多种方式,从轻到重依次是:
|
||||
|
||||
```bash
|
||||
# 方式 1: redis-cli --bigkeys(采样扫描)
|
||||
# 方式 1: redis-cli --bigkeys(按元素数量采样扫描)
|
||||
redis-cli --bigkeys -n 1000
|
||||
|
||||
# 输出示例:
|
||||
@@ -201,24 +232,36 @@ redis-cli --bigkeys -n 1000
|
||||
# 456KB 3 hash order:items:*
|
||||
# ...
|
||||
|
||||
# 方式 2: redis-rdb-tools (RDB 离线分析)
|
||||
# 方式 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
|
||||
|
||||
# 方式 3: RedisInsight / AnotherRedisDesktopManager GUI
|
||||
# 方式 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 个订单项
|
||||
order:12345 -> hash{ item1, item2, ..., item5000 } ← 5000 个订单项塞在一个 Hash 里
|
||||
|
||||
拆分后:
|
||||
order:12345:id -> order_main_12345 ← 订单元数据
|
||||
order:12345:items:page:1 -> list[ item1~item100 ] ← 按页拆分
|
||||
order:12345:items:page:2 -> list[ item101~item200 ]
|
||||
order:12345:meta -> hash{ status, amount, ... } ← 订单元数据(小)
|
||||
order:12345:items:0 -> list[ item1~item100 ] ← 按 100 个一组分片
|
||||
order:12345:items:1 -> list[ item101~item200 ]
|
||||
...
|
||||
|
||||
读取时拼接 pages 即可
|
||||
读取时先读 meta,再按需读取对应分片——大多数场景只需要第一页数据
|
||||
```
|
||||
|
||||
### DEL 大 Key 的风险
|
||||
@@ -239,6 +282,33 @@ UNLINK big_key # 异步删除(Redis 4.0+),立即返回
|
||||
|
||||
## 四、连接池调优
|
||||
|
||||
### 为什么需要连接池?
|
||||
|
||||
没有连接池时,每个请求都要"三次握手 → 发命令 → 四次挥手"——就像每次去银行都要重新取号、排队、开户。连接池相当于你开了个 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
|
||||
@@ -248,21 +318,24 @@ rdb := redis.NewClient(&redis.Options{
|
||||
DB: 0,
|
||||
|
||||
// ──── 连接池参数(go-redis v9) ────
|
||||
PoolSize: 100, // 连接池最大连接数(= 旧 MaxActive)
|
||||
MinIdleConns: 10, // 最小空闲连接数,启动时预热
|
||||
ConnMaxIdleTime: 5 * time.Minute, // 空闲连接存活上限,超时关闭
|
||||
ConnMaxLifetime: 0, // 连接最大存活时长,0 = 不限制
|
||||
// 类比:连接池就像餐厅的桌子——PoolSize 是最大桌数,MinIdleConns 是
|
||||
// 每天开业前至少要摆好的桌子数,ConnMaxIdleTime 是桌子空闲多久后收掉
|
||||
PoolSize: 100, // 最多同时维持 100 个连接(并发 Goroutine 很多时调大)
|
||||
MinIdleConns: 10, // 启动时预先建 10 个连接,避免冷启动延迟
|
||||
ConnMaxIdleTime: 5 * time.Minute, // 空闲超过 5 分钟的连接自动关闭,释放资源
|
||||
ConnMaxLifetime: 0, // 连接最大存活时间,0 = 不限制(通常保持默认即可)
|
||||
|
||||
// ──── 超时参数 ────
|
||||
DialTimeout: 5 * time.Second, // 建立 TCP 连接超时
|
||||
ReadTimeout: 3 * time.Second, // 读超时
|
||||
WriteTimeout: 3 * time.Second, // 写超时
|
||||
PoolTimeout: 1 * time.Second, // 获取连接的等待超时
|
||||
// 这些超时是你的"止损线"——如果 Redis 出问题,最多等多久就放弃
|
||||
DialTimeout: 5 * time.Second, // 建连超时:Redis 宕机时不会永远卡住
|
||||
ReadTimeout: 3 * time.Second, // 读超时:普通命令 3s 足够,大 Key 操作需调大
|
||||
WriteTimeout: 3 * time.Second, // 写超时:同上
|
||||
PoolTimeout: 1 * time.Second, // 从池里拿连接的等待时间:超时说明池子太小或 Redis 慢
|
||||
|
||||
// ──── 重试策略 ────
|
||||
MaxRetries: 3,
|
||||
MinRetryBackoff: 200 * time.Millisecond,
|
||||
MaxRetryBackoff: 1 * time.Second,
|
||||
MaxRetries: 3, // 最多重试 3 次(网络抖动时自动恢复)
|
||||
MinRetryBackoff: 200 * time.Millisecond, // 第一次重试等 200ms
|
||||
MaxRetryBackoff: 1 * time.Second, // 最长等 1s(指数退避,不会一直刷)
|
||||
})
|
||||
```
|
||||
|
||||
@@ -275,10 +348,10 @@ rdb := redis.NewClient(&redis.Options{
|
||||
### 连接池选型决策树
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A["预估 QPS"] -->|"< 500"| S["PoolSize: 20, MinIdleConns: 5"]
|
||||
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"]
|
||||
A -->|"\> 5000"| L["PoolSize: 100\~200, MinIdleConns: 30~50"]
|
||||
|
||||
B{"有无 Pipeline?"}
|
||||
B -->|"是"| C["增加 30% 连接数"]
|
||||
@@ -301,6 +374,10 @@ graph TD
|
||||
|
||||
## 五、内存管理与淘汰策略
|
||||
|
||||
前面讲了怎么发现大 Key、怎么配置连接池,现在来说说一个更根本的问题:**Redis 内存用完了怎么办?**
|
||||
|
||||
打个比方:你的 Redis 就像一个固定大小的冰箱。`maxmemory` 是冰箱的容量,淘汰策略决定了"新菜放不下的时候,扔掉哪道旧菜"。
|
||||
|
||||
### maxmemory 设置
|
||||
|
||||
```conf
|
||||
@@ -311,35 +388,56 @@ maxmemory-policy allkeys-lru
|
||||
### 淘汰策略详解
|
||||
|
||||
> [!QUESTION] 没有永不超限的策略?
|
||||
> 如果你不想数据丢失,应该设 `noeviction`——但这意味着写操作会失败。更好的做法是:**合理设置 maxmemory + 选择合适的淘汰策略**,而不是等到满了再处理。
|
||||
> 如果你不想丢数据,可以设 `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-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 | — | — | 数据不可丢失,超限返回错误(如配置中心) |
|
||||
| volatile-ttl | 仅设 TTL 的 key | TTL 最短优先 | 让数据自然过期的场景 |
|
||||
| noeviction | — | — | 数据不可丢失,满了返回错误(如配置中心) |
|
||||
|
||||
> [!NOTE] LRU vs LFU
|
||||
> - **LRU**:基于时间,维护一个候选池近似实现,内存开销小
|
||||
> - **LFU**:基于访问频率,每 key 额外消耗 2 bit 计数器,更精准但略贵
|
||||
> - 实际选型:不确定时先用 `allkeys-lru`;如果存在明显"二八定律"(20% 数据占 80% 访问),考虑 `allkeys-lfu`
|
||||
> [!NOTE] LRU vs LFU——"最久没用" vs "最少被用"
|
||||
> - **LRU**(Least Recently Used):图书馆里"最久没被借走的书先下架"。只要最近被借过就不会被淘汰,哪怕只借了一次。实现简单,内存开销小。
|
||||
> - **LFU**(Least Frequently Used):图书馆里"被借次数最少的书先下架"。一本冷门书即使昨天刚被借过,如果历史总次数少也可能被淘汰。每 key 多消耗 16 bits 记录频率,更精准但略贵。
|
||||
> - **实际选型**:不确定就先用 `allkeys-lru`;如果存在明显"二八定律"(20% 数据占 80% 访问),`allkeys-lfu` 更合理。
|
||||
|
||||
### 内存碎片治理
|
||||
|
||||
什么是碎片?想象你往书架上放书、取书,取走后留下空隙。时间久了,书架明明有空位,但因为每段空位都太小,新书放不进去——这就是碎片。
|
||||
|
||||
```bash
|
||||
# 查看碎片率
|
||||
memfragmentationratio = used_memory_rss / used_memory
|
||||
# 查看碎片率(来自 INFO memory 输出)
|
||||
# mem_fragmentation_ratio = used_memory_rss / used_memory
|
||||
# 正常范围: 1.0 ~ 1.5
|
||||
|
||||
# 配置激活碎片整理(Redis 4.0+,仅在 Linux 上有效)
|
||||
# 配置激活在线碎片整理(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%
|
||||
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?
|
||||
@@ -353,6 +451,9 @@ active-defrag-cycle-max 25 # 最高占 CPU 25%
|
||||
|
||||
## 六、备份与灾难恢复
|
||||
|
||||
> [!QUESTION] Redis 需要备份吗?不是有主从复制吗?
|
||||
> 主从复制能防"机器挂了",但防不了"误删数据"。如果有人执行了 `FLUSHALL`,这个命令会同步到所有从库——数据瞬间全没了。**备份是最后一道防线。**
|
||||
|
||||
### 定时备份方案
|
||||
|
||||
```bash
|
||||
@@ -414,27 +515,109 @@ flowchart TD
|
||||
|
||||
## 七、安全加固清单
|
||||
|
||||
> [!CHECKLIST] 生产环境安全核对
|
||||
Redis 默认配置是"裸奔"的——没有密码、绑定所有网卡、所有命令都能执行。上线前必须加固,就像搬进新家要换锁、装窗帘一样。
|
||||
|
||||
- [ ] `requirepass` 已设置强密码
|
||||
- [ ] `rename-command FLUSHALL ""` — 重命名危险命令(或设为空禁用)
|
||||
- [ ] `rename-command DEBUG ""` — 禁用 debug 命令
|
||||
- [ ] 绑定内网 IP 而非 `0.0.0.0`
|
||||
- [ ] TLS/SSL 加密传输(Redis 6.0+ 支持)
|
||||
- [ ] 定期更新版本号,修复已知 CVE
|
||||
- [ ] ACL 用户权限控制(Redis 6.0+ 支持多用户角色)
|
||||
> [!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 +add +incr +del +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]] — 知识索引总览
|
||||
|
||||
Reference in New Issue
Block a user