Redis 运维实战与排错

一、引言

Redis 基本是缓存、会话、排行榜、轻量队列的「事实标准」。你 AWS 上跑的 ElastiCache for Redis 就是托管形态------但托管不等于「不用懂底层」:当 ElastiCache 出现连接超时、failover 抖动、内存告警时,你得用 Redis 的底层语言跟 AWS 工单对话;而跨账号、特殊模块、合规隔离等场景,又往往要回到自建。

这篇不堆命令,而是把运维最该懂的几条线讲透:单线程模型为什么既快又脆弱、持久化与内存的两难、高可用的三种形态,以及生产里真正高频的排错清单。它和前面 Nginx、防火墙、systemd 几篇是一脉------都是「开源应用落到生产」的实战。

二、原理解析

2.1 单线程事件循环:为什么快,又为什么脆弱

Redis 的命令执行是单线程的。它快,靠三件事叠加:

  1. 纯内存操作:绝大部分命令 O(1)/O(log N),没有磁盘寻道。
  2. IO 多路复用:用 epoll(Linux)/kqueue 把成千上万连接收敛到一个线程,避免上下文切换。
  3. 无锁:单线程天然没有并发竞争,实现简单、不易出错。
    但这里有个关键认知更新:Redis 6.0 引入了 IO 多线程(io-threads),只加速网络读写,命令执行仍然单线程。另外,持久化、主从同步的 RDB 传输由 fork() 出来的子进程或后台线程承担,不阻塞主线程。
    所以结论是:
  • 主线程仍是「一条车道」。任何一个慢命令(大 key、Lua 长脚本、KEYS *)都会阻塞后续所有请求------这是 Redis 延迟毛刺的头号根因。
  • 多核 CPU 对单实例帮不上忙,要靠分片(Cluster)把数据摊到多个实例才能吃满多核。
    2.2 内存与数据结构:编码方式决定 bigkey 危害
    Redis 的 value 不是裸存字符串,而是按数据量和类型选编码(encoding)。比如 Hash/List/Set 在小数据量时用紧凑的 ziplist/listpack 连续存储,一旦超过阈值就转成 hashtable/linkedlist 的指针结构。
    这就引出运维核心概念 bigkey(大 key):
  • 一个 Hash 存了 500 万个 field,或 一个 String 值 200 MB;
  • 单次 HGETALL / DEL 会把单线程卡住几百毫秒甚至秒级;
  • 主从同步、AOF 重写、Cluster 迁移时,bigkey 会放大阻塞和带宽。

扫描 bigkey(别用 KEYS,用 --bigkeys)

redis-cli -h 10.0.1.20 -p 6379 --bigkeys

看某个 key 的编码与元素数

DEBUG OBJECT myhash

TYPE myhash ; OBJECT ENCODING myhash

2.3 持久化:RDB 与 AOF 的两难

机制

原理

优点

缺点

运维要点

RDB

定时 fork() 子进程做内存快照

恢复快、文件紧凑、对性能影响小

可能丢最后一次快照后的数据

save 900 1 之类阈值;fork 时 COW,内存峰值可能翻倍

AOF

追加每一条写命令,可 rewrite 压缩

数据更安全(everysec 最多丢 1s)

文件大、恢复慢、fsync 有开销

appendfsync everysec;定期 BGREWRITEAOF

混合(推荐)

RDB 全量 + AOF 增量

兼顾恢复速度与安全性

Redis 4.0+

aof-use-rdb-preamble yes

fork() 是隐藏雷区:做大内存实例的 RDB/AOF 重写时,fork 本身要复制页表,几十 GB 实例可能卡顿数百毫秒;COW(写时复制)意味着重写期间若大量写入,物理内存可能临时翻倍,触发 OOM。

2.4 过期与淘汰:别让内存悄悄打满

过期删除两条腿走路:

  • 惰性删除:访问时才检查过期------不访问就一直占着内存。
  • 定期删除:每秒抽 10 次、每次扫若干 key 主动清。
    所以过期 key 不会立刻消失是正常现象,大量冷 key 过期要靠定期删除慢慢清理。
    内存打满后靠 maxmemory-policy 淘汰(必须设 maxmemory,否则物理内存耗尽被 OOM Kill):
    策略
    含义
    适用
    noeviction
    不淘汰,写报错
    不能丢数据的缓存场景(慎用)
    allkeys-lru
    全库 LRU 淘汰
    通用缓存(最常用)
    volatile-lru
    仅带 TTL 的 LRU
    混用缓存+持久数据
    allkeys-lfu/volatile-lfu
    按访问频率
    有热点倾斜的缓存(4.0+)
    allkeys-random/volatile-random
    随机
    少见
    volatile-ttl
    先淘汰快过期的
    少见
    经验:缓存场景一律 allkeys-lru 或 allkeys-lfu,且务必设 maxmemory。用 noeviction 等于把炸弹埋给自己。
    2.5 高可用三形态:主从 → 哨兵 → Cluster
    主从复制(异步)
    master ──异步传播──> replica1
    └─> replica2
    全量同步(RDB 传输)+ 部分重同步(backlog 偏移量)
    注意:复制是异步的,master 宕机可能丢最后一点写

哨兵 Sentinel(自动 failover)

3 个 sentinel 监控 master,quorum 判定客观下线 → 选新 master → 通知客户端

解决「主从要人工切」的问题

Redis Cluster(数据分片 + 高可用)

16384 个 slot,按 CRC16(key)%16384 映射到节点

每个 slot 一主多从,节点间 gossip 互通

客户端请求错节点返回 MOVED(槽真搬家)/ ASK(迁移中临时重定向)

三、实战演示

3.1 生产级单机配置(docker-compose)

docker-compose.redis.yml

services:

redis:

image: redis:7.2-alpine

command:

  • redis-server

  • --requirepass ${REDIS_PWD}

  • --maxmemory 4gb

  • --maxmemory-policy allkeys-lru

  • --appendonly yes

  • --aof-use-rdb-preamble yes

  • --save 900 1

  • --save 300 100

  • --slowlog-log-slower-than 10000 # 慢于 10ms 记慢日志

  • --slowlog-max-len 128

  • --tcp-keepalive 60

ports:

  • "6379:6379"

volumes:

  • redis-data:/data

deploy:

resources:

limits:

memory: 5g # 给 AOF/RDB 重写留 headroom,别贴满 maxmemory

volumes:

redis-data:

关键:容器内存 limit 必须 > maxmemory(建议 1.2~1.5 倍),否则重写/COW 时物理内存超 limit 被 OOM Kill。

3.2 主从复制

replica 的配置里加(或命令行 REPLICAOF)

replicaof 10.0.1.20 6379

masterauth

replica-read-only yes # 从库只读(默认)

查看复制状态

redis-cli INFO replication

3.3 哨兵(三节点,防脑裂)

sentinel.conf(每个哨兵一份,monitor 指向 master)

sentinel monitor mymaster 10.0.1.20 6379 2

sentinel auth-pass mymaster

sentinel down-after-milliseconds mymaster 5000

sentinel failover-timeout mymaster 10000

防脑裂:master 至少要有 1 个 replica 且延迟 < 10s 才接受写

min-replicas-to-write 1

min-replicas-max-lag 10

3.4 Redis Cluster(三主三从)

启动 6 个节点(--cluster-enabled yes),然后建簇

redis-cli --cluster create

10.0.1.11:6379 10.0.1.12:6379 10.0.1.13:6379

10.0.1.14:6379 10.0.1.15:6379 10.0.1.16:6379

--cluster-replicas 1

查看槽位分配

redis-cli -h 10.0.1.11 -p 6379 CLUSTER SLOTS

扩容后重新分片(把 1000 个 slot 从 11 迁到 新节点 17)

redis-cli --cluster reshard 10.0.1.11:6379

--cluster-from --cluster-to

--cluster-slots 1000 --cluster-yes

3.5 连接池(客户端侧,常被忽略)

Redis 单线程,连接数 ≠ 并发能力,但连接本身吃内存(每连接约数 KB~几十 KB)。常见坑是应用端没用连接池,每次请求新建连接 → maxclients 打满(ERROR max number of clients reached)。

  • Jedis:用 JedisPool,maxTotal 控制在合理范围(如 20~50/实例),testOnBorrow 按需开。
  • Lettuce:默认基于 Netty 的共享连接 + 异步,天然高并发;但事务/阻塞命令需注意连接隔离。
  • 务必设置 timeout(服务端 timeout 300 回收空闲连接)+ 客户端 tcp-keepalive。
    3.6 与 AWS ElastiCache 对照
    自建 Redis
    ElastiCache for Redis
    备注
    一台 EC2 跑 redis-server
    托管节点,自动补丁/重启
    省运维,但黑盒
    自己配哨兵/Cluster
    Multi-AZ + 自动 failover
    托管 failover 更快更稳
    redis-cli 直连
    用 VPC 端点 + 安全组
    同样要管网络/安全组
    自定义参数
    Parameter Group
    调 maxmemory-policy 等要靠参数组
    什么场景仍要自建:跨账号/跨 VPC 的合规隔离、要用 Redis 社区版不支持的模块、或要做 ElastiCache 不做的事(如特定版本、定制编译)。其余一律上 ElastiCache,把 failover、备份交给 AWS。
    四、排错实战(重点)
    4.1 内存打满 / OOM Kill
    现象:redis-cli INFO memory 的 used_memory 逼近 maxmemory,随后写报错或进程被 OOM。
    排查:
    redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human|mem_fragmentation_ratio"
    redis-cli --bigkeys # 找大 key
    redis-cli INFO keyspace # 各库 key 数
    根因与对策:
  • 没设 maxmemory → 物理内存耗尽被系统 OOM Kill。立刻设 maxmemory + LRU 策略。
  • mem_fragmentation_ratio 远大于 1.5 → 内存碎片,可执行 MEMORY PURGE(jemalloc)或重启实例整理。
  • bigkey 占大头 → 拆分(Hash 按 field 二次分片、大 List 改用多个 List)。
    4.2 bigkey / hotkey
  • bigkey:用 --bigkeys 或 DEBUG OBJECT 定位;危害是阻塞单线程 + 放大同步/迁移。
  • hotkey:某个 key 被海量请求打(如爆款商品库存)。redis-cli --hotkeys(需 maxmemory-policy 为 LFU 类才能统计)或看 INFO commandstats + 业务埋点。
    对策:bigkey 拆分;hotkey 本地缓存 + 多副本(key 加随机后缀打散到多个副本)+ 限流。
    4.3 延迟毛刺(slowlog + latency monitor)
    现象:偶发 P99 延迟从 1ms 跳到几百 ms。
    redis-cli SLOWLOG GET 20 # 看慢命令(含执行时长、时间戳)
    redis-cli CONFIG SET latency-monitor-threshold 100 # 开启延迟监控(100ms)
    redis-cli LATENCY LATEST
    redis-cli LATENCY GRAPH command
    常见根因:bigkey 操作、KEYS */FLUSHALL 误操作、fork 阻塞(持久化)、操作系统 swap(必查)、跨可用区网络抖动、CPU 竞争。
    swap 是隐形杀手:Redis 一旦被换页到磁盘,单线程会因缺页暂停几百 ms。务必关 swap(swapoff -a)或设 vm.swappiness=1,并保证内存充足。
    4.4 连接数暴涨 / maxclients 报错
    redis-cli INFO clients | grep -E "connected_clients|blocked_clients"
    redis-cli CLIENT LIST # 看每连接来源 IP、空闲时间、命令
    根因:应用端没用连接池(每次新建连接)、连接泄漏(没关)、timeout 设 0(永远不回收空闲连接)。
    对策:上连接池、timeout 300、安全组限制来源 IP、maxclients 适当调大(注意 ulimit)。
    4.5 AOF/RDB 加载慢、fork 阻塞
  • 重启恢复时 AOF 比 RDB 慢(要重放命令)。大实例建议开混合持久化。
  • fork 慢:INFO persistence 看 latest_fork_usec,几十 GB 实例可达数百 ms。大内存实例考虑分片降低单实例体量。
  • COW 期间写入放大导致内存翻倍 → OOM:给宿主机/容器留足 headroom,避开业务高峰做 BGREWRITEAOF。
    4.6 Cluster 槽位 / 重定向问题
  • CLUSTERDOWN:有 slot 未分配(reshard 中断、节点失联)。CLUSTER SLOTS + CLUSTER NODES 定位缺失 slot,补 CLUSTER ASSIGNSLOT / CLUSTER SETSLOT。
  • MOVED vs ASK:MOVED = 槽已永久迁移,客户端应更新路由;ASK = 迁移进行中临时重定向,客户端本次请求跟过去、但路由表不变。
  • 客户端没开 Cluster 模式:普通客户端连 Cluster 会被 MOVED 拒绝,必须用支持 Cluster 的客户端(Jedis Cluster / Lettuce Cluster)。
    4.7 脑裂(split-brain)
    网络分区导致出现双 master 同时接受写,分区恢复后数据冲突/丢失。
    对策:min-replicas-to-write 1 + min-replicas-max-lag 10,让 master 在失去副本时拒绝写,以可用性换一致性。ElastiCache 的 Multi-AZ 也用类似机制。
    4.8 KEYS 卡死 → 用 SCAN
    KEYS * 在百万 key 上会阻塞单线程几十秒,线上严禁。改用游标迭代:
    redis-cli --scan --count 1000 | grep "session:*"

或代码里 SCAN 游标

同理 FLUSHALL/DEL 大 key 用 UNLINK(异步惰性删除,不阻塞)。

4.9 与 ElastiCache 排错衔接

ElastiCache 出问题时,底层还是这套语言:

  • 连接超时/failover 抖动:查 Event(控制台)+ 节点重启,多半是内存压力触发托管重启或网络分区 → 回到 4.1/4.7。
  • CPU 100%:单分片单线程瓶颈 → 看 EngineCPUUtilization,需增加分片摊负载(ElastiCache 在线 reshard)。
  • 内存逐出/写失败:Evictions 指标上升、CurrItems 陡降 → 回到 4.1 的淘汰策略与 bigkey。
    五、总结
    Redis 运维的核心就三条线,记住就不会慌:
  1. 单线程 = 一条车道:任何慢命令、bigkey、KEYS 都会堵全车。监控靠 slowlog + latency-monitor,预防靠拆 key、用 SCAN/UNLINK、避免跨可用区。
  2. 内存是生命线:必须设 maxmemory + LRU/LFU 策略;给持久化/COW 留 headroom;盯 mem_fragmentation_ratio 和 swap。
  3. 高可用按需选形态:主从(只读扩展)→ 哨兵(自动 failover)→ Cluster(分片扛多核)。托管场景上 ElastiCache,自建只留给合规/特殊模块。
    速查表
    问题
    第一手命令
    根因方向
    内存打满
    INFO memory / --bigkeys
    没设 maxmemory、bigkey、碎片
    延迟毛刺
    SLOWLOG GET / LATENCY GRAPH
    bigkey、swap、fork、网络
    连接爆
    INFO clients / CLIENT LIST
    无连接池、泄漏、timeout=0
    集群不可用
    CLUSTER SLOTS / CLUSTER NODES
    slot 缺失、MOVED/ASK、客户端模式
    启动慢
    看 AOF 大小 / latest_fork_usec
    AOF 重放、fork 阻塞
相关推荐
QXWZ_IA1 小时前
变电站数字孪生巡检的能力分层、数据接入与空间计算边界
运维·服务器·空间计算
miaobinfei2 小时前
docker拉取dify镜像,执行docker compose up -d 报错
运维·docker·容器
小天源2 小时前
智叟云集系统 · 架构与职责分层
服务器·前端·数据库
女神下凡2 小时前
芯参谋(85):Linux_PPI_NAND_驱动开发指南
linux·运维·arm开发·驱动开发·单片机·嵌入式硬件·设计规范
天远API2 小时前
零信任架构实战:基于天远租凭分期报告构建自动化智慧租房免押分期履约评估网关
运维·人工智能·架构·自动化
我要见SA姐12 小时前
Chrome 扩展安全攻防实录:从权限滥用到供应链投毒
运维·人工智能·python·机器学习·自动化
kobe_OKOK_2 小时前
docker compose命名卷 映射到宿主机的路径
运维·docker·容器
herinspace3 小时前
管家婆实用贴-SQL 2008如何修改成混合模式
服务器·数据库·sql·管家婆软件·财务软件·合肥管家婆软件
打螺丝的小李3 小时前
制造企业质量管理系统的三类产品形态:分别解决什么问题
数据库·制造