一、引言
Redis 基本是缓存、会话、排行榜、轻量队列的「事实标准」。你 AWS 上跑的 ElastiCache for Redis 就是托管形态------但托管不等于「不用懂底层」:当 ElastiCache 出现连接超时、failover 抖动、内存告警时,你得用 Redis 的底层语言跟 AWS 工单对话;而跨账号、特殊模块、合规隔离等场景,又往往要回到自建。
这篇不堆命令,而是把运维最该懂的几条线讲透:单线程模型为什么既快又脆弱、持久化与内存的两难、高可用的三种形态,以及生产里真正高频的排错清单。它和前面 Nginx、防火墙、systemd 几篇是一脉------都是「开源应用落到生产」的实战。
二、原理解析
2.1 单线程事件循环:为什么快,又为什么脆弱
Redis 的命令执行是单线程的。它快,靠三件事叠加:
- 纯内存操作:绝大部分命令 O(1)/O(log N),没有磁盘寻道。
- IO 多路复用:用 epoll(Linux)/kqueue 把成千上万连接收敛到一个线程,避免上下文切换。
- 无锁:单线程天然没有并发竞争,实现简单、不易出错。
但这里有个关键认知更新: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
role:slave / master_link_status:up / master_sync_in_progress:0 都是关键指标
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 运维的核心就三条线,记住就不会慌:
- 单线程 = 一条车道:任何慢命令、bigkey、KEYS 都会堵全车。监控靠 slowlog + latency-monitor,预防靠拆 key、用 SCAN/UNLINK、避免跨可用区。
- 内存是生命线:必须设 maxmemory + LRU/LFU 策略;给持久化/COW 留 headroom;盯 mem_fragmentation_ratio 和 swap。
- 高可用按需选形态:主从(只读扩展)→ 哨兵(自动 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 阻塞