Files
cs-note/hzh/REDIS/内存持续增长排查.md
T

208 lines
6.5 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, memory, troubleshooting, monitoring]
create time: 2026-06-01 10:30
---
# Redis 内存持续增长排查
## 概述
生产环境中 Redis 内存持续增长是一个常见的告警场景。它不一定意味着 Bug——但如果不及时处理,最终会导致 OOM 或频繁 Swap。本节提供一套**系统性的排查思路**,对应你的薄弱点 F10(TTL 命令检查 Key 过期状态)。
## 一、排查流程图
```mermaid
flowchart TD
Start["⚠️ 内存持续增长告警"] --> Step1["Step 1: MEMORY USED 确认增长幅度"]
Step1 --> Q1{"增长是持续的还是突发的?"}
Q1 -- "持续增长" --> SLOW["缓慢增长路径"]
Q1 -- "突然飙升" --> SPIKE["突发峰值路径"]
subgraph SLOW["慢增长排查"]
S1["检查 Key 总数变化<br/>DBSIZE / SCAN"] --> S2["找出增长最快的 Key 前缀<br/>SCAN + TTL 检查"] --> S3["确认 EXPIRE 是否生效<br/>TTL key → -1 表示未过期"] --> S4["僵尸 Key 清理"]
end
subgraph SPIKE["爆发增长排查"]
P1["监控 BigKey 列表<br/>MEMORY USAGE + SORTED BY size"] --> P2["检查是否有大量写入并发<br/>Slowlog / Monitoring"] --> P3["排查是否为临时批处理任务<br/>定时任务/数据同步"]
end
S4 --> Action{"找到原因了?"}
P3 --> Action
Action -- "是" --> Fix["修复"]
Action -- "否" --> Dump["MEMORY ANALYZER<br/>深入分析内存分布"]
style Start fill:#fff3e0
style Fix fill:#c8e6c9
style Dump fill:#e3f2fd
```
## 二、逐步排查方法
### Step 1:定位增长来源
```bash
# 查看所有数据库的大小
INFO keyspace
→ db0:keys=12345,expires=12000,avg_ttl=3600000
```
输出解析:
| 字段 | 含义 |
|------|------|
| `keys` | 该数据库中 Key 的总数 |
| `expires` | 设置了过期时间的 Key 数量 |
| `avg_ttl` | 这些 Key 的平均剩余生存时间(毫秒) |
如果 `keys` 在持续增长而 `expires` 不变 → 有大量未设置过期的 Key
### Step 2:查找大 Key
```bash
# 方式1: redis-cli --bigkeys(推荐快速使用)
redis-cli --bigkeys
# 扫描中...
# --- STRINGS ---
# biggest rate:sliding:user123 (6 MB)
# count 15 (total 15 keys)
# ...
# 方式2: 手动用 SCAN + MEMORY USAGE
SCAN 0 MATCH rate:* TYPE string | xargs -I{} redis-cli MEMORY USAGE {}
```
> [!tip]- bigkeys 工具的坑
>
> `redis-cli --bigkeys` 会遍历所有 Key,对大库来说仍然可能阻塞 Redis。**生产环境慎用**,建议在从库或低峰期运行。
### Step 3:检查过期状态(F10 考点 🔑)
这是排查内存问题的核心技能——**判断 Key 是否已经过期**。
```bash
# 检查单个 Key 的 TTL
TTL rate:limit:user123
→ 86400 ← 剩余 86400 秒,正常
TTL rate:limit:user456
→ -1 ← ❌ 没有过期时间!这就是僵尸 Key
TTL rate:limit:user789
→ -2 ← ❌ Key 已不存在,已被删除
```
| TTL 返回值 | 含义 | 处理方式 |
|-----------|------|---------|
| `> 0` | Key 存在,还有 N 秒过期 | 正常,等待自动清理 |
| `-1` | **Key 存在但没有设置过期时间** | ❌ 检查代码中是否漏了 EXPIRE,手动 DEL |
| `-2` | Key 已不存在 | 正常,无需操作 |
### Step 4:批量检查僵尸 Key
```go
func findZombieKeys(ctx context.Context, rdb *redis.Client, pattern string) []string {
var zombies []string
cursor := uint64(0)
for {
keys, nextCursor, _ := rdb.Scan(ctx, cursor, pattern, 200).Result()
for _, key := range keys {
ttl, err := rdb.TTL(ctx, key).Result()
if err == nil && ttl == -1 {
zombies = append(zombies, key)
}
}
if nextCursor == 0 {
break
}
cursor = nextCursor
}
return zombies
}
```
## 三、常见内存增长原因及对策
| 原因 | 表现 | 解决方式 |
|------|------|---------|
| **EXPIRE 未生效** | TTL 返回 -1 | 检查 Lua 脚本或 Go 代码中的 EXPIRE 调用 |
| **Sorted Set 规模过大** | 个别 Key 占几 MB~几十 MB | 切换到令牌桶方案(O(1) 空间) |
| **批量写入无上限** | DBSIZE 快速增长 | 添加写入限流和 Key 数量上限 |
| **大 Hash/List 结构** | 单 Key 占用巨大 | 拆分或使用更合适的数据结构 |
| **副本同步延迟** | Master 已过期,Slave 还没删 | 配置 `replica-ignore-maxmemory` |
| **Redis 缓存淘汰策略不当** | maxmemory-policy 设为 noeviction | 改为 `allkeys-lru` 或 `volatile-ttl` |
## 四、预防性措施
```mermaid
flowchart TD
Monitor["📊 持续监控"] --> A["MEMORY USAGE per Key 告警"]
Monitor --> B["DBSIZE 增长速率告警"]
Monitor --> C["Evicted Keys 计数告警"]
Policy["⚙️ 合理的淘汰策略"] --> D["maxmemory-policy: allkeys-lru"]
Policy --> E["maxmemory: 设为可用内存的 70%"]
Design["🔧 设计时考虑"] --> F["所有 Key 必须有 EXPIRE"]
Design --> G["高频场景用 O(1) 算法(令牌桶)"]
Design --> H["定期 SCAN 清理残留数据"]
style Monitor fill:#e3f2fd
style Policy fill:#fff3e0
style Design fill:#e8f5e9
```
### 推荐配置
```conf
# redis.conf
# 最大内存限制(建议设置为服务器总内存的 70%,留余量给 Swap)
maxmemory 2gb
# 淘汰策略:LRU(最近最少使用)
maxmemory-policy allkeys-lru
# 或者:优先删除有过期时间的 Key 中最早过期的
# maxmemory-policy volatile-ttl
# RDB/AOF 混合持久化
rdb-save-incremental-mem YES
aof-use-rdb-preamble YES
```
## 五、紧急止血步骤
```
当内存已经接近上限时的应急操作:
1. 停止所有非必要的写入(暂停定时任务、数据同步)
2. 找到并删除最大的几个 Key:
redis-cli --bigkeys
DEL <bigkey1> <bigkey2> ...
3. 如果无法确定哪些 Key 该删,先批量减少 Key 总量:
SCAN 0 MATCH rate:* COUNT 1000 → DEL 随机抽取的部分 Key(谨慎评估影响)
4. 临时提升 maxmemory(如果服务器还有物理内存可用)
5. 事后复盘:为什么没及时发现?监控阈值要不要调低?
```
> [!warning]- DEL vs UNLINK
>
> 删除大 Key 时用 `UNLINK`(异步删除)替代 `DEL`(同步删除),避免阻塞主线程:
> ```bash
> # 同步删除(阻塞)
> DEL rate:sliding:large_key
>
> # 异步删除(不阻塞)
> UNLINK rate:sliding:large_key
> ```
## 关联笔记
- [[分布式限流]] — 排错指南中的内存问题章节
- [[僵尸Key防护]] — 针对僵尸 Key 的具体防护措施
- [[SCAN命令]] — SCAN 是排查内存问题的必备工具