1. 项目背景

1.1 业务场景

电商系统在接入 Redis 后的第二周,告警群开始热闹起来。周一上午 10 点,商品详情接口大面积超时,P99 延迟从 5ms 涨到 2.3 秒。运维同学第一反应是"Redis 挂了",SSH 登录后发现进程还在、CPU 也不高。但 INFO stats 显示 instantaneous_ops_per_sec 从 25000 掉到了 3000。排查了 20 分钟后才发现——有人在业务高峰期执行了 KEYS *product*,Redis 主线程被阻塞了 2.3 秒,所有接口都在排队。

周三凌晨 3 点,Redis 内存打满触发 OOM,写入全部失败(maxmemory-policy 设为了 noeviction)。缓存全部 miss,数据库瞬间被回源流量打满。凌晨 4 点,运维重启 Redis 后才发现——maxmemory 配置的是 8GB,但业务团队在活动期间塞了大量无 TTL 的大 key,实际需要 12GB。

周五下午,测试环境的 FLUSHALL 误清了预发布环境的 key(因为共用了一个 Redis 实例的 db 编号隔离),导致预发布所有缓存 miss,触发了一次假故障。

这些问题的共同特征——不是 Redis 代码有 bug,而是使用方式、配置策略、运维流程有缺陷。

1.2 排障的基本原则

原则 说明
不要先重启 重启会清空现场——慢查询日志、连接状态、内存快照全没了
先看 INFO INFO 是 Redis 的"体检报告",不同段(server/clients/memory/stats/persistence/replication)各有价值
对比基线 正常时各项指标是多少?出问题时偏离多少?没有基线,排查就是猜谜
从外到内 先确认网络通不通→认证对不对→命令有没有执行→有没有慢命令→有没有大 key
保持现场 出问题时先保存 INFO ALL、CLIENT LIST、SLOWLOG GET 128、CONFIG GET *

1.3 本章目标

建立 Redis 日常运维和基础故障排查的五个核心能力:

  1. 读懂 INFO 的关键指标并建立基线。
  2. 使用 CLIENT LIST 排查连接问题。
  3. 用 SLOWLOG 和 LATENCY 定位性能瓶颈。
  4. 发现和处理大 key、热 key、无 TTL key。
  5. 安全地执行备份、重启和配置变更。

2. 项目设计

2.1 第一幕:Redis 又抽风了,重启一下?

小胖(盯着告警群):"Redis 又不行了!接口大面积超时,要不重启一下?重启治百病。"

小白:"等一下!先别重启。重启会丢掉重要的诊断信息——比如是谁在执行慢命令、内存里哪些 key 最大、哪个客户端连接数异常——这些信息一旦重启就没了。而且如果问题不在 Redis(比如是网络抖动或应用连接池配置问题),重启完全没用。"

大师:"排障就像医生看病。你不做检查就直接开刀,病人可能死在手术台上。Redis 有丰富的诊断命令,先用它们收集证据。"

Redis 排障速查卡:

  1. PING → 能通吗?
     ↓
  2. INFO server → 版本、运行时间(最近重启过吗?)
     ↓
  3. INFO clients → 连接数正常吗?有没有异常客户端?
     ↓
  4. INFO memory → 内存用到了多少?有没有在淘汰 key?
     ↓
  5. INFO stats → QPS 正常吗?命中率有没有暴跌?
     ↓
  6. SLOWLOG GET 20 → 最近有慢命令吗?
     ↓
  7. CLIENT LIST → 哪些客户端在连接?有没有异常?
     ↓
  8. LATENCY DOCTOR → 有没有延迟尖刺?

技术映射:先诊断,再治疗。INFO 是体检报告,SLOWLOG 是病历,CLIENT LIST 是探视记录。把证据收齐再动刀。

2.2 第二幕:慢查询日志没有记录,但应用仍然超时

小胖:"我用 SLOWLOG GET 看了,最近 10 条都是微秒级的,没有慢命令。但应用还是超时啊!"

小白:"这说明瓶颈可能不在 Redis 内部,而在 Redis 外部。客户端连接池耗尽了、网络延迟过高、应用 GC 停顿——这些 Redis 的慢查询日志都看不到。"

大师:"慢查询记录的是命令在 Redis 内部执行的时间,不包括网络传输时间、客户端排队时间和应用处理时间。所以排查性能问题要全链路看:"

应用超时可能的原因分布:

  ┌─────────────────────────────────────────────┐
  │ 应用层(30%)                                │
  │  - 线程池/连接池耗尽                          │
  │  - GC 停顿                                  │
  │  - 序列化/反序列化慢                          │
  ├─────────────────────────────────────────────┤
  │ 网络层(20%)                                │
  │  - 跨机房网络延迟                             │
  │  - 防火墙/负载均衡瓶颈                        │
  │  - TCP 重传                                 │
  ├─────────────────────────────────────────────┤
  │ Redis 客户端层(15%)                         │
  │  - 连接池配置不当(maxTotal/maxIdle)          │
  │  - Pipeline 大包阻塞                         │
  │  - 集群重定向过多                             │
  ├─────────────────────────────────────────────┤
  │ Redis 服务端层(35%)                         │
  │  - 慢命令阻塞                                 │
  │  - fork 子进程(BGSAVE 时)                    │
  │  - 内存淘汰                                   │
  │  - 大 key 删除                                │
  └─────────────────────────────────────────────┘

技术映射:SLOWLOG 只是 Redis 视角的慢,不等于全链路的慢。排查时要从应用监控(APM)、网络监控和 Redis 监控三个维度交叉对比。

2.3 第三幕:大 key 的发现与处理

小胖:"怎么知道哪个 key 最大?"

大师:"有几个工具可以用。redis-cli --bigkeys 可以扫描所有 key 按类型统计最大的。MEMORY USAGE key 可以精确计算某个 key 占用内存。还有 STRLEN(String 长度)、HLEN(Hash 字段数)、LLEN(List 长度)、SCARD(Set 成员数)、ZCARD(ZSet 成员数)分别看各类型的规模。"

# 用 redis-cli 内置工具扫描大 key
redis-cli -a redis123 --bigkeys

# 输出示例:
# [00.00%] Biggest string found so far '"mall:cache:product:..."' with 25600 bytes
# [05.14%] Biggest hash   found so far '"mall:cart:user:9527"' with 382 fields
# ...
# -------- summary -------
# Sampled 8245 keys in the keyspace!
# Total key length in bytes is 235678
# Biggest string found '"mall:cache:product:8888"' has 45230 bytes
# Biggest hash   found '"mall:cart:user:1001"' has 1250 fields

大师:"发现大 key 之后的处理要谨慎。删除大 key 不要用 DEL——它会在主线程同步释放内存。用 UNLINK,它在后台线程异步释放。如果必须用 DEL,要选择业务低峰期。"

# 危险!大 key 同步删除 → 阻塞主线程
DEL mall:big:hash

# 安全!异步删除 → 不阻塞主线程
UNLINK mall:big:hash

# 渐进式删除大 Hash(每次都删一小部分)
# 用 HSCAN + HDEL 分批删除,每批 100 个 field

3. 项目实战

3.1 环境准备

docker exec -it redis-lab redis-cli -a redis123

3.2 实战一:INFO 关键指标解读

# 服务器信息
127.0.0.1:6379> INFO server
# redis_version:8.6.0    ← 确认版本
# uptime_in_seconds:86400 ← 运行了多久(重启过吗?)
# process_id:1           ← 容器内 PID

# 客户端信息
127.0.0.1:6379> INFO clients
# connected_clients:5     ← 当前连接数
# maxclients:10000        ← 最大连接数配置
# client_recent_max_input_buffer:0  ← 输入缓冲区
# client_recent_max_output_buffer:0 ← 输出缓冲区

# 内存信息
127.0.0.1:6379> INFO memory
# used_memory_human:45.23M      ← Redis 实际使用内存
# used_memory_rss_human:52.10M  ← 操作系统视角(含碎片)
# maxmemory_human:512.00M       ← 配置的最大内存
# mem_fragmentation_ratio:1.15  ← 碎片率 = rss/used
#   (> 1.5 说明碎片严重,考虑 active defrag)

# 统计信息
127.0.0.1:6379> INFO stats
# total_commands_processed:1234567
# instantaneous_ops_per_sec:8240   ← 当前 QPS
# keyspace_hits:152340             ← 缓存命中
# keyspace_misses:12890            ← 缓存未命中
# expired_keys:156                 ← 过期删除数
# evicted_keys:0                   ← 淘汰删除数 (如果>0要警惕!)
# rejected_connections:0           ← 连接拒绝数

# 持久化信息
127.0.0.1:6379> INFO persistence
# rdb_last_bgsave_status:ok        ← 最近 RDB 是否成功
# aof_enabled:1
# aof_last_write_status:ok

# 键空间信息
127.0.0.1:6379> INFO keyspace
# db0:keys=8245,expires=3200,avg_ttl=452000

关键告警阈值建议:

指标 正常范围 告警阈值 紧急阈值
mem_fragmentation_ratio < 1.5 > 2.0 > 3.0
evicted_keys 增长率 0 > 10/s > 100/s
connected_clients 基线±20% 超过 maxclients 的 70% 超过 90%
instantaneous_ops_per_sec 基线±30% 骤降 50% 骤降 80%
rejected_connections 0 > 0 > 10/s

3.3 实战二:CLIENT LIST 排查连接问题

# 查看所有客户端连接
127.0.0.1:6379> CLIENT LIST
# id=3 addr=127.0.0.1:54321 laddr=127.0.0.1:6379 fd=8 name= age=120 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 obl=0 oll=0 omem=0 tot-mem=2048 events=r cmd=client|list user=default redir=-1 resp=3

# 关键字段解读:
#   id: 客户端 ID
#   addr: 客户端地址 (IP:端口)
#   age: 连接已建立的秒数
#   idle: 最后一次执行命令距今的秒数
#   flags: N=普通客户端, b=阻塞中(如 BLPOP), M=master, S=slave
#   cmd: 最后一次执行的命令
#   qbuf: 查询缓冲区大小(命令未完整接收)
#   obl/oll: 输出缓冲区长度(固定/动态)
#   omem: 输出缓冲区占用内存

# 查看客户端统计
127.0.0.1:6379> CLIENT INFO

排查连接问题的典型场景:

# 场景1: 连接数突增 → 检查是否有应用泄漏连接
# 按 addr 统计各 IP 的连接数
127.0.0.1:6379> CLIENT LIST | grep "10.0.1.5"

# 场景2: 某客户端输出缓冲区超大 → 可能是慢消费(如 Monitor 客户端)
# 看 omem 字段
127.0.0.1:6379> CLIENT LIST | grep "omem="

# 场景3: 大量 idle 连接 → 可能连接池配置了保活但无复用
# 看 idle 字段,闲置几百秒的可能需要清理

# 杀掉指定客户端
127.0.0.1:6379> CLIENT KILL 127.0.0.1:54321
# 或按条件杀
127.0.0.1:6379> CLIENT KILL SKIPME no IDLE 300  # 杀闲置超过 300 秒的

3.4 实战三:SLOWLOG 慢查询分析

# 配置慢查询
127.0.0.1:6379> CONFIG SET slowlog-log-slower-than 10000  # 超过 10ms 记录
OK
127.0.0.1:6379> CONFIG SET slowlog-max-len 128
OK
127.0.0.1:6379> SLOWLOG RESET

# 模拟一些慢操作
127.0.0.1:6379> KEYS *
127.0.0.1:6379> EVAL "local sum=0; for i=1,100000 do sum=sum+i end; return sum" 0

# 查看慢查询日志
127.0.0.1:6379> SLOWLOG GET 5
# 1) 1) (integer) 1               # 日志唯一 ID
#    2) (integer) 1714500000       # 执行时间戳
#    3) (integer) 231000           # 执行耗时(微秒)→ 231ms!
#    4) 1) "KEYS"                  # 命令
#       2) "*"
#    5) "127.0.0.1:54321"          # 客户端地址
#    6) ""                         # 客户端名称

# 慢查询日志长度
127.0.0.1:6379> SLOWLOG LEN
(integer) 2

# 分析慢查询趋势(需要多次采样)
# 关注: 哪些命令最慢、什么时间段集中、是否和业务高峰吻合

3.5 实战四:大 key 发现与处理

# 方法 1: 用 --bigkeys 参数扫描
# 退出 redis-cli,在容器内执行:
docker exec redis-lab redis-cli -a redis123 --bigkeys

# 方法 2: 手动检查单个 key 的大小
127.0.0.1:6379> MEMORY USAGE mall:cache:product:1001
(integer) 312
127.0.0.1:6379> STRLEN mall:cache:product:1001
(integer) 245

# 方法 3: 检查集合类 key 的规模
127.0.0.1:6379> LLEN mall:ops:latest
(integer) 15000    # 列表有 15000 个元素 → 偏大
127.0.0.1:6379> HLEN mall:cart:user:9527
(integer) 382       # Hash 有 382 个字段 → 正常
127.0.0.1:6379> ZCARD mall:rank:point
(integer) 250000    # ZSet 有 25 万成员 → 偏大

# 大 key 删除的正确姿势:
# DEL → 同步删除,可能阻塞 → 不推荐
# UNLINK → 异步删除,不阻塞 → 推荐
127.0.0.1:6379> UNLINK mall:ops:latest
(integer) 1

3.6 实战五:延迟诊断

# LATENCY DOCTOR: 综合诊断报告
127.0.0.1:6379> LATENCY DOCTOR
# 输出各延迟事件的分析和建议

# LATENCY LATEST: 最近的延迟事件
127.0.0.1:6379> LATENCY LATEST
# 返回各种延迟事件类型:
#   command: 命令执行延迟
#   fast-command: 快命令异常慢
#   fork: fork 子进程时延迟

# LATENCY HISTORY event: 特定事件的延迟历史
127.0.0.1:6379> LATENCY HISTORY command

# LATENCY RESET: 重置延迟统计
127.0.0.1:6379> LATENCY RESET

3.7 实战六:模拟 5 类常见故障

故障 1:认证失败

# 无密码连接
127.0.0.1:6379> PING
(error) NOAUTH Authentication required.

# 解决: AUTH redis123
127.0.0.1:6379> AUTH redis123
OK
127.0.0.1:6379> PING
PONG

故障 2:类型错误

127.0.0.1:6379> SET mall:user:1001 "string-value"
OK
127.0.0.1:6379> HGET mall:user:1001 name
(error) WRONGTYPE Operation against a key holding the wrong kind of value

# 诊断: TYPE 确认类型
127.0.0.1:6379> TYPE mall:user:1001
string
# → 应该用 GET 而不是 HGET。或者 key 被错误覆盖了

故障 3:OOM(内存满)

# 设置很小的 maxmemory 模拟 OOM
127.0.0.1:6379> CONFIG SET maxmemory 1mb
OK

# 尝试写入大 value
127.0.0.1:6379> SET large:key "lots of data..."
(error) OOM command not allowed when used memory > 'maxmemory'.

# 诊断:
127.0.0.1:6379> INFO memory | grep "used_memory_human\|maxmemory_human\|evicted_keys"
# 如果 evicted_keys=0 且报 OOM → maxmemory-policy 可能是 noeviction
127.0.0.1:6379> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"    # ← 不淘汰只拒绝写入!

# 解决: 改淘汰策略
127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lru
OK

# 恢复 maxmemory
127.0.0.1:6379> CONFIG SET maxmemory 512mb
OK

故障 4:慢查询定位

# 执行一次 KEYS(学习环境模拟,生产不要!)
127.0.0.1:6379> KEYS mall:*
# ... 返回结果 ...

# 查看慢查询
127.0.0.1:6379> SLOWLOG GET 1
# 可以看到 KEYS 的耗时

故障 5:大 key 阻塞删除

# 创建一个大 List 模拟
127.0.0.1:6379> DEL big:list
(integer) 1
# ... 写入 50000 个元素 ...
127.0.0.1:6379> LLEN big:list
(integer) 50000

# 危险删除(同步)
# DEL big:list    ← 可能阻塞几百毫秒

# 安全删除(异步)
127.0.0.1:6379> UNLINK big:list
(integer) 1

3.8 实战七:备份与恢复检查

# 查看持久化状态
127.0.0.1:6379> INFO persistence
# rdb_last_save_time: 上次 RDB 保存时间
# rdb_changes_since_last_save: 上次保存以来的变更数

# 手动触发后台快照
127.0.0.1:6379> BGSAVE
Background saving started

# 查看最后一次 BGSAVE 是否成功
127.0.0.1:6379> LASTSAVE
(integer) 1714500000

# 查看 AOF 状态
127.0.0.1:6379> CONFIG GET appendonly
127.0.0.1:6379> CONFIG GET appendfsync

3.9 实战八:监控指标采集脚本

日常运维需要定时采集关键指标,形成趋势图。以下是一个 Python 脚本示例,可集成到定时任务中:

import redis
import json
import time

def collect_redis_metrics(host='localhost', port=6379, password='redis123'):
    r = redis.Redis(host=host, port=port, password=password, decode_responses=True)
    metrics = {}

    # 基础信息
    info_server = r.info('server')
    metrics['uptime_seconds'] = info_server.get('uptime_in_seconds', 0)
    metrics['redis_version'] = info_server.get('redis_version', 'unknown')

    # 客户端
    info_clients = r.info('clients')
    metrics['connected_clients'] = info_clients.get('connected_clients', 0)
    metrics['blocked_clients'] = info_clients.get('blocked_clients', 0)
    metrics['maxclients'] = info_clients.get('maxclients', 10000)

    # 内存
    info_memory = r.info('memory')
    used_memory = info_memory.get('used_memory', 0)
    maxmemory = info_memory.get('maxmemory', 0)
    metrics['used_memory_bytes'] = used_memory
    metrics['maxmemory_bytes'] = maxmemory
    metrics['memory_usage_pct'] = round(used_memory / maxmemory * 100, 2) if maxmemory > 0 else 0
    metrics['mem_fragmentation_ratio'] = info_memory.get('mem_fragmentation_ratio', 1.0)

    # 统计
    info_stats = r.info('stats')
    metrics['ops_per_sec'] = info_stats.get('instantaneous_ops_per_sec', 0)
    metrics['keyspace_hits'] = info_stats.get('keyspace_hits', 0)
    metrics['keyspace_misses'] = info_stats.get('keyspace_misses', 0)
    metrics['expired_keys'] = info_stats.get('expired_keys', 0)
    metrics['evicted_keys'] = info_stats.get('evicted_keys', 0)

    # 计算命中率
    total = metrics['keyspace_hits'] + metrics['keyspace_misses']
    metrics['hit_rate'] = round(metrics['keyspace_hits'] / total * 100, 2) if total > 0 else 0

    # 持久化
    info_pers = r.info('persistence')
    metrics['rdb_last_save_seconds_ago'] = time.time() - info_pers.get('rdb_last_save_time', 0)
    metrics['rdb_changes_since_last_save'] = info_pers.get('rdb_changes_since_last_save', 0)

    # 慢查询
    slowlog = r.slowlog_get(10)
    metrics['slowlog_count'] = len(slowlog)
    if slowlog:
        metrics['slowlog_max_duration_us'] = max(s[2] for s in slowlog)

    return metrics

# 输出 JSON 格式,方便 Prometheus/Grafana 采集
if __name__ == '__main__':
    metrics = collect_redis_metrics()
    print(json.dumps(metrics, indent=2))

3.10 实战九:连接池配置检查

很多"Redis 超时"问题的根源不在 Redis,而在客户端的连接池配置:

// 典型的 Lettuce 连接池配置(Spring Boot)
@Configuration
public class RedisConfig {
    @Bean
    public LettuceClientConfigurationBuilderCustomizer customizer() {
        return builder -> builder
            .clientOptions(ClientOptions.builder()
                .autoReconnect(true)           // 自动重连
                .disconnectedBehavior(DisconnectedBehavior.REJECT_COMMANDS)
                .socketOptions(SocketOptions.builder()
                    .connectTimeout(Duration.ofSeconds(1))  // 连接超时
                    .keepAlive(true)
                    .build())
                .build())
            .commandTimeout(Duration.ofMillis(500));  // 命令超时
    }
}

连接池常见参数及其排查意义:

参数 建议值 如果太小 如果太大
maxTotal 50-200 高并发时线程等待连接,接口超时 占用 Redis 连接数,可能触发 maxclients
maxIdle 10-50 频繁创建/销毁连接 连接数浪费
minIdle 5-10 突发流量时建立连接慢 同上
commandTimeout 500-2000ms 正常请求也超时 慢命令无法及时发现
maxWaitMillis 2000ms 线程快速放弃,大量超时日志 线程堆积

3.11 实战十:热 key 发现与处理

热 key 是指被大量请求集中访问的 key,可能成为 Redis 的性能瓶颈:

# 方法 1: redis-cli --hotkeys(Redis 4.0+)
docker exec redis-lab redis-cli -a redis123 --hotkeys

# 输出示例:
# Hot key 'mall:cache:product:8888' found with 15000 hits (占 23.5%)

# 方法 2: MONITOR + 统计(低峰期临时使用,生产慎用!)
# 终端 A: redis-cli -a redis123 MONITOR | grep "GET mall:cache:product:" > hotkey_analysis.log
# 运行 30 秒后 Ctrl+C,然后统计:
# cat hotkey_analysis.log | awk '{print $NF}' | sort | uniq -c | sort -rn | head -10

热 key 处理方案:

public class HotKeyHandler {

    // 方案 1: 本地缓存前置——热点 key 缓存在应用内存中
    private final Cache<String, Object> hotKeyLocalCache = Caffeine.newBuilder()
        .maximumSize(100)
        .expireAfterWrite(10, TimeUnit.SECONDS)
        .build();

    // 方案 2: key 拆分(如库存拆分成多个子库存)
    public void distributeStock(String skuId, int totalStock, int shardCount) {
        int perShard = totalStock / shardCount;
        for (int i = 0; i < shardCount; i++) {
            redis.set("mall:stock:" + skuId + ":shard:" + i, String.valueOf(perShard));
        }
    }

    // 方案 3: 读写分离——热 key 的读走从节点
    // (需要主从架构,第 23 章展开)
}

3.12 实战十一:配置变更的正确流程

# 查看当前运行时配置
127.0.0.1:6379> CONFIG GET maxmemory
127.0.0.1:6379> CONFIG GET maxmemory-policy
127.0.0.1:6379> CONFIG GET slowlog-log-slower-than

# 临时修改(运行时有效,重启丢失)
127.0.0.1:6379> CONFIG SET maxmemory 1gb
OK

# 永久生效:修改 redis.conf
# 然后重启或执行 CONFIG REWRITE
127.0.0.1:6379> CONFIG REWRITE
OK
# 注: CONFIG REWRITE 会将当前运行时配置写回配置文件

配置变更检查清单:

变更项 需要确认 风险
maxmemory 调大 服务器内存余量是否足够 可能导致 OOM Killer 杀掉 Redis
maxmemory-policy 改为 noeviction 是否所有写入都允许失败 写操作全部拒绝
slowlog-log-slower-than 调低 日志量会不会过大 磁盘写满
timeout 调小 是否有长连接的客户端 正常客户端被断开
rename-command 禁用 FLUSHALL 依赖 FLUSHALL 的测试/运维流程 运维脚本失效

4. 项目总结

4.1 Redis 排障 SOP(标准操作流程)

Step 1: 确认基础连通
  PING → INFO server (uptime, version)

Step 2: 检查资源水位
  INFO memory (used_memory, maxmemory, evicted_keys)
  INFO clients (connected_clients, rejected_connections)

Step 3: 检查性能指标
  INFO stats (instantaneous_ops_per_sec, keyspace_hits/misses)
  SLOWLOG GET 20
  LATENCY DOCTOR

Step 4: 定位问题 key
  --bigkeys 扫描
  检查无 TTL key (TTL key)
  检查 key 类型是否正确 (TYPE key)

Step 5: 分析客户端
  CLIENT LIST (异常连接、输出缓冲区)

Step 6: 检查持久化
  INFO persistence (RDB/AOF 状态、上次保存时间)

Step 7: 采取行动
  必要时: UNLINK 清理大 key, CLIENT KILL 断开异常连接, CONFIG SET 调整参数
  最后手段: 重启(需确认持久化正常、有备份)

4.2 常见故障速查表

现象 优先查看 常见原因
接口超时 SLOWLOG, INFO commandstats 慢命令阻塞、大 key 删除
写入失败 INFO memory maxmemory 满了且策略是 noeviction
连接超时 INFO clients 连接数打满、客户端连接池配置
缓存命中率骤降 INFO stats keyspace_misses 缓存全 miss(过期风暴/FLUSHALL)
内存持续增长 INFO memory 无 TTL key 堆积、输出缓冲区增长
延迟抖动 LATENCY DOCTOR fork 子进程、AOF rewrite、过期清理

4.3 危险命令清单(生产禁用或受限)

命令 风险 替代方案
KEYS * 阻塞主线程,复杂度 O(N) SCAN
FLUSHALL/FLUSHDB 清空所有数据 重命名或禁用(ACL)
CONFIG SET 运行时修改配置可能引入风险 通过配置管理平台
MONITOR 输出所有命令,性能开销 50%+ 慢查询日志 + 客户端打点
SHUTDOWN 立即关闭 Redis 通过运维平台执行
DEBUG SLEEP 让 Redis 休眠 不要执行
大 key DEL 同步释放大内存,阻塞 UNLINK 异步删除

4.4 思考题

  1. 排查题:线上 Redis 的 evicted_keys 从 0 突然开始增长(每秒约 50 个),但业务方反馈"功能没有异常"。你需要分析这是正常的(缓存淘汰本身就是设计的一部分)还是问题(关键数据被淘汰了)。你会怎么排查?

  2. 设计题:你需要为团队编写一份"Redis 新人值班手册",包含 5 个最常见的告警场景和处理步骤。请选择 5 个场景并写出精简版的处理流程(每个场景不超过 5 步)。

(答案提示将在下一章末尾给出。)

上一章思考题答案提示(第14章):

第1题(积分+排名+流水 Lua):脚本同时操作三个 key——积分 HINCRBY、排名 ZINCRBY、流水 RPUSH。这三个在同一个 Lua 脚本中是原子的。流水记录格式可以为 {userId}:{points}:{timestamp}。

第2题(降低 WATCH 冲突率):① 拆 key——将热点账户的余额拆成多个子账户(如 account:1001:shard1),写请求分散到不同 shard;② 排队串行——对同一账户的请求先进入应用层队列,单个线程顺序处理;③ 用 Lua 替代——Lua 在服务端原子执行,不存在客户端 WATCH 重试的开销。

延伸阅读与资源

Redis 8 实战精讲:从 CRUD 到源码,构建高可用缓存系统
Java 工程师进阶:从 JVM 生产排障到OpenJDK原理
MySQL Server 9从入门到进阶的实战之旅
Nacos 3.x 注册配置中心实战修炼:从入门到源码扩展
Elasticsearch从入门到进阶的实战之旅
RabbitMQ从入门到进阶的实战之旅:从单机到大促高可用架构
Celery 入门到进阶之路:从异步任务到自研调度平台
LangGraph 生产级实战进阶:从零到生产级Agent工作流开发
Dify 从入门到源码:LLM 应用平台实战修炼
从零到生产级:FastAPI 异步高并发、源码与 SRE 实战
实战SQLAlchemy 2.0: 从 CRUD 到生产级架构
从零打造企业级 AI 助手:LangChain RAG、Agent 与生产实战
后端工程师 AI 转型课:Ollama 私有化大模型从入门到生产
MongoDB 实战进阶与内核修炼
NumPy 从入门到生产落地:全链路实战指南(科学计算/向量化/性能调优)
Milvus向量数据库实战修炼:从 0 到 1 精通向量检索与生产落地
Python 3实战精进:从脚本到高并发订单引擎
python入门:Rquests从菜鸟脚本到企业级SDK的网络实战圣经

posted on 2026-11-10 10:19  一天不进步,就是退步  阅读(11)  评论(0)    收藏  举报