redis
一、是什么
1. Redis 的准确定位
Redis 是开源的、基于内存的键值数据库,也是一种非关系型数据库。它以 Key-Value 为基础模型,但 Value 不只有字符串,还包括 Hash、List、Set、Sorted Set、Bitmap、HyperLogLog、Geo 和 Stream。
Redis 不是关系型数据库,不能直接替代 MySQL 的事务、表关系、外键和复杂查询;也不是天然可靠的消息队列,List 只能实现简单队列;更不是强一致分布式数据库,主从复制和 Cluster 默认主要是异步复制。
Redis 的主要职责包括缓存、Session、计数、限流、库存预扣、排行榜、集合关系、延迟任务、事件流、地理位置和实时统计。
2. Redis 一次请求的执行过程
一次请求通常经历连接复用、命令写入、网络事件读取、协议解析、命令执行和响应返回几个阶段。客户端先从连接池获取连接并发送 RESP 请求,Redis 事件循环接收字节并解析出命令,命令在执行线程中完成数据访问或修改,结果写入客户端输出缓冲区,最后由网络层返回给客户端。任一阶段出现排队、阻塞或缓冲区积压,应用看到的耗时都会上升。
Redis 命令执行核心通常是单线程。单线程减少锁竞争和上下文切换,也使单条命令具有原子性;代价是一个耗时命令会阻塞同一实例上的所有请求。
必须区分:
具体包括:INCR 是单条原子命令;GET 后在应用中计算再 SET 不是原子操作;MULTI/EXEC 保证命令按顺序执行,但没有关系数据库式的通用回滚;Lua 可以将判断和修改放在一次执行中,但脚本执行期间仍占用主线程;主从复制传播副本,不等于强一致提交。
3. 内部结构
Redis 实例主要包含:
具体包括:Key 字典:根据 Key 找到 Value;过期字典:保存 Key 的过期时间;数据结构对象:保存不同编码的 String、Hash、List、Set 和 ZSet;客户端连接和输入输出缓冲区;复制 backlog;RDB、AOF 和后台任务。
过期处理分为:
具体包括:惰性过期:访问 Key 时发现已过期,再删除;主动过期:后台周期性抽样检查并删除。
因此 TTL 到期和内存立即下降不是同一时刻。
小对象可能使用 listpack 或 intset,规模变大后可能切换为哈希表、跳表等结构。ZSet 通常同时需要哈希表和跳表,以支持按 member 查找和按 score 排序。Geo 以 ZSet 为基础,Bitmap 是 String 的位操作视图。
4. 官方资源、版本和发布地址
具体包括:官网:https://redis.io;文档:https://redis.io/docs/latest/;源码:https://github.com/redis/redis;下载:https://download.redis.io/;源码安装:Redis from source;Stack 安装:Install Redis Stack;发行说明:Redis Community Edition release notes。
截至当前发行线,官方发布页面可见 Redis 8.x 主线已经迭代到 v8.10.0。生产环境必须同时确认服务端版本、客户端版本、模块版本、发行说明和持久化恢复兼容性。
5. 总体架构
Redis 快的原因是内存访问、简单命令模型、专用数据结构、事件驱动、单线程执行和较少的网络往返。Redis 慢的原因通常是 KEYS、超大集合、Big Key、复杂 Lua、超大 Pipeline、fork、内存不足、连接失控和网络抖动。
6. Redis 为什么属于非关系型数据库
关系型数据库的核心是表、行、列、主键、索引、外键和 SQL。Redis 的核心是 Key、Value、命令和数据结构。两者解决的问题不同:
| 对比项 | Redis | 关系型数据库 |
|---|---|---|
| 数据组织 | Key-Value 和专用数据结构 | 表、行、列 |
| 查询方式 | 命令、Key、范围、集合、分数 | SQL、索引、关联 |
| 事务模型 | MULTI/EXEC、WATCH、Lua | 完整事务和回滚 |
| 主要存储 | 内存,配合持久化 | 磁盘为主,配合缓存 |
| 复制方式 | 默认异步复制 | 由具体数据库实现 |
| 适用数据 | 高频访问、实时状态、缓存 | 业务事实、关系约束、复杂查询 |
| 一致性边界 | 需要业务明确 | 通常由事务和约束提供 |
| 扩展方式 | 主从、Sentinel、Cluster | 分库分表、主从、分片 |
Redis 的非关系型不是“没有结构”,而是结构不再以表关系为中心。Hash、Set、ZSet 和 Stream 仍然有明确结构,但它们的结构由业务访问方式决定。
例如:
具体包括:需要按用户 ID 直接查询对象,使用 String 或 Hash;需要判断成员是否存在,使用 Set;需要按分数排序,使用 ZSet;需要按时间消费事件,使用 Stream;需要按位统计状态,使用 Bitmap。
7. Redis 的网络协议和客户端通信
Redis 客户端通常通过 RESP 协议发送命令。命令可以理解为一个参数数组:
SET user:1001:name zhangsan EX 3600服务端返回简单字符串、整数、批量字符串、数组或错误。客户端不需要解析 SQL,而是把命令和参数按照协议编码后发送。
协议简单带来的好处:
具体包括:解析成本低;客户端容易实现;多语言客户端容易保持兼容;可以用 redis-cli 直接观察交互;Pipeline 可以在一次网络往返中发送多个命令。
协议简单不代表请求一定快。以下因素仍会影响整体耗时:
具体包括:客户端连接池等待;网络往返次数;Value 序列化和反序列化;Redis 主线程排队;服务端命令执行时间;服务端输出缓冲区;客户端读取响应的速度;应用线程池和数据库回源。
因此,应用端看到的 Redis 耗时不一定等于 Redis 命令执行耗时。SLOWLOG 主要记录服务端命令执行时间,不能代替应用端完整链路耗时。
8. Redis 的事件循环
Redis 使用事件驱动模型处理网络连接。事件循环主要处理:
事件循环先监听连接和读写事件;收到新连接后创建客户端状态;收到请求数据后解析命令和参数;命令执行完成后把响应写入输出缓冲区;Socket 可写时发送响应;连接关闭、超时或协议错误时清理客户端资源。
IO 多路复用的意义是一个线程可以同时等待多个 Socket,而不需要为每个连接创建一个阻塞线程。Linux 环境通常使用 epoll,其他系统使用对应的事件机制。
事件循环模型适合大量短请求。它不适合把长时间计算、无限集合遍历和大批量数据处理放在主线程。以下命令虽然语法正确,但可能破坏整个实例的延迟:
具体包括:KEYS 对全部 Key 扫描;HGETALL 读取超大 Hash;SMEMBERS 读取超大 Set;LRANGE 读取整个大 List;ZRANGE 返回数十万成员;复杂 Lua 扫描大集合;大事务一次执行大量命令;DEL 同步释放超大对象。
9. 单线程执行与原子性
Redis 单线程执行命令的直接结果是:一条命令执行期间,其他命令不能插入。例如 INCR 不会在读取数字和写回数字之间被另一个客户端插入。
但是,业务流程通常由多条命令组成:
GET stock:sku:1
应用判断库存
DECR stock:sku:1
创建订单这不是一个原子流程。多个客户端可能同时读到相同库存,也可能在扣减成功后创建订单失败。
可以根据边界选择:
具体包括:单条命令:直接使用原子命令;多条连续命令:使用 MULTI/EXEC;需要判断后修改:使用 WATCH 或 Lua;Redis 和数据库一起更新:使用数据库事务、消息、补偿和幂等;长时间业务流程:不要把整个流程放在 Redis 锁或 Lua 中。
单线程只保证 Redis 命令执行顺序,不保证业务系统跨组件的一致性。
10. 后台线程和子进程
Redis 的命令执行核心通常是单线程,但以下工作可能由后台线程或子进程承担:
具体包括:RDB fork 和快照写入;AOF 重写;文件关闭和异步释放;部分网络 IO;复制缓冲区处理;LazyFree 释放大对象。
先用以下命令区分任务是否正在后台运行,以及后台任务是否已经影响内存、磁盘和延迟:
INFO persistence
INFO memory
INFO commandstats
LATENCY LATEST
SLOWLOG GET 20手工触发快照或 AOF 重写时,只使用后台命令,并在执行前确认磁盘和内存余量:
INFO memory
INFO persistence
BGSAVE
BGREWRITEAOFrdb_bgsave_in_progress、aof_rewrite_in_progress 变为 1 说明任务正在进行;完成后分别检查 rdb_last_bgsave_status 和 aof_last_bgrewrite_status。删除大对象优先使用 UNLINK,但仍要观察后台释放造成的 CPU 和内存压力。
后台任务不会消除资源消耗。RDB fork 仍然需要复制进程页表,写时复制仍然会增加内存;AOF 重写仍然需要磁盘空间和磁盘 IO;LazyFree 仍然需要后台 CPU。
判断一个任务是否会影响主线程,要看两个问题:
判断任务是否影响主线程,先看它是否包含大范围遍历、复杂计算或同步释放;再看 Redis 是否已经将这部分工作交给后台线程或子进程。即使任务在后台执行,也要继续评估 fork、磁盘、CPU 和内存带来的间接影响。
例如 UNLINK 可以减少同步释放对主线程的影响,但大量 UNLINK 仍可能让后台释放堆积,不能把超大对象当作没有成本。
11. Redis 数据库和逻辑库
Redis 默认可以创建多个逻辑数据库:
SELECT 0
SELECT 1
DBSIZE逻辑库只是同一 Redis 实例中的不同 Key 空间,并没有隔离 CPU、内存、网络、持久化和高可用能力。
使用多个逻辑库不能解决:
具体包括:不同业务需要不同淘汰策略;不同业务需要不同持久化策略;不同业务需要独立扩容;不同业务需要独立故障转移;一个业务的慢命令影响另一个业务。
Cluster 模式通常只使用 DB 0。生产环境更常使用 Key 前缀区分业务,并通过 ACL 限制 Key 模式。
12. Key 字典和过期字典
Redis 维护 Key 到 Value 的映射:
user:1001 -> Hash
rank:daily -> Sorted Set
session:abc -> String设置 TTL 后,Redis 还需要记录:
session:abc -> 过期时间同一个 Key 被覆盖时,TTL 行为取决于具体命令。普通 SET 通常会覆盖原值并清除旧 TTL;使用带 EX 或 PX 的 SET 可以在写入时重新设置 TTL。
常见命令:
SET key value EX 60
TTL key
PERSIST key
SET key value KEEPTTL应用必须明确写入是否保留旧 TTL。对缓存而言,错误地使用不带 TTL 的 SET,可能让 Key 变成永久数据;对状态数据而言,错误地覆盖 TTL,可能让状态提前过期。
13. 过期机制的实际含义
设置:
SET login:code:13800000000 483921 EX 300
TTL login:code:13800000000TTL 到达后,Key 不一定在同一毫秒被物理删除。原因是:
具体包括:Redis 不会持续扫描所有 Key;访问 Key 时才会触发惰性过期;主动过期使用抽样机制;删除大对象需要释放内存;后台释放可能存在排队。
大量 Key 同时过期会形成几个问题:
具体包括:主动过期任务增加;业务大量缓存未命中;数据库回源突然增加;应用线程等待数据库;数据库连接池耗尽;形成缓存雪崩。
因此 TTL 设计不仅是数据生命周期问题,也是流量控制问题。
14. 内存编码和数据结构转换
Redis 对外提供稳定数据类型,对内根据数据规模选择编码。常见原则:
具体包括:小 Hash 使用紧凑结构,元素增多后转为哈希结构;小 Set 且成员为整数时使用整数集合;ZSet 使用适合排序和成员查找的组合结构;String 的位操作使用 Bitmap;Geo 使用地理编码后的 Sorted Set。
编码转换可能发生在一次写入期间。一个本来很小的对象增加到阈值后,Redis 可能重新组织内部结构。业务感知不到类型变化,但会感受到这次操作的 CPU 和内存开销。
查看对象占用:
MEMORY USAGE key:name
OBJECT ENCODING key:name
OBJECT FREQ key:name
OBJECT IDLETIME key:nameOBJECT ENCODING 可以帮助判断内部编码,MEMORY USAGE 可以估算对象占用,OBJECT FREQ 和 OBJECT IDLETIME 可辅助热点分析,但需要结合版本和淘汰策略理解。
15. 命令复杂度和数据量
常见复杂度:
| 命令 | 典型复杂度 | 说明 |
|---|---|---|
| GET、SET | O(1) | Value 大时网络成本仍然增加 |
| INCR | O(1) | 只保证单条命令原子 |
| HGET、HSET | O(1) 平均 | 大 Hash 不应全量读取 |
| LPUSH、RPOP | O(1) | 适合两端操作 |
| LRANGE | O(S+N) | S 是起始定位,N 是返回数量 |
| SADD、SISMEMBER | O(1) 平均 | 成员数量大时全量读取危险 |
| SINTER | 与集合规模相关 | 大集合交集可能阻塞 |
| ZADD | O(logN) | 集合无限增长会影响成本 |
| ZRANGE | O(logN+M) | M 是返回元素数 |
| KEYS | O(N) | 扫描全库,生产禁止 |
| SCAN | 分批迭代 | 不提供严格快照 |
| DEL | 与释放对象相关 | 大对象可能阻塞 |
| UNLINK | 主线程释放较少 | 后台仍要承担释放成本 |
| EVAL | 取决于脚本 | 脚本无界会阻塞实例 |
复杂度只描述算法增长趋势,不包括网络、序列化、内存分配、复制和持久化成本。一个 O(1) 的 GET,如果返回 100 MB Value,仍然不是低成本操作。
16. Redis 的版本演进重点
Redis 5 时代的核心能力包括 Stream、主从、Sentinel、Cluster、RDB、AOF 和基础数据结构。
Redis 6 重点增加:
具体包括:ACL;IO 线程;客户端缓存和 Tracking;更完善的多线程 IO;更细的权限模型。
Redis 7 继续增强:
具体包括:Function;ACL 和命令能力;Stream 维护能力;复制和 Cluster 相关能力;数据结构和内存管理优化。
Redis 8.x 的具体命令和默认配置仍需以官方发行说明为准。升级时不能只替换 Redis 二进制,还要检查:
具体包括:客户端连接行为;ACL 规则;RDB 和 AOF 恢复;模块数据;Sentinel 和 Cluster;序列化格式;监控指标名称;默认配置变化。
17. Redis 与缓存、数据库、消息系统的关系
缓存链路:
应用 -> Redis -> 数据库Redis 缓存通常保存数据库结果,而不是取代数据库事实。缓存丢失后,应用应能够从数据库重新构建;如果缓存丢失会造成业务事实丢失,就不能把它当作普通缓存。
状态链路:
应用 -> Redis 原子状态 -> 数据库事实和补偿例如库存预扣可以先在 Redis 中控制并发,但最终订单、支付和库存事实必须落到可靠存储。
消息链路:
业务变更 -> Stream 或消息系统 -> 消费者 -> 数据库、缓存、搜索Redis Stream 可以承担中小规模事件流,但需要设计 ACK、Pending、重试、幂等和保留长度。
18. Redis 的故障边界
Redis 故障可以来自不同层面:
具体包括:进程层:崩溃、启动失败、命令阻塞;机器层:内存不足、CPU 抢占、磁盘满;网络层:丢包、分区、端口不通、地址宣告错误;数据层:大 Key、错误 TTL、错误类型、数据不一致;复制层:延迟、全量同步、复制中断;高可用层:Sentinel 无法达成 quorum、Cluster 槽位缺失;客户端层:连接池泄漏、重试风暴、拓扑不刷新;业务层:缓存雪崩、重复消费、锁错误释放、回源打穿。
排障不能只执行 PING。PING 只能证明某个网络连接在某个瞬间可用,不能证明数据正确、复制正常、持久化成功和业务恢复。
19. Redis 核心结论
Redis 的核心判断可以归纳为:
Redis 的核心判断应依次落到数据模型、访问复杂度、内存容量、持久化和恢复目标、高可用边界、客户端行为、故障降级以及监控验证上。只有这些条件同时成立,Redis 才是可落地的系统方案,而不是只因为“速度快”就被引入。
二、为什么
1. 什么时候使用 Redis
适合使用 Redis 的条件:
具体包括:访问主要按 Key、范围、分数、集合或时间流;关系数据库已经成为延迟或吞吐瓶颈;数据可以放进内存,或有清晰的淘汰和分层策略;业务需要原子自增、过期、条件写入、批量操作或脚本;数据有可靠来源、恢复方式和一致性边界;团队能够维护容量、持久化、备份、高可用和监控。
2. 什么时候不能只使用 Redis
资金、账务、订单、支付事实不能只放 Redis。需要复杂关联查询、审计和报表的数据也不应只放 Redis。需要长期保留、确认、重放的消息不能只使用 LPUSH 和 BRPOP。
合理分工:
关系数据库:主数据和业务事实
Redis:热点副本、实时状态和高频计算
消息系统:传播变化、削峰、重试和重放
对象存储:大文件和归档数据
搜索系统:全文检索和复杂聚合3. 数据角色和部署方式
| 数据角色 | 允许丢失 | 重点设计 |
|---|---|---|
| 可重建缓存 | 可以,但要保护数据库 | TTL、淘汰、预热、击穿处理 |
| Session 和验证码 | 可部分丢失 | TTL、限流、重新登录 |
| 限流和临时状态 | 可降级 | 原子计数、TTL、失败策略 |
| 库存预扣 | 不能只依赖 Redis | 原子扣减、事实库、对账补偿 |
| 订单支付状态 | 不能丢 | 数据库、消息、幂等、对账 |
| 任务事件 | 不能无声丢失 | Stream、ACK、重试、补偿 |
| 模式 | 能力 | 限制 |
|---|---|---|
| 单机 | 简单、成本低 | 实例故障即不可用 |
| 主从 | 副本、读扩展 | 不自动选主 |
| Sentinel | 单分片自动故障转移 | 不提供分片 |
| Cluster | 16384 槽位横向扩展 | 多 Key 受槽位限制 |
4. 一致性边界
主从复制默认异步。主节点成功响应时,从节点可能还没有收到写入。可以设置:
min-replicas-to-write 1
min-replicas-max-lag 10关键写入可以执行:
SET order:1001:status paid
WAIT 1 1000WAIT 只等待副本收到数据,不保证已经刷盘,也不解决跨数据库事务和业务幂等。
5. 选择 Redis 之前先回答六个问题
5.1 数据是否允许丢失
先用持久化和复制状态确认当前系统能提供什么保护,再把结果与业务允许的数据损失窗口对照:
INFO persistence
INFO replication
CONFIG GET appendonly
CONFIG GET save
LASTSAVE这些命令只能说明当前配置和状态,不能替业务定义 RPO。aof_last_write_status、RDB 最近成功时间和复制 offset 都应纳入验证记录。
先把数据分成四类:
| 类型 | 示例 | Redis 故障后的处理 |
|---|---|---|
| 可重建缓存 | 商品详情、查询结果 | 从数据库重新加载 |
| 临时状态 | Session、验证码、限流计数 | 重新登录、重新申请或降级 |
| 业务事实 | 订单、支付、资金、最终库存 | 不能依赖 Redis 恢复 |
| 事件数据 | Stream、任务、领域事件 | 需要 ACK、重试、补偿和重放 |
可重建缓存可以优先考虑低成本和高吞吐;业务事实必须先设计可靠存储;事件数据要先确定消息保存时间和消费语义。
如果一个团队说“Redis 丢了也没关系”,必须继续追问:
具体包括:丢失后是否会打穿数据库?;Session 丢失是否会导致所有用户重新登录?;限流状态丢失是否会导致接口被攻击?;库存状态丢失是否会导致超卖?;Stream 丢失是否会导致订单状态没有传播?;数据能否在目标恢复时间内重建?。
5.2 访问模式是什么
访问模式可以用命令和慢日志共同确认,不能只根据业务名称猜测:
SCAN 0 MATCH prod:* COUNT 100
TYPE prod:user:1001:profile:v2
TTL prod:user:1001:profile:v2
SLOWLOG GET 50
INFO commandstats如果线上主要出现范围扫描、全量读取或复杂聚合,应重新评估数据模型,必要时把搜索、分析和报表交给更合适的系统。
Redis 适合已知 Key 或有限范围查询:
GET user:1001
HGET user:1001 name
ZRANGE rank:daily 0 99 WITHSCORES
SISMEMBER user:1001:roles admin
GEOSEARCH store:geo ...不适合把 Redis 当成任意查询引擎:
具体包括:复杂多条件筛选;多表关联;任意字段排序;复杂聚合;大范围报表;长期历史分析。
如果业务只能通过扫描大量 Key 才能找到结果,通常说明 Key 模型不适合 Redis,或者应该引入数据库、搜索系统或离线计算系统。
5.3 数据规模能否放入内存
容量判断至少要同时查看数据内存、RSS、碎片、客户端缓冲区和 Key 空间:
INFO memory
MEMORY STATS
INFO keyspace
INFO clients
CONFIG GET maxmemory不能用 Value 大小简单相加代替容量预算。必须用真实 Key 数量、平均大小、最大对象、增长速度、复制数量和持久化 fork 余量计算可用上限。
容量不能只看当前数据量,还要计算增长:
目标容量 = 当前数据 + 增长周期数据 + 复制余量 + fork 余量 + 系统余量至少统计:
具体包括:当前 Key 数量;平均 Key 长度;平均 Value 大小;最大 Value 大小;Hash、List、Set、ZSet 的平均和最大成员数;每天新增数据量;TTL 过期速度;是否启用 RDB 和 AOF;是否有副本和 Cluster 迁移;机器可用内存和磁盘空间。
如果数据本身超过内存,不能简单地说“加 maxmemory”。maxmemory 只能限制 Redis 的数据内存,不会消除操作系统、fork、复制和客户端缓冲区的内存需求。
5.4 允许多长时间的数据丢失
用持久化时间点、复制状态和备份时间验证实际保护能力:
INFO persistence
INFO replication
LASTSAVE如果业务要求接近零丢失,单靠 appendfsync everysec 和异步复制不够,还需要同步确认、事实库、消息补偿和恢复演练;命令输出不能替代这些架构措施。
用 RPO 描述允许丢失的数据时间:
具体包括:RPO 接近 0:需要更强的持久化确认、同步策略和业务事实库;RPO 几秒:可以采用 AOF everysec、异步复制和业务补偿;RPO 几分钟:定期 RDB 和异机备份可能足够;可完全重建:可以关闭持久化,但必须验证数据库回源能力。
Redis 的 AOF everysec 通常只能把刷盘丢失窗口控制在约一秒级,异步复制仍可能在主节点故障时丢失尚未传播的写入。RPO 必须由业务确定,不能由 Redis 默认配置决定。
5.5 允许多长时间不可用
RTO 需要通过切换和恢复实测,先检查高可用组件是否具备切换条件:
SENTINEL CKQUORUM mymaster
SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
CLUSTER INFO
ROLE记录从故障注入到客户端恢复读写的时间,并把连接池刷新、DNS 或拓扑更新、数据校验和回源压力都算入恢复时间。
用 RTO 描述恢复时间:
具体包括:单机重启的 RTO 取决于数据量和持久化恢复速度;主从手工切换依赖人工处理时间;Sentinel 自动切换依赖检测、选举和客户端重连;Cluster 故障转移依赖槽位、主从关系和客户端拓扑刷新;从备份恢复通常比故障转移更慢。
RPO 和 RTO 要一起确定。高可用主要缩短 RTO,持久化和备份主要控制 RPO;二者不能互相替代。
5.6 故障时应用如何运行
故障策略要在应用和 Redis 两端验证。Redis 侧先记录连接、错误和复制状态:
INFO clients
INFO stats
INFO replication
ROLE应用侧必须明确连接失败、读超时、写超时、主从切换和缓存未命中的处理方式。缓存可以降级为回源,但 Session、库存和消息不能无条件回源,否则 Redis 故障会直接放大为数据库故障。
必须提前确定:
具体包括:Redis 不可用时是否直接失败;是否使用本地缓存;是否允许读数据库降级;是否关闭非核心功能;数据库能承受多少回源;哪些写入必须拒绝;哪些请求可以返回旧数据;故障恢复后如何分批预热。
没有降级设计的 Redis 高可用,仍可能在故障时因为数据库回源把整个系统拖垮。
6. 什么时候不应该使用 Redis
6.1 不能替代关系数据库
以下需求由关系数据库更适合承担:
具体包括:订单事实;资金流水;多行事务;唯一约束;外键关系;复杂筛选;报表聚合;审计和追溯。
Redis 可以保存订单状态缓存、支付状态副本和高频查询结果,但最终事实应该有可靠来源。
6.2 不能替代专业消息系统
List 和 Stream 可以承担部分消息场景,但要评估:
具体包括:消息是否需要长期保存;是否需要多个独立订阅者;是否需要按分区扩展;是否需要大量重放;是否需要复杂消费位点;是否需要跨机房复制;是否需要成熟的死信和重试管理。
如果这些需求很强,Kafka、RabbitMQ 等系统通常更合适。Redis 适合低延迟、短周期、规模可控的任务和事件。
6.3 不能把 Redis 当搜索引擎
使用 SCAN 加应用过滤的方式不能代替搜索索引。数据量变大后,会产生:
具体包括:扫描耗时;网络传输;应用 CPU;Redis 主线程压力;查询延迟不可预测。
全文搜索、多字段组合筛选和复杂聚合应使用 Elasticsearch、数据库索引或其他搜索系统。
6.4 不能用 Redis 解决所有分布式一致性
分布式锁可以减少重复执行,但不能保证:
具体包括:客户端永远不暂停;业务永远不超时;数据库写入永远成功;网络永远不分区;重试永远不重复;业务状态永远可恢复。
锁必须配合 TTL、唯一令牌、幂等、超时和补偿。高可用也不能自动解决业务一致性。
7. 数据角色决定部署和持久化
7.1 纯缓存
特点:
具体包括:数据可以从数据库重建;可以接受 Redis 重启后缓存为空;重点是保护数据库回源;可以选择关闭持久化;通常设置 maxmemory 和淘汰策略;更关注命中率、回源 QPS、热 Key 和雪崩。
纯缓存并不等于不需要高可用。如果缓存故障会让数据库无法承受回源,仍然需要主从、Sentinel 或 Cluster。
7.2 Session 和临时状态
特点:
具体包括:数据有明确 TTL;丢失后可以让用户重新登录或重新申请;需要避免无 TTL Key;需要限制验证码和限流计数的增长;需要防止 Redis 故障时所有请求同时回源。
Session 适合集中存储,但要限制对象大小。不要把完整用户对象、菜单树和权限快照全部放进每个 Session。
7.3 业务状态
库存预扣、订单状态和支付状态即使放入 Redis,也需要可靠事实库。Redis 可以承担:
具体包括:高并发扣减;快速状态读取;幂等标记;临时锁;状态变化通知。
最终要由数据库、消息、对账和补偿保证业务可恢复。
7.4 事件和任务
事件数据需要定义:
具体包括:事件 ID;生产时间;保留时间;消费组;ACK 时机;重试次数;失败处理;幂等键;积压报警;数据归档。
只保存消息而不设计消费失败路径,不能称为可靠消息方案。
8. 单机、主从、Sentinel、Cluster 怎么选
8.1 单机
适合:
具体包括:本地开发;功能测试;低风险、可完全重建的缓存;临时计算结果。
不适合:
具体包括:不能中断的生产服务;不允许丢失的状态;单机内存无法满足的数据;需要自动故障转移的业务。
8.2 主从
适合:
具体包括:需要副本;读请求可以访问副本;需要手工故障恢复;数据规模仍能放在一个主节点。
局限:
具体包括:主节点故障不会自动选主;副本默认异步;从节点存在复制延迟;读副本可能读到旧数据;主从不能解决单主容量上限。
8.3 Sentinel
适合:
具体包括:一个逻辑主分片;需要自动故障转移;数据规模可以放入单主;客户端支持 Sentinel;能接受异步复制的数据丢失窗口。
局限:
具体包括:不提供数据分片;主节点仍是写入瓶颈;故障转移期间可能短暂不可写;客户端必须发现新主;Sentinel 自身也要分布到独立故障域。
8.4 Cluster
适合:
具体包括:单实例内存和吞吐不够;业务可以接受槽位分片;客户端支持拓扑和重定向;业务能够设计 Hash Tag;团队能够维护迁移、扩缩容和节点故障。
局限:
具体包括:多 Key 必须在同一槽位;跨槽事务和跨槽 Lua 受限制;节点加入不等于槽位迁移;一个槽位无可用主节点时该槽位不可用;网络、地址宣告和 bus 端口配置复杂。
9. 一致性、可用性和性能如何取舍
9.1 优先可用
适合一般缓存:
具体包括:允许短暂旧数据;允许数据从数据库重建;Redis 故障时可降级;使用较短连接超时;失败时返回本地缓存或旧值。
风险是数据可能短时间不一致,需要 TTL、消息和校准收敛。
9.2 优先一致
适合订单、权限和关键状态:
具体包括:关键读走主节点或事实库;更新数据库成功后删除缓存;使用版本号阻止旧值覆盖;使用消息重试删除失败;使用幂等和对账;关键写入可以等待副本确认。
代价是延迟、复杂度和故障时的写入拒绝增加。
9.3 优先性能
适合高频只读缓存:
具体包括:使用本地缓存和 Redis 多级缓存;使用 Pipeline;使用连接池;使用短 Key 和小 Value;避免复杂结构全量读取;关闭不必要的持久化。
代价是持久化能力、实时一致性和故障恢复能力可能降低。
10. 高可用设计中的故障域
高可用不能只看节点数量,还要看节点是否独立。
需要考虑:
具体包括:机器故障;机架故障;可用区故障;网络交换机故障;电源故障;磁盘故障;容器宿主机故障;公共依赖故障。
三个 Sentinel 如果都在一台机器上,只能提供三个进程,不能提供三个故障域。主节点和副本如果在同一台宿主机上,也不能真正抵御宿主机故障。
Cluster 的主从应尽量分散到不同机器和可用区。否则主节点和副本可能同时失效。
11. 高可用不是零数据丢失
高可用解决的是:
具体包括:节点故障后自动或快速切换;客户端找到新的主节点;服务尽量继续提供读写。
高可用不能自动解决:
具体包括:异步复制尚未传播的数据;AOF 尚未刷盘的数据;应用重复重试;数据库和缓存双写不一致;错误删除和错误释放锁;业务消息已经消费但 ACK 失败;旧主恢复后产生脑裂风险。
因此架构上要分别设计:
可用性:Sentinel、Cluster、副本、客户端重连
持久性:RDB、AOF、异机备份、恢复演练
一致性:版本号、幂等、消息、补偿、对账
容量:拆分、扩容、淘汰、冷数据
观测:指标、日志、慢日志、业务告警12. 成本和复杂度选择
单机的成本最低,但故障风险最高;Sentinel 增加节点和运维成本,但不改变数据分片;Cluster 提升容量和吞吐,也增加客户端、迁移、槽位和故障处理复杂度。
不能因为 Cluster 能扩容,就在数据量很小的系统中直接使用 Cluster。也不能因为 Sentinel 简单,就把超过单主容量的数据硬塞入 Sentinel。
选择判断:
| 现状 | 推荐起点 |
|---|---|
| 开发和学习 | 单机 |
| 可重建小型缓存 | 单机或主从 |
| 生产单分片 | 主从加 Sentinel |
| 多主分片需求 | Cluster |
| 不希望维护底层设施 | 托管 Redis |
| 高可靠事实数据 | Redis 加事实数据库和补偿体系 |
13. 选型示例
13.1 商品详情缓存
条件:
具体包括:数据来自 MySQL;丢失后可以回源;读多写少;访问存在热点。
设计:
具体包括:Cache Aside;商品详情使用 String JSON;TTL 加随机值;热点商品使用本地缓存;数据库更新后删除缓存;处理穿透、击穿和雪崩;纯缓存可以评估关闭持久化。
创建商品缓存时,把数据库查询结果序列化后写入 Redis,并让每个 Key 带有明确的失效时间:
SET prod:product:sku-1001:detail:v1 '{"id":1001,"name":"phone","price":3999}' EX 2100 NX
GET prod:product:sku-1001:detail:v1
TTL prod:product:sku-1001:detail:v1读取时先执行 GET;返回空值才回源 MySQL;回源成功后使用 SET ... EX 回填。商品更新时先提交数据库,再删除旧缓存,避免把数据库事务未提交的数据写入缓存:
DEL prod:product:sku-1001:detail:v1
SET prod:product:sku-1001:detail:v2 '{"id":1001,"name":"phone","price":4099}' EX 2400热点 Key 需要防止多个请求同时回源。可以使用短 TTL 的互斥 Key,获取不到锁的请求短暂等待后重新读取缓存;锁必须带过期时间,释放时要校验 owner token,不能直接删除别人的锁。
13.2 登录 Session
条件:
具体包括:多个应用实例共享登录状态;TTL 明确;Redis 故障时可以要求重新登录;对象大小可控。
设计:
具体包括:String 或 Hash;设置滑动 TTL;不保存大对象;登出删除 Key;限制 Session 总大小;Redis 故障时返回重新登录,而不是无限回源。
String 适合整体读写 Session。登录成功后写入带 TTL 的值,收到有效请求后按安全策略刷新 TTL:
SET session:user:1001 '{"userId":1001,"login":true,"role":"user"}' EX 1800
GET session:user:1001
TTL session:user:1001
EXPIRE session:user:1001 1800登出时删除 Key,并验证删除结果:
DEL session:user:1001
EXISTS session:user:1001如果需要按字段更新,可以使用 Hash,但仍要对整个 Session 设置 TTL:
HSET session:user:1001 userId 1001 role user login true
EXPIRE session:user:1001 1800
HGETALL session:user:1001
TTL session:user:1001Redis 不可用时,Session 不能无限回源数据库。应用应区分连接失败、Key 不存在和认证失败,连接失败时按业务安全策略返回重新登录或降级页面,并记录失败率。
13.3 秒杀库存
条件:
具体包括:并发扣减高;不能超卖;订单和支付最终要落数据库;用户可能重复提交。
设计:
具体包括:Lua 原子判断和扣减;用户和订单幂等键;Redis 锁只保护必要区域;数据库记录预扣和最终订单;取消、超时和支付失败要补偿库存;故障后以数据库和对账结果为准。
先写入可售库存,再用一个 Lua 脚本完成库存判断、幂等判断和扣减。脚本中的所有命令在 Redis 主线程内连续执行,其他客户端不能插入:
SET stock:sku-1001 100
EVAL "local stock=tonumber(redis.call('GET',KEYS[1]) or '0'); if stock<=0 then return -1 end; if redis.call('SADD',KEYS[2],ARGV[1])==0 then return -2 end; redis.call('DECRBY',KEYS[1],1); return stock-1" 2 stock:sku-1001 order:dedupe order-1001
GET stock:sku-1001
SISMEMBER order:dedupe order-1001返回 -1 表示库存不足,返回 -2 表示订单已经处理过,返回非负数表示扣减后的库存。扣减成功只代表 Redis 预扣成功,应用还必须把订单写入数据库;数据库写入失败、订单取消或支付超时,都要执行补偿:
INCRBY stock:sku-1001 1
SREM order:dedupe order-1001补偿命令不能脱离业务状态直接执行,必须由订单状态、幂等记录和对账任务共同约束,避免重复补库存。
13.4 排行榜
条件:
具体包括:需要实时排序;读取前 N 名;历史榜单按日或按周保存;数据可以重建。
设计:
具体包括:Sorted Set;业务周期拆 Key;限制返回数量;过期后归档或删除;大榜单不要直接返回全部成员;数据丢失时从行为日志或数据库重建。
用 Sorted Set 的 score 保存排序分值,用成员保存用户或对象标识:
ZINCRBY rank:daily:current 100 user:1001
ZINCRBY rank:daily:current 80 user:1002
ZREVRANGE rank:daily:current 0 9 WITHSCORES
ZREVRANK rank:daily:current user:1001
ZRANGE rank:daily:current 0 -1 WITHSCORES读取排行榜只返回需要展示的范围,不能使用无边界的全量读取。周期结束后可以设置 Key 的保留时间,或先导出到数据库再删除:
EXPIRE rank:daily:current 604800
ZREM rank:daily:current user:1001
ZCARD rank:daily:current如果同一用户存在多个业务维度,应把维度写入 Key 或使用 Hash Tag 规划,不能把不同榜单成员混在一个无限增长的 ZSet 中。
13.5 任务事件
条件:
具体包括:任务需要确认;消费失败要重试;消费者会扩缩容;任务处理必须幂等。
设计:
具体包括:Stream 消费组;ACK 放在业务成功之后;Pending 超时转移;失败次数达到上限进入补偿流;设置消息保留长度;监控积压数量和最长等待时间。
创建 Stream 和消费组后,生产者追加消息,消费者读取未确认消息;只有业务处理成功后才能执行 XACK:
XADD task:events * task_id 1001 type email
XGROUP CREATE task:events workers 0 MKSTREAM
XREADGROUP GROUP workers worker-1 COUNT 10 BLOCK 5000 STREAMS task:events >
XACK task:events workers <message-id>消费失败或消费者宕机时,消息会留在 Pending 列表。先检查积压,再把超过处理租约的消息转移给其他消费者:
XPENDING task:events workers
XAUTOCLAIM task:events workers worker-2 60000 0-0 COUNT 10
XINFO GROUPS task:events
XTRIM task:events MAXLEN ~ 100000重复投递是正常可能性,业务处理必须使用 task_id 做幂等。达到最大失败次数的消息不能无限重试,应写入补偿 Stream,并保留原消息、失败原因和最后处理时间。
14. 为什么必须做容量和故障演练
仅做功能测试只能证明命令能执行,不能证明:
具体包括:数据量大时不会阻塞;连接数多时不会耗尽;持久化时不会超时;副本延迟时应用不会读到错误数据;Sentinel 能真正切换;Cluster 能真正迁移;Redis 故障时数据库不会被打穿;备份文件真的可以恢复。
因此 Redis 选型必须包含:
选型验证应先用真实访问模式做基线压测,再验证数据规模和内存余量;随后测试 RDB、AOF、备份恢复和重启耗时;再演练 Sentinel 或 Cluster 故障切换、客户端重连和数据一致性;最后验证 Redis 故障时数据库、限流和业务降级是否能够承受回源压力。
这些不是部署后的附加工作,而是选择 Redis 架构时判断“是否适合”的证据。
15. Redis 选型结论
选择 Redis 的结论必须同时包括:
具体包括:为什么当前访问模式适合 Redis;为什么数据可以放在内存;为什么允许或不允许丢失;为什么选择单机、主从、Sentinel 或 Cluster;为什么采用或关闭持久化;为什么允许读副本或要求读主;Redis 故障时应用如何降级;数据库能否承受回源;如何验证容量、恢复和高可用;方案的成本和限制是什么。
如果只能说“Redis 快、支持高并发、适合缓存”,还没有完成选型分析。
三、怎么做
1. 源码安装
1.1 系统准备
sudo yum install -y gcc make jemalloc-devel tcl curl
ulimit -n
free -h
df -h
vmstat 1 5预留内存给数据、过期字典、客户端缓冲区、复制 backlog、fork COW、持久化临时文件和操作系统。不要把物理内存全部设置为 Redis maxmemory。
sudo sysctl -w vm.overcommit_memory=1
echo 'vm.overcommit_memory=1' | sudo tee /etc/sysctl.d/99-redis.conf
sudo sysctl --system1.2 编译
cd /usr/local/src
curl -LO https://download.redis.io/releases/redis-7.2.6.tar.gz
tar -xzf redis-7.2.6.tar.gz
cd redis-7.2.6
make -j"$(nproc)"
make PREFIX=/usr/local/redis install
/usr/local/redis/bin/redis-server --version
/usr/local/redis/bin/redis-cli --version生产环境固定版本、下载地址和校验值,不安装未知来源二进制。
1.3 创建用户和目录
sudo useradd --system --no-create-home --shell /sbin/nologin redis
sudo mkdir -p /etc/redis /var/lib/redis/6379 /var/log/redis /run/redis
sudo chown -R redis:redis /var/lib/redis /var/log/redis /run/redis
sudo chmod 750 /var/lib/redis /var/log/redis1.4 配置文件
bind 127.0.0.1
protected-mode yes
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize no
supervised systemd
pidfile /run/redis/redis-6379.pid
loglevel notice
logfile /var/log/redis/redis-6379.log
databases 16
dir /var/lib/redis/6379
dbfilename dump-6379.rdb
appendonly yes
appendfilename appendonly-6379.aof
appendfsync everysec
aof-use-rdb-preamble yes
maxmemory 4gb
maxmemory-policy allkeys-lru参数说明:
具体包括:bind 只监听指定地址;protected-mode 降低误暴露风险;tcp-backlog 受操作系统监听队列影响;dir 保存 RDB 和 AOF;appendfsync 控制 AOF 刷盘;maxmemory 设置数据内存上限;maxmemory-policy 决定满内存后的处理。
1.5 启动和检查
/usr/local/redis/bin/redis-server /etc/redis/redis-6379.conf
redis-cli -h 127.0.0.1 -p 6379 PING
redis-cli -h 127.0.0.1 -p 6379 INFO server继续检查:
CONFIG GET bind
CONFIG GET protected-mode
CONFIG GET appendonly
CONFIG GET dir
INFO persistence
INFO memoryPONG 只证明端口可用,不证明认证、持久化和高可用正确。
2. systemd 和 Docker
systemd:
[Unit]
Description=Redis 6379
After=network.target
[Service]
Type=notify
User=redis
Group=redis
ExecStart=/usr/local/redis/bin/redis-server /etc/redis/redis-6379.conf --supervised systemd
ExecStop=/usr/local/redis/bin/redis-cli -p 6379 SHUTDOWN NOSAVE
Restart=on-failure
LimitNOFILE=100000
TimeoutStopSec=30
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now redis-6379
sudo systemctl status redis-6379
sudo journalctl -u redis-6379 -n 100 --no-pagerDocker:
docker run -d --name redis --restart unless-stopped -p 127.0.0.1:6379:6379 -v /opt/redis/data:/data -v /opt/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro redis:7.2 redis-server /usr/local/etc/redis/redis.conf容器部署必须确认数据目录、镜像版本、容器地址、Cluster bus 端口、备份目录和重启恢复。
3. 安全配置
Redis 不应直接暴露到公网。远程访问需要同时正确配置监听地址、ACL 或密码、防火墙和客户端认证。
ACL SETUSER app on >change-this-password `app:* +@read +@write -CONFIG -FLUSHALL -FLUSHDB
ACL GETUSER app
ACL LIST
ACL LOG 20应用用户只能操作 app:*,运维账号和应用账号分离,密码通过密钥管理系统注入。
4. 通用命令
监控:
redis-cli INFO server
redis-cli INFO clients
redis-cli INFO memory
redis-cli INFO stats
redis-cli INFO persistence
redis-cli INFO replication
redis-cli INFO keyspace重点关注 connected_clients、blocked_clients、rejected_connections、instantaneous_ops_per_sec、used_memory、used_memory_rss、mem_fragmentation_ratio、evicted_keys、expired_keys、keyspace_hits、keyspace_misses、持久化状态、复制 offset 和 lag。
Key 生命周期:
SET user:1:name zhangsan EX 3600 NX
GET user:1:name
EXPIRE user:1:name 600
TTL user:1:name
PTTL user:1:name
PERSIST user:1:name
TYPE user:1:name
EXISTS user:1:name
DEL user:1:name
UNLINK user:1:nameSET 和 EXPIRE 应尽量通过一个带 EX 或 PX 的 SET 完成,避免中间崩溃留下永久 Key。
生产巡检使用 SCAN:
redis-cli --scan --pattern 'user:*' --count 1000SCAN 0 MATCH user:* COUNT 1000SCAN 不是快照,可能重复。禁止在生产大库使用 KEYS。
批量清理:
redis-cli --scan --pattern 'tmp:*' | xargs -n 100 redis-cli UNLINK清库命令:
FLUSHDB
FLUSHALL
SHUTDOWN SAVE
SHUTDOWN NOSAVE应用用户禁止 FLUSHDB 和 FLUSHALL。
5. 数据结构和适用场景
5.1 String
SET app:version 2026.08 EX 3600
GET app:version
MSET config:a 1 config:b 2
MGET config:a config:b
SETNX lock:key token
INCR page:view
INCRBY stock:sku:1 -1
DECRBY quota:user:1 10适合缓存对象、计数、验证码、限额、开关和 ID。经常修改单字段时使用 Hash;需要条件判断和修改时使用 Lua。
5.2 Hash
HSET user:1 id 1 name zhangsan age 28
HGET user:1 name
HMGET user:1 name age
HGETALL user:1
HINCRBY user:1 login_count 1
HEXISTS user:1 name
HDEL user:1 age
HLEN user:1
HSCAN user:1 0 MATCH * COUNT 100适合对象属性、商品信息和购物车。HGETALL 可能返回超大结果;field 没有独立 TTL;字段过多要拆分。
5.3 List
LPUSH queue:email job-1
RPUSH queue:email job-2
LPOP queue:email
BLPOP queue:email 30
LRANGE feed:user:1 0 19
LTRIM feed:user:1 0 999适合简单任务和时间线。List 没有 ACK,消费者取出后宕机可能丢失;可靠消费使用 Stream。
5.4 Set
SADD user:1:tags java redis
SISMEMBER user:1:tags redis
SMEMBERS user:1:tags
SCARD user:1:tags
SINTER user:1:friends user:2:friends
SUNION user:1:tags user:2:tags
SDIFF user:1:tags user:2:tags
SSCAN user:1:tags 0 COUNT 100适合标签、好友、权限、去重和在线用户。大集合不要全量读取和直接做复杂交集。
5.5 Sorted Set
ZADD rank:daily 98 user:1 87 user:2
ZINCRBY rank:daily 5 user:1
ZREVRANGE rank:daily 0 9 WITHSCORES
ZRANGEBYSCORE rank:daily 90 +inf WITHSCORES
ZRANK rank:daily user:1
ZREM rank:daily user:2
ZREMRANGEBYSCORE rank:daily -inf 0适合排行榜、延迟队列、优先级和时间线。score 不会自动过期,需要主动清理成员。
5.6 Bitmap、HyperLogLog 和 Geo
SETBIT sign:day0 10001 1
GETBIT sign:day0 10001
BITCOUNT sign:day0
PFADD uv:day0 user:1 user:2
PFCOUNT uv:day0
GEOADD store:geo 116.397 39.908 store:1
GEOSEARCH store:geo FROMLONLAT 116.397 39.908 BYRADIUS 5 km WITHDIST COUNT 20 ASCBitmap 用于签到和活跃;HyperLogLog 只做基数估算,不能取出成员;Geo 经度在前、纬度在后,复杂空间分析应使用专业数据库。
5.7 Stream
XADD orders:events * order_id 1001 status paid
XGROUP CREATE orders:events order-workers 0 MKSTREAM
XREADGROUP GROUP order-workers worker-1 COUNT 10 BLOCK 5000 STREAMS orders:events >
XACK orders:events order-workers 1710000000000-0
XPENDING orders:events order-workers可靠流程是读取、校验幂等键、执行业务、成功 ACK、失败保留 Pending、用 XAUTOCLAIM 转移超时消息、超过重试上限进入补偿流。
6. 事务、Lua 和锁
MULTI/EXEC:
MULTI
SET order:1:status paid
INCR user:1:paid_count
EXECRedis 事务没有通用回滚。WATCH 适合乐观锁,但要限制重试和退避。
Lua 库存扣减:
local stock = tonumber(redis.call('get', KEYS[1]) or '0')
local count = tonumber(ARGV[1])
if stock < count then
return -1
end
redis.call('decrby', KEYS[1], count)
return stock - count分布式锁:
SET lock:order:1001 550e8400-e29b-41d4-a716-446655440000 NX PX 30000释放必须比较唯一 token 后删除:
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end不能只 DEL,因为旧锁过期后新线程可能已经获得同名锁。
7. 缓存设计
Cache Aside:
读:Redis 命中直接返回
读:未命中查询数据库,写入 Redis 后返回
写:数据库提交成功,再删除 Redis删除失败需要可靠重试、消息补偿或定时校准。先删缓存再更新数据库可能被并发读请求回填旧值。
穿透:参数校验、布隆过滤器、空对象短 TTL、限流和请求合并。
击穿:互斥锁、逻辑过期、提前刷新、热点异步更新。
雪崩:TTL 随机化、多级缓存、分批预热、限流熔断、数据库连接池保护、Sentinel/Cluster。恢复后限制回填并发。
热 Key:
redis-cli --hotkeys
redis-cli OBJECT FREQ key:name可使用本地缓存、Key 分片、定时刷新和客户端 Tracking。
Big Key:
redis-cli --bigkeys -i 0.01
redis-cli MEMORY USAGE key:name
redis-cli MEMORY STATS使用 UNLINK、分批读取、对象拆分和单 Key 上限。
8. RDB、AOF 和恢复
RDB 配置:
save 900 1
save 300 10
save 60 10000
dbfilename dump.rdb
dir /var/lib/redis
rdbcompression yes
rdbchecksum yes
stop-writes-on-bgsave-error yesSAVE 会阻塞主线程;BGSAVE 通过 fork 生成快照,主进程继续服务,但 COW 会增加内存。
SAVE
BGSAVE
LASTSAVE
INFO persistenceAOF 配置:
appendonly yes
appendfilename appendonly.aof
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yesalways 数据安全高、性能低;everysec 通常最多丢约一秒;no 由操作系统决定。
重写:
BGREWRITEAOF恢复前校验:
redis-check-rdb /backup/dump.rdb
redis-check-aof --fix /backup/appendonly.aof恢复必须在隔离实例中检查 Key 数量、类型、TTL、业务对象和应用反序列化,不能只看 Redis 能否启动。
9. 主从、Sentinel 和 Cluster
主从:
replicaof 10.0.0.10 6379
masterauth master-password
replica-read-only yes
repl-backlog-size 256mb
repl-timeout 60全量同步需要生成和传输 RDB,部分同步依赖复制 ID、offset 和 backlog。backlog 应按照峰值写入字节速率乘允许断线时间估算。
监控:
INFO replication
ROLESentinel:
port 26379
sentinel monitor mymaster 10.0.0.10 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel auth-pass mymaster master-password至少三个 Sentinel,最好分布在独立故障域。SDOWN 是单个 Sentinel 判断,ODOWN 是多个 Sentinel 按 quorum 达成共识。客户端不能写死旧主地址。
Cluster:
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
cluster-announce-ip 10.0.0.10
cluster-announce-port 7000
cluster-announce-bus-port 17000槽位:
slot = CRC16(key) mod 16384创建:
redis-cli --cluster create 10.0.0.10:7000 10.0.0.10:7001 10.0.0.11:7000 10.0.0.11:7001 10.0.0.12:7000 10.0.0.12:7001 --cluster-replicas 1扩容:
redis-cli --cluster add-node 10.0.0.13:7000 10.0.0.10:7000
redis-cli --cluster reshard 10.0.0.10:7000add-node 只加入节点,不自动迁移槽位。服务端口和 bus 端口必须同时放行。多 Key 操作要使用相同 Hash Tag,否则可能 CROSSSLOT。
10. 性能和客户端
慢日志:
slowlog-log-slower-than 10000
slowlog-max-len 256SLOWLOG GET 50
LATENCY LATEST
LATENCY DOCTOR
INFO commandstats压测:
redis-benchmark -h 127.0.0.1 -p 6379 -n 100000 -c 50 -t get,setPipeline 不是事务,批次太大会增加缓冲区和延迟。连接池要设置最大连接、获取超时、读写超时和重试次数。Jedis 适合同步调用,Lettuce 支持异步和响应式,Spring Data Redis 使用 RedisTemplate 时必须明确 Key 和 Value 序列化。
11. 业务模型
Session:
SET session:{token} '{"userId":1001,"role":"user"}' EX 1800验证码:
SET login:code:13800000000 483921 EX 300 NX分布式 ID:
INCR order:id:day0排行榜:
ZINCRBY game:rank:daily 100 user:1
ZREVRANGE game:rank:daily 0 99 WITHSCORES签到:
SETBIT sign:day0 10001 1
BITCOUNT sign:day0库存、订单、支付和任务必须继续设计事实库、幂等、补偿、对账和恢复方式,不能只完成 Redis 命令。
12. 数据结构选择的底层判断
12.1 String、Hash 和 JSON
String 适合整体读取和整体写入,例如商品详情、配置、Session 和接口结果。
示例命令:
SET prod:user:1001:profile '{"id":1001,"name":"zhangsan","age":28}' EX 3600 GET prod:user:1001:profile
如果两个线程同时读取并修改 JSON,后写入的线程可能覆盖先写入线程的字段。经常按字段更新的对象应使用 Hash:
HSET prod:user:1001:profile id 1001 name zhangsan age 28 HINCRBY prod:user:1001:profile login_count 1
Hash 的字段不能单独设置 TTL。字段需要独立过期时,应拆成独立 Key,或者保存字段过期时间并由应用判断。
12.2 Key 命名和版本
Key 应包含环境、业务、对象、标识和版本:
prod:user:1001:profile:v2 prod:order:1001:status:v1 prod:product:sku-1001:detail:v3
这样设计可以:
具体包括:通过前缀进行巡检;通过环境隔离数据;通过版本平滑升级;通过业务前缀批量清理;避免不同团队使用相同 Key。
Key 中不要保存密码、身份证号和其他隐私内容。不要把所有数据放入 user:all、order:all 这类无限增长的 Key。
12.3 TTL 设计
每一类数据都要回答:
TTL 设计应先按数据类型确定业务有效期,再决定固定 TTL、随机 TTL、滑动续期或逻辑过期;对热点缓存增加随机值,避免同一时间集中失效;对 Session、锁和验证码分别设置安全边界;上线后持续检查 TTL 分布,及时处理大量永久 Key。
建议使用基础 TTL 加随机 TTL。商品缓存可以是 30 分钟加 0 到 10 分钟随机值;验证码可以是 5 分钟固定值;Session 可以使用滑动续期;热点对象可以使用逻辑过期和异步刷新。
检查命令:
TTL prod:user:1001:profile:v2 PTTL prod:user:1001:profile:v2
TTL 为 -1 表示没有过期时间,TTL 为 -2 表示 Key 不存在。缓存实例中大量 TTL 为 -1,通常意味着失效策略没有真正落地。
12.4 List、Stream 和消息系统
List 适合简单任务:
LPUSH queue:email job-1001 BRPOP queue:email 30
List 中的消息被取出后就从队列中消失,消费者执行任务时宕机,消息可能丢失。要使用 List,业务必须具备幂等和失败补偿。
Stream 适合可靠消费:
XADD task:events * task_id 1001 type email XGROUP CREATE task:events workers 0 MKSTREAM XREADGROUP GROUP workers worker-1 COUNT 10 BLOCK 5000 STREAMS task:events > XACK task:events workers message-id
Stream 消费失败时消息进入 Pending,可以通过 XPENDING 查看,通过 XAUTOCLAIM 转移给其他消费者。需要长期保留、大规模堆积和多系统重放时,应使用专用消息系统。
12.5 Sorted Set 延迟任务
简单实现:
ZADD delay:jobs 1710000000000 job:1001 ZRANGEBYSCORE delay:jobs -inf 1710000000000 LIMIT 0 1 ZREM delay:jobs job:1001
读取和删除分开执行时,多个消费者可能拿到同一个任务。需要使用 Lua 将读取和删除合并,或者为任务增加处理中状态和租约。
延迟任务还要处理:
具体包括:消费者执行中宕机;业务执行成功但删除失败;任务重复执行;失败任务重试;重试次数上限;延迟队列无限增长。
13. 配置参数的完整理解
13.1 网络参数
bind 决定监听地址,port 决定服务端口,tcp-backlog 决定连接等待队列,timeout 决定空闲连接关闭时间,tcp-keepalive 用于发现失效连接。
检查系统队列:
sysctl net.core.somaxconn sysctl net.ipv4.tcp_max_syn_backlog
Redis 的 tcp-backlog 设置很大,但系统 somaxconn 很小时,实际效果仍然受系统限制。
13.2 客户端缓冲区
重点配置:
maxclients 10000 client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60
输出缓冲区快速增长通常说明客户端读取慢、订阅者停止消费、Replica 跟不上、应用请求了超大结果或 Pipeline 太大。不能只提高缓冲区上限,否则可能把连接问题扩大为 OOM。
13.3 内存和淘汰
常见配置:
maxmemory 8gb maxmemory-policy allkeys-lru maxmemory-samples 5 lazyfree-lazy-eviction yes lazyfree-lazy-expire yes lazyfree-lazy-server-del yes
allkeys-lru 适合一般缓存,allkeys-lfu 适合访问频率明显的热点缓存,noeviction 适合不能无声丢数据的状态型数据。volatile 策略只从设置 TTL 的 Key 中淘汰,如果大量 Key 没有 TTL,可能仍然无法释放内存。
lazyfree 只能减少主线程同步释放压力,不能让大 Key 无限增长。后台释放也会消耗 CPU 和内存。
14. 从源码安装到多实例运行
14.1 目录规划
/usr/local/redis/bin /etc/redis/redis-6379.conf /etc/redis/redis-6380.conf /var/lib/redis/6379 /var/lib/redis/6380 /var/log/redis/redis-6379.log /var/log/redis/redis-6380.log /run/redis/redis-6379.pid /run/redis/redis-6380.pid
每个实例必须独立配置 port、pidfile、logfile、dir、dbfilename、appendfilename 和服务名。
14.2 创建第二个实例
cp /etc/redis/redis-6379.conf /etc/redis/redis-6380.conf sed -i 's/6379/6380/g' /etc/redis/redis-6380.conf mkdir -p /var/lib/redis/6380 chown -R redis:redis /var/lib/redis/6380 redis-server /etc/redis/redis-6380.conf redis-cli -p 6380 PING
同机多个实例共享 CPU、内存、磁盘和 fork 资源。它适合开发和轻量隔离,不代表两台独立机器的高可用。
14.3 多实例的风险
一个实例执行 BGSAVE 时,同机另一个实例也可能遇到磁盘和内存压力。一个实例出现大 Key 或慢 Lua,也会抢占共享 CPU。
以下数据不建议放在同一实例:
具体包括:纯缓存;订单状态;Stream 消息;大量排行榜;需要高频持久化的数据;需要不同淘汰策略的数据。
15. 事务和 Lua 的执行边界
15.1 Redis 事务中的错误
Redis 事务错误分为入队错误和执行错误。
入队错误通常是命令参数错误;执行错误可能是类型错误。例如:
MULTI SET tx:key value INCR tx:key EXEC
如果 INCR 因为类型错误失败,SET 可能已经成功。Redis 不会自动回滚。
处理方式:
具体包括:执行前检查 TYPE;使用 Lua 合并检查和修改;记录每一条命令的返回值;为部分成功设计补偿;不把 Redis 事务当作跨系统事务。
15.2 Lua 的边界
Lua 脚本执行期间,其他客户端命令要等待。脚本不能做无上限循环、全量扫描、复杂排序、超大返回、外部网络调用和数量不可控的 Key 操作。
脚本输入必须限制:
具体包括:KEYS 数量;ARGV 长度;集合成员数量;循环次数;返回结果字节数。
发现脚本阻塞时查看:
SLOWLOG GET 50 INFO commandstats
SCRIPT KILL 只能终止尚未执行写操作的脚本。已经执行写操作的脚本不能安全中断,极端情况下只能等待完成或停止实例。
15.3 锁续期
长任务使用续期时:
锁续期前先使用唯一 owner token 加锁并设置初始 TTL;任务执行期间由可靠的续期机制在 TTL 过半前续租;任务完成后只允许持有正确 token 的客户端释放锁;续期失败、客户端失联或任务超过最大执行时间时,停止继续执行业务并进入补偿或人工处理。
锁不能替代幂等。即使锁正确,客户端超时重试仍可能导致业务执行两次。
16. 持久化、备份和恢复
16.1 RDB 备份流程
执行前检查:
INFO memory INFO persistence LASTSAVE
确认有足够内存、磁盘、备份目录权限,并且没有正在执行的 BGSAVE。
执行:
BGSAVE
完成后检查:
INFO persistence LASTSAVE
将文件复制到异机后计算校验值:
sha256sum /var/lib/redis/6379/dump-6379.rdb
恢复前再次计算校验值,防止文件传输损坏。
16.2 AOF 重写流程
执行前检查磁盘空间和持久化状态:
INFO persistence INFO memory CONFIG GET dir
确认磁盘可以同时保存旧 AOF、新 AOF、临时文件和备份文件。
执行:
BGREWRITEAOF
观察:
具体包括:aof_rewrite_in_progress;aof_current_size;aof_base_size;aof_pending_bio_fsync;used_memory_rss;磁盘使用率;Redis 延迟。
16.3 恢复验证
启动隔离实例后执行:
redis-server /etc/redis/restore.conf redis-cli -p 6381 PING redis-cli -p 6381 DBSIZE redis-cli -p 6381 INFO keyspace
检查对象:
TYPE user:1001 TTL user:1001 HGETALL user:1001 ZCARD rank:daily XLEN orders:events
恢复成功的标准不仅是 Redis 能启动,还包括类型、TTL、序列化、Stream Pending 和关键业务字段正确。
17. 主从复制的详细过程
17.1 复制初始化
主节点配置:
bind 10.0.0.10 port 6379 requirepass master-password masterauth master-password repl-backlog-size 256mb repl-timeout 60
副本配置:
bind 10.0.0.11 port 6379 replicaof 10.0.0.10 6379 masterauth master-password replica-read-only yes replica-priority 100
启动后检查:
INFO replication ROLE
主节点应看到 connected_slaves,副本应看到 master_link_status:up。
17.2 全量同步
全量同步过程:
副本先通过 PSYNC 尝试部分同步;如果复制积压无法覆盖断线期间的偏移量,主节点会进入全量同步;主节点生成 RDB 并通过网络发送给副本;副本加载 RDB 后继续接收主节点在同步期间积累的命令;追平偏移量后恢复为在线复制状态。
全量同步消耗主节点 fork、磁盘、网络、backlog 和副本加载资源。多个副本同时重连会形成同步风暴。
17.3 复制延迟
INFO replication
重点看 master_link_status、master_last_io_seconds_ago、master_repl_offset、slave_repl_offset、master_sync_in_progress 和 lag。
offset 差距持续扩大,说明副本处理速度小于主节点写入速度。原因可能是副本磁盘慢、CPU 不足、网络拥塞、大 Key 加载或全量同步。
18. Sentinel 故障转移
18.1 配置
port 26379 sentinel monitor mymaster 10.0.0.10 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 sentinel parallel-syncs mymaster 1 sentinel auth-pass mymaster master-password
至少三个 Sentinel,最好分布在不同机器、机架或可用区。同一台机器上的三个进程不能抵御机器故障。
18.2 故障阶段
具体包括:单个 Sentinel 连接不上主节点,标记 SDOWN;多个 Sentinel 达到 quorum,形成 ODOWN;选举 Sentinel 领导者;筛选合适副本;副本提升为主;其他副本指向新主;客户端发现新主;旧主恢复后重新成为副本。
18.3 检查命令
SENTINEL MASTER mymaster SENTINEL REPLICAS mymaster SENTINEL SENTINELS mymaster SENTINEL CKQUORUM mymaster SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
故障转移验证不能只看端口恢复,还要看客户端是否重连、旧主是否降为副本、复制是否重新建立、应用写入是否恢复以及数据丢失窗口。
19. Cluster 迁移
19.1 节点加入和槽位迁移
add-node 只加入节点拓扑,不代表节点已经拥有槽位。检查:
CLUSTER INFO CLUSTER NODES CLUSTER SLOTS
扩容:
redis-cli --cluster add-node 10.0.0.13:7000 10.0.0.10:7000 redis-cli --cluster reshard 10.0.0.10:7000
迁移期间观察:
具体包括:cluster_state;各节点内存;网络流量;客户端延迟;复制 lag;大 Key;迁移失败数量。
19.2 扩容前检查
具体包括:版本兼容;服务端口互通;bus 端口互通;宣告地址可达;数据目录为空;内存和磁盘充足;客户端支持拓扑更新;集群当前没有 fail 状态;槽位完整覆盖;没有全量同步风暴。
19.3 缩容顺序
缩容前先确认目标节点没有业务槽位或已经完成槽位迁移;再检查副本关系、客户端拓扑和集群状态;确认槽位完整覆盖后让节点退出集群并在其他节点执行忘记操作;最后删除节点实例,并观察迁移后的容量、复制延迟和请求错误率。
20. 容量和性能计算
20.1 内存公式
总内存不是 Value 大小简单相加:
总内存 = Key 元数据 + Value + 数据结构开销 + 过期字典 + 客户端缓冲区 + backlog + 碎片 + fork 余量
规划时统计:
具体包括:Key 总数;Key 平均和最大长度;Value 平均和最大大小;Hash、List、Set、ZSet 元素数量;TTL 分布;每秒写入字节数;峰值 QPS;峰值连接数;副本数量;持久化频率;允许增长周期。
20.2 常见命令复杂度
| 命令 | 典型复杂度 | 风险 |
|---|---|---|
| GET、SET | O(1) | Value 大时网络慢 |
| INCR | O(1) | 多命令组合需 Lua |
| HGET、HSET | O(1) 平均 | HGETALL 大集合危险 |
| LPUSH、RPOP | O(1) | 阻塞连接占用 |
| LRANGE | 与返回数量相关 | 全量读取 |
| SADD、SISMEMBER | O(1) 平均 | SMEMBERS 全量返回 |
| ZADD、ZINCRBY | O(logN) | 集合无限增长 |
| ZRANGE | O(logN+M) | M 太大时变慢 |
| KEYS | O(N) | 阻塞实例 |
| DEL | 与对象释放相关 | 大 Key 卡顿 |
| UNLINK | 主线程释放较少 | 后台仍消耗资源 |
| EVAL | 取决于脚本 | 无界脚本阻塞 |
20.3 监控分层
实例层:
used_memory used_memory_rss mem_fragmentation_ratio connected_clients blocked_clients instantaneous_ops_per_sec rejected_connections evicted_keys expired_keys
持久化层:
rdb_last_bgsave_status rdb_bgsave_in_progress aof_last_bgrewrite_status aof_rewrite_in_progress aof_current_size
复制层:
master_link_status master_repl_offset slave_repl_offset master_sync_in_progress lag
业务层:
缓存命中率 数据库回源 QPS 热点 Key 访问量 Stream Pending 数 队列积压时长 库存扣减失败率 Session 失效率
只监控 Redis 进程而不监控数据库回源,无法发现缓存雪崩。
可以先用 Redis 原生命令采集实例、客户端、持久化和复制指标,再把结果接入监控系统:
INFO memory
INFO clients
INFO stats
INFO persistence
INFO replication
INFO commandstats重点不是单次值,而是连续采样后的趋势。used_memory 持续增长、blocked_clients 增加、evicted_keys 突然上升、复制 offset 差距扩大或 rdb_last_bgsave_status:fail,都应关联业务 QPS、回源量和错误率判断影响。
21. Java 客户端和 Spring
21.1 连接池
连接池需要设置最大连接数、最小空闲、获取连接超时、命令读写超时、空闲检测、拓扑刷新和最大重试次数。
连接过小会让应用线程排队,连接过大则会耗尽 maxclients、文件描述符和网络资源。重试过多会形成重试风暴。
服务端可以用以下命令核对连接池是否把连接数、空闲连接和阻塞连接推到边界:
INFO clients
CLIENT LIST
CONFIG GET maxclients应用侧应同时记录连接池等待时间、获取连接超时、命令超时和重试次数。Redis 端连接数正常但应用仍然超时,通常要继续检查连接池等待和网络读写超时,不能只调大 maxclients。
21.2 Spring 配置
spring.data.redis.host=127.0.0.1 spring.data.redis.port=6379 spring.data.redis.timeout=2s spring.data.redis.connect-timeout=1s spring.data.redis.lettuce.pool.max-active=32 spring.data.redis.lettuce.pool.max-idle=16 spring.data.redis.lettuce.pool.min-idle=4 spring.data.redis.lettuce.pool.max-wait=500ms
Sentinel 必须配置逻辑主节点名称和多个 Sentinel 地址;Cluster 必须配置多个初始节点并允许客户端刷新槽位。
21.3 序列化
建议 Key 和 Hash field 使用字符串,简单值使用字符串或 JSON,复杂对象使用稳定 JSON。不要把 Java 原生序列化作为跨语言格式。升级字段和序列化器时保留兼容读取。
22. 安装后的第一轮配置检查
服务启动后不要直接接入业务,先检查:
redis-cli -h 127.0.0.1 -p 6379 PING
redis-cli -h 127.0.0.1 -p 6379 INFO server
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET bind
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET protected-mode
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET dir
redis-cli -h 127.0.0.1 -p 6379 INFO memory
redis-cli -h 127.0.0.1 -p 6379 INFO persistence检查目标:
具体包括:版本是否是计划版本;监听地址是否为预期内网地址;端口是否正确;数据目录是否可写;RDB 和 AOF 是否按预期启用;maxmemory 是否低于物理内存安全上限;当前是否有异常客户端;日志是否出现启动警告;ACL 是否已经生效。
23. 远程访问的完整配置步骤
23.1 服务端监听
配置内网地址:
bind 10.0.0.10
protected-mode yes
port 6379检查监听:
ss -lntp | grep 637923.2 防火墙和安全组
只允许应用网段访问:
sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=10.0.1.0/24 port port=6379 protocol=tcp accept'
sudo firewall-cmd --reload云环境还要在安全组中限制来源。Redis 端口不应对 0.0.0.0/0 开放。
23.3 ACL 和客户端连接
ACL SETUSER app on >strong-password `prod:* +GET +MGET +SET +DEL +EXPIRE +TTL客户端验证:
redis-cli -h 10.0.0.10 -p 6379 --user app -a strong-password PING验证权限:
GET prod:user:1001
CONFIG GET dir第一个命令应按权限成功,第二个管理命令应被拒绝。
24. 多环境和多租户 Key 设计
环境前缀:
dev:user:1001:profile
test:user:1001:profile
prod:user:1001:profile租户前缀:
prod:tenant:2001:user:1001:profile
prod:tenant:2002:user:1001:profile版本前缀:
prod:user:1001:profile:v1
prod:user:1001:profile:v2升级时可以先写 v2,再逐步切换读取,最后删除 v1。批量清理时使用前缀和 SCAN,不要使用 FLUSHDB。
25. 数据结构实战步骤
25.1 对象缓存
写入:
SET prod:product:sku-1001:detail:v1 '{"id":"sku-1001","name":"phone","price":3999}' EX 3600读取:
GET prod:product:sku-1001:detail:v1
TTL prod:product:sku-1001:detail:v1更新流程:
更新对象时先确定数据库写入是否成功;再按照一致性策略更新缓存或删除旧缓存;读取请求在缓存未命中时从数据库加载并写回带 TTL 的新值;对并发更新使用版本号、条件写入或消息顺序控制;完成后验证数据库、缓存内容和失效时间一致。
25.2 Hash 对象
HSET prod:user:1001:profile id 1001 name zhangsan age 28
HGET prod:user:1001:profile name
HINCRBY prod:user:1001:profile login_count 1
EXPIRE prod:user:1001:profile 3600Hash 整体过期,field 不单独过期。字段数量超过设计上限时拆分 Hash。
25.3 购物车
HSET cart:user:1001 sku-1 2 sku-2 1
HINCRBY cart:user:1001 sku-1 1
HGETALL cart:user:1001
HDEL cart:user:1001 sku-2
EXPIRE cart:user:1001 604800购物车要处理:
具体包括:商品下架;价格变化;库存不足;数量上限;用户多端并发修改;登录前后购物车合并;过期清理。
25.4 点赞和去重
SADD article:1001:likes user:2001
SISMEMBER article:1001:likes user:2001
SREM article:1001:likes user:2001
SCARD article:1001:likes如果还需要点赞总数,可以使用计数器,但 Set 和计数器双写可能不一致。可以定期从 Set 重算计数,或者用数据库作为最终事实。
25.5 排行榜
ZADD game:rank:daily 100 user:1
ZINCRBY game:rank:daily 20 user:1
ZREVRANGE game:rank:daily 0 99 WITHSCORES
ZREVRANK game:rank:daily user:1
EXPIRE game:rank:daily 172800榜单的分数、相同分数排序、榜单周期、过期时间和归档方式必须明确。
26. Pipeline 的正确使用
Pipeline 适合大量互不依赖的命令:
SET cache:1 value-1
SET cache:2 value-2
SET cache:3 value-3
GET cache:1
GET cache:2不适合 Pipeline 的场景:
具体包括:后一个命令依赖前一个命令结果;命令数量无法控制;返回结果非常大;业务要求每条命令立即确认;失败后无法安全重试。
批次控制方式:
具体包括:限制命令数量;限制总字节数;限制单批次执行时间;分批处理失败重试;统计单批次返回大小;对写命令设计幂等。
Pipeline 不是事务。多个命令在执行过程中可能被其他客户端插入。
27. 连接池和超时
建议分别设置:
具体包括:建立连接超时;获取连接超时;命令执行超时;读取超时;空闲连接检测;最大连接数;最小空闲连接数;最大重试次数。
超时不能无限加大。Redis 故障时,长超时会让应用线程长时间占用,最终拖垮线程池。
重试需要区分:
具体包括:GET 可以安全重试;SET 是否幂等要看写入语义;INCR 重试可能重复计数;发送任务重试可能重复投递;释放锁必须带 token;订单创建必须带业务幂等号。
28. RDB 详细操作
28.1 自动保存
save 900 1
save 300 10
save 60 10000查看:
INFO persistence
LASTSAVE手工保存:
BGSAVE生产环境不要在高峰执行 SAVE。BGSAVE 期间观察:
rdb_bgsave_in_progress
rdb_last_bgsave_status
rdb_last_cow_size
current_cow_peak28.2 备份文件
备份至少保存:
具体包括:dump.rdb;Redis 版本;redis.conf;ACL 文件;模块版本;备份时间;校验值;恢复步骤。
备份复制到异机或对象存储:
sha256sum /var/lib/redis/6379/dump-6379.rdb28.3 恢复文件
恢复步骤:
恢复 RDB 或 AOF 前先停止目标实例并保留现场文件;校验备份文件完整性、来源、时间和校验值;将文件复制到正确数据目录并修正属主和权限;启动 Redis 后检查加载日志、Key 数量、关键业务数据和 TTL;确认服务稳定后再恢复应用流量,并记录恢复点与数据缺口。
以 RDB 恢复为例,先在隔离实例执行,不要直接覆盖生产数据目录:
systemctl stop redis-6379
cp /var/lib/redis/6379/dump-6379.rdb /var/lib/redis/6379/dump-6379.rdb.before-restore
sha256sum /backup/redis/dump-6379.rdb
cp /backup/redis/dump-6379.rdb /var/lib/redis/6379/dump-6379.rdb
chown redis:redis /var/lib/redis/6379/dump-6379.rdb
systemctl start redis-6379
redis-cli -p 6379 PING
redis-cli -p 6379 DBSIZE
redis-cli -p 6379 INFO persistence恢复后至少抽查关键 Key、TTL、类型和业务数量。若加载失败,立即停止实例并恢复 dump-6379.rdb.before-restore,不要在未保留现场文件的情况下反复执行 redis-check-rdb 或覆盖原备份。
29. AOF 详细操作
29.1 刷盘策略
appendonly yes
appendfsync everysecalways 每次写入刷盘,安全性高但性能成本高;everysec 每秒刷盘,通常是生产折中;no 由操作系统决定,数据丢失窗口不可控。
29.2 重写阈值
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mbAOF 当前大小相对基础大小超过阈值后触发重写。重写前确认:
INFO persistence
INFO memory重写期间观察:
具体包括:aof_rewrite_in_progress;aof_current_size;aof_base_size;aof_pending_bio_fsync;used_memory_rss;磁盘空间;应用延迟。
29.3 AOF 损坏
先复制原文件,再执行检查和修复:
redis-check-aof /var/lib/redis/6379/appendonly.aof
redis-check-aof --fix /var/lib/redis/6379/appendonly.aof修复可能丢弃文件尾部不完整命令。修复后必须在隔离实例验证关键数据,不能直接覆盖生产文件。
30. 主从复制详细步骤
30.1 主节点
bind 10.0.0.10
port 6379
requirepass master-password
masterauth master-password
repl-backlog-size 256mb
repl-timeout 60
repl-ping-replica-period 1030.2 副本节点
bind 10.0.0.11
port 6379
replicaof 10.0.0.10 6379
masterauth master-password
replica-read-only yes
replica-priority 100启动后查看:
INFO replication
ROLE30.3 复制状态
主节点重点看:
具体包括:connected_slaves;master_repl_offset;min_slaves_good;slave0、slave1 的 state、offset 和 lag。
副本重点看:
具体包括:master_host;master_port;master_link_status;master_last_io_seconds_ago;master_sync_in_progress;slave_repl_offset。
30.4 部分同步和全量同步
副本短暂断线时,主节点会根据复制 ID 和 offset 尝试部分同步。如果缺失数据已经超出 backlog,就只能全量同步。
backlog 估算:
backlog 最小值 = 峰值写入字节速率 × 允许断线时间还要加突发流量和安全余量。backlog 太小会反复全量同步,太大则占用内存。
31. Sentinel 详细部署
31.1 三个配置
26379、26380、26381 三个 Sentinel 都监控同一个逻辑主节点:
port 26379
sentinel monitor mymaster 10.0.0.10 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel auth-pass mymaster master-password不同实例只修改端口和独立日志文件。三个 Sentinel 应部署到不同故障域。
31.2 检查状态
SENTINEL MASTER mymaster
SENTINEL REPLICAS mymaster
SENTINEL SENTINELS mymaster
SENTINEL CKQUORUM mymaster
SENTINEL GET-MASTER-ADDR-BY-NAME mymaster31.3 故障演练
演练步骤:
故障演练先记录主从、Sentinel、客户端和业务基线;再隔离主节点或停止 Redis 进程;观察 Sentinel 的主观下线、客观下线、投票和故障转移过程;确认客户端刷新主节点地址并恢复读写;最后检查复制偏移、关键数据、错误率和延迟,再恢复原拓扑。
在明确演练窗口和回滚负责人后,可以通过停止主节点模拟故障:
systemctl stop redis-6379
redis-cli -p 26379 SENTINEL MASTER mymaster
redis-cli -p 26379 SENTINEL CKQUORUM mymaster
redis-cli -p 26379 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
redis-cli -h <new-master-ip> -p 6379 INFO replication确认客户端已切换到新主节点、旧主节点恢复后变成副本,再启动旧主并检查 master_link_status:up。演练中不能只看 Sentinel 返回了新地址,还要用真实业务读写验证连接池拓扑刷新、写入恢复和数据丢失窗口。
32. Cluster 详细部署
32.1 节点配置
port 7000
dir /var/lib/redis/cluster/7000
appendonly yes
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
cluster-announce-ip 10.0.0.10
cluster-announce-port 7000
cluster-announce-bus-port 17000每个节点修改端口、目录、节点配置文件和地址。放行服务端口和 bus 端口。
32.2 创建集群
redis-cli --cluster create 10.0.0.10:7000 10.0.0.10:7001 10.0.0.11:7000 10.0.0.11:7001 10.0.0.12:7000 10.0.0.12:7001 --cluster-replicas 1检查:
redis-cli --cluster check 10.0.0.10:7000
redis-cli -c -h 10.0.0.10 -p 7000 CLUSTER INFO
redis-cli -c -h 10.0.0.10 -p 7000 CLUSTER NODES
redis-cli -c -h 10.0.0.10 -p 7000 CLUSTER SLOTS32.3 扩容
redis-cli --cluster add-node 10.0.0.13:7000 10.0.0.10:7000
redis-cli --cluster add-node 10.0.0.13:7001 10.0.0.10:7000 --cluster-slave --cluster-master-id <master-id>
redis-cli --cluster reshard 10.0.0.10:7000add-node 只加入节点,reshard 才迁移槽位。迁移期间观察延迟、内存、网络、复制和大 Key。
32.4 缩容
redis-cli --cluster reshard 10.0.0.10:7000
redis-cli --cluster del-node 10.0.0.10:7000 <node-id>必须先迁移槽位,确认节点没有槽位后才能删除。
33. 性能调优顺序
不要一开始就修改几十个配置。推荐顺序:
性能调优先建立 QPS、P50、P99、命令耗时、命中率、内存和复制延迟基线;再定位慢命令、大 Key、网络往返、连接池和序列化问题;随后优化命令和数据模型;最后才调整内存、持久化、线程和连接相关配置,并通过同等流量回归验证收益。
常用命令:
SLOWLOG GET 50
LATENCY LATEST
LATENCY DOCTOR
INFO commandstats
INFO memory
INFO persistence
INFO replication压测:
redis-benchmark -h 127.0.0.1 -p 6379 -n 100000 -c 50 -t get,set压测必须使用接近生产的 Key、Value、命令比例、Pipeline、持久化和复制配置。只测 GET/SET 不能代表真实系统。
34. Redis 6 及以后能力
IO 线程:
io-threads 4
io-threads-do-reads yesIO 线程主要帮助网络读写,不会把复杂 Lua、Big Key 和 KEYS 变成并行操作。
Tracking:
CLIENT TRACKING on BCAST PREFIX prod: INVALIDATE-TRACKING <connection-id>客户端缓存要处理失效消息、断线重连、多进程差异和本地容量。
ACL:
ACL SETUSER app on >password `prod:* +@read +@write -CONFIG -FLUSHALL -FLUSHDBACL 配置要在主从、Sentinel 和 Cluster 节点之间保持一致。
35. Java 和 Spring 的接入
35.1 Spring 单机配置
spring:
data:
redis:
host: 127.0.0.1
port: 6379
timeout: 2s
connect-timeout: 1s
lettuce:
pool:
max-active: 32
max-idle: 16
min-idle: 4
max-wait: 500ms35.2 Sentinel 配置
spring:
data:
redis:
sentinel:
master: mymaster
nodes:
- 10.0.0.10:26379
- 10.0.0.11:26379
- 10.0.0.12:2637935.3 Cluster 配置
spring:
data:
redis:
cluster:
nodes:
- 10.0.0.10:7000
- 10.0.0.11:7000
- 10.0.0.12:7000
max-redirects: 5客户端必须处理:
具体包括:连接池;读写超时;拓扑刷新;MOVED 和 ASK;Sentinel 主节点变化;写命令重试;序列化兼容;Lua 和 Pipeline 返回值;业务幂等。
36. 发布前的操作验证
在接入业务前,至少执行:
发布前先验证连接、认证和 ACL;再验证 String、Hash、List、Set、ZSet、Stream、TTL、Pipeline 和 Lua;随后验证持久化文件、重启恢复、主从复制、Sentinel 或 Cluster 切换;最后进行并发压力、超时重试、序列化兼容和故障降级测试,并准备回滚路径。
每一步都要记录命令、输出、目标值和异常处理,不能只说“已经测试”。
四、问题处理
1. 统一流程
现象 -> 根因假设 -> 命令定位 -> 处理动作 -> 验证结果
通用采集:
INFO server INFO clients INFO memory INFO stats INFO persistence INFO replication INFO keyspace SLOWLOG GET 50 LATENCY LATEST
2. 详细故障闭环
2.1 连接被拒绝
现象:
具体包括:客户端提示 Connection refused;Redis CLI 连接失败;systemd 显示服务启动失败;应用连接池快速耗尽。
根因:
具体包括:Redis 进程没有运行;Redis 监听了错误地址或端口;配置文件路径错误;数据目录权限错误导致启动后退出;防火墙或安全组拒绝;应用使用了旧地址。
定位:
systemctl status redis-6379
ss -lntp | grep 6379
ps -ef | grep redis-server
journalctl -u redis-6379 -n 100 --no-pager
redis-cli -h 127.0.0.1 -p 6379 PING解决:
处理启动失败时先确认服务状态和进程退出原因;再检查配置文件语法、数据目录和日志目录权限;随后确认端口未被占用、bind 地址可达、磁盘空间充足;修正配置后以前台方式启动观察日志;最后通过本机 PING、远程 TCP、认证和应用连接池逐层验证。
redis-server /etc/redis/redis-6379.conf --test-memory 2
systemctl restart redis-6379
systemctl status redis-6379 --no-pager
redis-cli -p 6379 PING前台启动仍失败时直接查看错误输出;服务恢复后再检查端口、认证和连接池,不要只依据 systemd 的 active 状态判断应用已经可用。
验证:
具体包括:Redis 进程持续运行;本机 PING 返回 PONG;应用主机 TCP 连接成功;客户端认证成功;连接池能正常获取和归还连接。
2.2 远程连接被拒绝
远程连接处理先执行以下命令,分别确认 Redis 监听地址、保护模式、TCP 端口和进程监听状态:
redis-cli -h <redis-ip> -p 6379 CONFIG GET bind
redis-cli -h <redis-ip> -p 6379 CONFIG GET protected-mode
nc -vz <redis-ip> 6379
ss -lntp | grep 6379确认 bind、操作系统防火墙和云安全组后,只放行应用来源地址,不能为了临时连通把 Redis 暴露到公网。
现象:Redis 所在机器可以连接,应用服务器连接失败。
根因:
具体包括:bind 只配置了 127.0.0.1;protected-mode 拦截;云安全组没有放行;操作系统防火墙没有放行;应用连接了错误 IP;容器端口没有映射。
定位:
CONFIG GET bind
CONFIG GET protected-mode
INFO serverss -lntp | grep 6379
nc -vz 10.0.0.10 6379解决:
具体包括:绑定明确的内网 IP;使用 ACL 或密码认证;配置最小来源网段;检查容器宿主机和容器网络;检查 Sentinel 或 Cluster 宣告地址;不使用 protected-mode no 作为长期方案。
验证:允许网段连接成功,不允许网段仍然无法连接。
2.3 认证失败
现象:返回 NOAUTH、WRONGPASS 或 NOPERM。
根因:
具体包括:客户端没有执行 AUTH;用户名错误;密码错误;Sentinel 使用的认证信息错误;Cluster 节点 ACL 不一致;ACL 用户没有目标 Key 权限。
定位:
ACL LIST
ACL GETUSER app
ACL LOG 20解决:
认证故障处理时先确认客户端使用了正确的用户名、密码和认证方式;再检查 ACL 用户是否启用、命令权限和 Key 访问范围是否符合预期;核对 Sentinel、Cluster 和连接池中的认证配置;完成密码轮换后验证允许的 GET、SET 和业务命令成功,禁止的管理命令确实被拒绝。
ACL GETUSER app
ACL LOG 20
AUTH app <password>
GET prod:user:1001
CONFIG GET dir允许的业务读取应成功,禁止的管理命令应返回权限错误。密码轮换要先创建新凭证并灰度客户端,再删除旧凭证,不能先删除唯一可用账号。
验证:
AUTH app password
GET prod:user:1001
CONFIG GET dir允许的 GET 成功,禁止的 CONFIG 被拒绝,说明权限边界生效。
2.4 连接数耗尽
处理前先确认连接是否泄漏、空闲连接是否过多,以及服务端上限是否已经触发:
INFO clients
CLIENT LIST
CONFIG GET maxclients确认是异常连接后才使用 CLIENT KILL 清理;长期方案应修复连接池归还、空闲检测、获取超时和最大连接数配置,不能只把 maxclients 调大。
现象:
具体包括:max number of clients reached;应用连接池获取超时;connected_clients 持续上升;Redis 业务请求并没有增加,但连接数增加。
根因:
具体包括:应用连接泄漏;每次请求创建新连接;连接池最大值过大;阻塞命令占用连接;文件描述符不足;发布订阅连接没有关闭。
定位:
INFO clients
CONFIG GET maxclients
CLIENT LIST
INFO statslsof -p <redis-pid> | wc -l
ulimit -n解决:
具体包括:修复连接归还;使用稳定连接池;为连接池设置获取超时;检查 blocked_clients;限制每个应用实例连接数;预留运维连接;调整系统文件描述符。
验证:
具体包括:connected_clients 回到稳定区间;rejected_connections 不再增长;应用连接池等待时间下降;Redis 管理连接仍然可用。
2.5 所有命令延迟升高
现象:GET、SET、HGET 等简单命令也同时变慢。
根因:
具体包括:主线程被慢命令阻塞;RDB fork 或 AOF rewrite;内存不足或系统发生 swap;CPU 被其他进程抢占;磁盘或网络拥塞;客户端排队和重试形成二次压力。
定位:
SLOWLOG GET 100
LATENCY LATEST
LATENCY DOCTOR
INFO commandstats
INFO persistence
INFO memory
INFO clientsvmstat 1 5
iostat -x 1 5
top -H -p <redis-pid>解决:
慢命令处理先从 SLOWLOG、INFO commandstats 和应用链路确认具体命令;再检查命令涉及的数据量、大 Key 和执行频率;用 SCAN、分页、批量拆分、异步释放或更合适的数据结构替换阻塞操作;在低风险窗口发布后持续观察主线程延迟、P99 和应用超时。
SLOWLOG GET 50
LATENCY LATEST
LATENCY DOCTOR
INFO commandstats
INFO clients处理后重复相同负载,确认慢日志数量、blocked_clients、P99 和应用超时一起下降;只看到 Redis 慢日志下降而数据库回源、连接池等待继续上升,说明问题没有真正解决。
验证:慢日志减少,主线程延迟恢复,P99 和应用超时下降。
2.6 KEYS 阻塞
把全量匹配改成游标扫描和分批处理:
SCAN 0 MATCH prod:user:* COUNT 1000
SCAN <next-cursor> MATCH prod:user:* COUNT 1000
UNLINK prod:user:1001迁移脚本要持续使用返回的游标,直到游标回到 0;生产环境禁止用 KEYS 代替 SCAN,也不能一次性删除全部匹配结果。
现象:执行 KEYS 后全实例延迟尖刺。
根因:KEYS 需要扫描当前数据库,命令执行期间主线程无法处理其他请求。
定位:
SLOWLOG GET 50
INFO commandstats
CLIENT LIST解决:
redis-cli --scan --pattern 'prod:user:*' --count 1000使用 SCAN 分批遍历,处理结果时自行去重,不能把 SCAN 当严格快照。
验证:
具体包括:生产代码不再使用 KEYS;SCAN 的 COUNT 和批次受控;遍历期间 P99 不出现异常尖刺;删除操作使用 UNLINK 或分批删除。
2.7 大 Hash 全量读取
先看 Hash 大小,再按游标读取或按字段读取:
HLEN user:profile:all
HSCAN user:profile:all 0 COUNT 100
HGET user:profile:all user:1001把 HGETALL 改为 HSCAN、HGET 或分页读取;如果 Hash 长期增长,应按租户、时间或对象拆分 Key。
现象:HGETALL 后 Redis 延迟上升,应用响应体变大。
根因:Hash field 数量过大,HGETALL 一次返回全部内容。
定位:
HLEN prod:user:1001:profile
MEMORY USAGE prod:user:1001:profile
SLOWLOG GET 50解决:
具体包括:改为 HSCAN;按业务域拆分 Hash;只读取需要的 field;接口限制返回字段和数量;对历史字段做归档。
验证:单次响应字节数受控,HLEN 在设计上限内,慢日志消失。
2.8 大 Key 删除
删除前先确认类型、占用内存和业务影响,生产环境优先使用异步释放:
MEMORY USAGE cache:large:key
TYPE cache:large:key
UNLINK cache:large:key
EXISTS cache:large:key删除后继续观察 RSS、后台释放对象数量和延迟,不能把 UNLINK 当成没有资源成本。
现象:DEL 或 Key 过期时出现延迟尖刺。
根因:同步释放大对象,占用主线程。
定位:
TYPE key:name
MEMORY USAGE key:name
HLEN key:name
LLEN key:name
SCARD key:name
ZCARD key:name解决:
具体包括:使用 UNLINK;Hash、Set、ZSet 分批删除成员;List 按范围分批处理;从模型上限制单 Key 元素数量;对历史数据按日期拆分。
验证:删除不再明显阻塞主线程,后台释放没有长期堆积,内存最终下降。
2.9 内存满和 OOM
现象:
具体包括:OOM command not allowed;写命令失败;evicted_keys 快速增加;系统开始使用 swap;Redis 进程被 OOM Killer 杀死。
定位:
INFO memory
INFO stats
CONFIG GET maxmemory
CONFIG GET maxmemory-policy
MEMORY DOCTORfree -h
dmesg | grep -i oom
df -h处理:
内存问题处理先区分 used_memory、used_memory_rss、碎片率和客户端缓冲区;再定位大 Key、增长 Key、复制 backlog、AOF 重写和 fork 余量;确认 maxmemory 与淘汰策略符合业务;通过 TTL、拆分对象、删除无效数据或扩容释放压力;最后验证淘汰、OOM、命中率、回源和持久化是否恢复。
INFO memory
MEMORY STATS
CONFIG GET maxmemory
CONFIG GET maxmemory-policy
MEMORY USAGE <key>确认是可重建缓存后,才可以调整淘汰策略;订单、库存和消息状态不能通过淘汰来解决容量问题,应先拆分数据、扩容或迁移事实数据。
验证:used_memory、used_memory_rss、淘汰、OOM、命中率和数据库回源同时恢复。
2.10 内存碎片率高
先区分分配器碎片、对象持续增长和持久化造成的额外 RSS:
INFO memory
MEMORY STATS
CONFIG GET activedefrag
CONFIG SET activedefrag yes
MEMORY PURGE主动碎片整理会消耗 CPU,必须在低风险窗口观察延迟和吞吐;如果对象数量持续增长,应先处理容量和数据模型。
现象:used_memory 不高,但 used_memory_rss 很高。
根因:
具体包括:不同大小对象频繁创建和删除;内存分配器碎片;大对象删除后 RSS 没有马上归还;业务 Key 大小变化过于剧烈。
定位:
INFO memory
MEMORY STATS解决:
具体包括:减少大对象频繁改写;统一对象大小和拆分策略;分批删除;在有副本时滚动重启;必要时迁移数据重建实例;内存紧张时避免同时执行重写。
验证:RSS 与 used_memory 差距收敛,系统没有出现新的 OOM 和延迟峰值。
2.11 缓存穿透
对确认不存在的对象写入短 TTL 空值,并配合应用参数校验和限流:
SET cache:product:missing:null 1 EX 60 NX
GET cache:product:missing:null
TTL cache:product:missing:null空值缓存不能替代权限校验,也不能把长期不存在的数据永久写入 Redis。
现象:大量不存在的 ID 请求持续访问数据库。
根因:
具体包括:非法参数;恶意随机 ID;缓存没有保存空结果;布隆过滤器缺失;接口没有限流。
定位:
INFO stats
INFO commandstats结合应用日志统计不存在 ID、来源 IP、设备和请求频率。
解决:
具体包括:参数格式和范围校验;布隆过滤器;空对象短 TTL;请求合并;IP、账号和设备限流;网关拦截明显攻击流量。
验证:不存在 ID 的数据库查询下降,合法数据命中率不受影响,空对象按预期过期。
2.12 缓存击穿
热点 Key 失效时只允许一个请求回源,其余请求等待后重读。锁释放必须校验 owner token:
local owner = redis.call('GET', KEYS[1])
if owner == ARGV[1] then
return redis.call('DEL', KEYS[1])
end
return 0执行脚本并检查结果:
SET lock:cache:product:sku-1001 <owner-token> NX EX 5
redis-cli --eval unlock-cache.lua lock:cache:product:sku-1001 , <owner-token>
TTL lock:cache:product:sku-1001返回 1 表示当前持有者释放成功,返回 0 表示 token 不匹配或锁已经变化,不能继续删除。
现象:热点 Key 过期瞬间数据库 QPS 暴涨。
根因:大量请求同时发现缓存未命中并回源重建。
定位:
TTL hot:key
OBJECT FREQ hot:key
SLOWLOG GET 20结合应用日志统计同一 Key 的并发回源数。
解决:
具体包括:互斥锁;逻辑过期;提前刷新;热点 Key 异步更新;返回旧值或降级值;限制重建任务执行时间。
验证:同一热点 Key 同时只有一个重建任务,数据库 QPS 恢复,等待线程不会无限增加。
2.13 缓存雪崩
新写入的缓存使用基础 TTL 加随机值,并观察过期和回源指标:
SET cache:product:sku-1001 value EX 2100
SET cache:product:sku-1002 value EX 2473
INFO stats
INFO commandstats处理结果要同时看 expired_keys、数据库回源 QPS、应用 P99 和限流拒绝数,不能只看 Redis 命中率。
现象:大量 Key 同时失效,或者 Redis 整体不可用后数据库被打满。
根因:
具体包括:统一 TTL;批量任务同时写入相同过期时间;Redis 节点故障;应用没有本地缓存和降级;数据库连接池没有保护。
定位:
INFO keyspace
INFO stats
INFO replication
SENTINEL MASTER mymaster
CLUSTER INFO解决:
具体包括:TTL 增加随机值;多级缓存;分批预热;限流、熔断和降级;数据库连接池设置上限;Redis 恢复后限制回填并发;核心接口优先,非核心接口降级。
验证:数据库连接池不耗尽,回源 QPS 在保护线内,缓存恢复后逐步升温。
2.14 Redis 和数据库不一致
以事实库为准删除旧缓存,再通过版本 Key 或消息补偿写入新值:
GET prod:order:1001:status:v1
TTL prod:order:1001:status:v1
DEL prod:order:1001:status:v1
SET prod:order:1001:status:v2 paid EX 300删除失败应进入重试队列或对账任务,不能在线上手工反复 SET 一个未经数据库确认的值。
现象:数据库已经更新,接口仍返回旧缓存。
根因:
具体包括:先删缓存再更新数据库;删除缓存失败;并发读请求回填旧值;消息通知丢失;没有版本号和校准任务。
定位:
GET cache:key
TTL cache:key
MONITORMONITOR 只适合短时间低流量诊断,不能长期打开。
解决:
锁不释放时先确认锁的 owner、TTL、持有时间和续期状态;禁止直接 DEL 他人锁,必须通过校验 token 的 Lua 脚本释放;为加锁设置最大 TTL,并处理持有者宕机、续期失败和业务超时;对已超时任务执行幂等校验和补偿;最后用并发和重启场景验证锁不会误删、死锁或长期占用。
GET lock:order:1001
TTL lock:order:1001
EVAL "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 1 lock:order:1001 <owner-token>释放结果为 1 才表示当前持有者释放成功;返回 0 表示 token 不匹配或锁已经变化,不能继续删除。锁修复后要用客户端宕机、续期失败和任务超时场景验证幂等补偿。
验证:模拟并发更新、删除失败和应用重启,确认缓存最终收敛。
2.15 分布式锁不释放
先确认锁的持有者和剩余时间,再使用带 token 校验的 Lua 脚本释放:
GET lock:order:1001
TTL lock:order:1001如果没有 owner token,不要直接 DEL;应等待 TTL 到期或通过业务补偿确认任务已经停止。修复后要用客户端宕机、续期失败和任务超时场景验证不会误删、死锁或长期占用。
现象:业务长期获取不到锁。
根因:
具体包括:加锁没有 TTL;持有者宕机;续期线程失败;锁名设计过于粗;业务执行时间超过 TTL。
定位:
GET lock:order:1001
PTTL lock:order:1001
OBJECT IDLETIME lock:order:1001解决:
具体包括:SET 使用 NX 和 PX;value 使用唯一 token;释放使用比较 token 的 Lua;长任务使用受控续期;设置最大持有时间;业务操作保持幂等。
验证:持有者异常退出后锁能自动过期,旧持有者不能删除新持有者的锁。
2.16 Redis 事务部分成功
先用 TYPE 发现类型错误,再用 WATCH 或 Lua 约束并发修改:
TYPE tx:key
WATCH tx:key
MULTI
SET tx:key value
INCR tx:key
EXECEXEC 返回后必须逐项检查结果;部分成功时执行幂等补偿,不能假设 Redis 会自动回滚。
现象:事务前面的命令成功,后面的命令运行时报错,数据出现部分更新。
根因:Redis 事务不提供通用回滚。
定位:
MULTI
SET tx:key value
INCR tx:key
EXEC解决:
具体包括:执行前检查类型;用 Lua 合并判断和修改;对部分成功设计补偿;跨数据库使用消息和幂等;记录每条命令的返回值。
验证:模拟运行时错误,确认补偿后数据恢复,不能只看 EXEC 是否返回。
2.17 RDB fork 失败
先确认 fork 所需的内存、磁盘和内核策略:
redis-cli -p 6379 INFO memory
redis-cli -p 6379 INFO persistence
free -h
df -h
sysctl vm.overcommit_memory
dmesg | grep -i oom释放资源或扩容后再执行 BGSAVE;不要在内存不足时反复触发快照,也不能只修改 overcommit 而忽略写时复制带来的真实内存需求。
现象:
具体包括:BGSAVE 失败;延迟出现尖刺;内存突然增长;日志出现 fork、磁盘或权限错误。
定位:
INFO persistence
INFO memory
LASTSAVE
CONFIG GET dir
CONFIG GET savedf -h
free -m
vmstat 1 5解决:
具体包括:释放磁盘空间;检查数据目录权限;预留 fork 和 COW 内存;降低高峰重写;隔离 MySQL、MQ 和 Redis;纯缓存根据数据角色评估关闭 RDB。
验证:
rdb_bgsave_in_progress = 0
rdb_last_bgsave_status = ok并确认延迟和内存峰值稳定。
2.18 AOF 过大和重写阻塞
先检查重写状态、磁盘和重写阈值,再在低风险窗口手工触发:
INFO persistence
INFO memory
BGREWRITEAOF
CONFIG GET auto-aof-rewrite-percentage
CONFIG GET auto-aof-rewrite-min-size重写期间观察 aof_rewrite_in_progress、aof_pending_bio_fsync、used_memory_rss 和磁盘空间,发现资源逼近边界时暂停扩流。
现象:AOF 文件持续增长,磁盘告警,BGREWRITEAOF 期间 CPU、内存和 IO 上升。
根因:
具体包括:重写阈值过大;写入量高;fork 和 COW 压力;磁盘吞吐不足;重写反复失败;磁盘空间不足。
定位:
INFO persistence
CONFIG GET auto-aof-rewrite-percentage
CONFIG GET auto-aof-rewrite-min-size解决:
具体包括:检查磁盘空间;调整重写阈值;使用混合持久化;避免业务高峰手工重写;拆分高写入实例;监控重写时间和业务延迟。
验证:重写成功,AOF 大小回落,重写期间 P99 在可接受范围。
2.19 RDB 或 AOF 恢复失败
修复前先复制原文件并在隔离目录校验:
redis-check-rdb /backup/redis/dump.rdb
redis-check-aof /backup/redis/appendonly.aof
journalctl -u redis-6379 -n 200 --no-pager
ls -l /var/lib/redis/6379redis-check-aof --fix 可能丢弃文件尾部不完整命令;修复结果必须在隔离实例启动并抽查关键数据,不能直接覆盖生产文件。
现象:Redis 启动报文件损坏、AOF 截断或版本错误。
根因:
具体包括:文件复制不完整;磁盘写入中断;文件权限错误;版本和模块不兼容;备份时复制了正在变化的文件;磁盘损坏。
定位:
redis-check-rdb /backup/dump.rdb
redis-check-aof /backup/appendonly.aof
ls -lh /var/lib/redis解决:
具体包括:先复制原文件;优先从已校验的异机备份恢复;在隔离实例验证;确认 Redis 版本和模块;AOF 修复前保存原始文件;记录尾部命令可能丢失的影响。
验证:检查关键 Key、类型、TTL、数量、序列化和业务关系,再切换流量。
2.20 主从反复全量同步
先比较复制断线时间与 backlog 覆盖窗口:
INFO replication
CONFIG GET repl-backlog-size
CONFIG GET repl-timeout
CLIENT LIST必要时扩大 backlog、降低副本同时重连数量,并处理副本频繁重启、磁盘慢和大 Key 加载问题。
现象:日志反复出现 full resync,副本延迟变大。
根因:
具体包括:网络断线超过 backlog 覆盖范围;backlog 太小;主节点重启;副本磁盘慢;大 Key 或 RDB 传输压力;多副本同时同步。
定位:
INFO replication
ROLE
INFO persistence重点观察 master_repl_offset、slave_repl_offset、master_link_status 和 master_sync_in_progress。
解决:
具体包括:按写入速率扩大 backlog;修复网络和磁盘;错峰增加副本;拆分大 Key;避免同时重启多个副本;观察全量同步期间主节点延迟。
验证:短暂断网恢复后能 partial resync,复制 offset 差距稳定。
2.21 主从复制延迟
连续采集主副本 offset、同步状态和慢命令:
INFO replication
ROLE
INFO persistence
SLOWLOG GET 50根据 offset 差距判断是网络、磁盘、CPU、大 Key 加载还是全量同步造成,再针对瓶颈降低副本压力。
现象:从节点能连接,但读取到旧数据。
根因:
具体包括:主节点写入速度超过副本处理速度;网络带宽不足;副本磁盘慢;副本 CPU 不足;副本正在加载 RDB;大 Key 复制。
定位:
INFO replication
ROLE处理:
具体包括:强一致读取走主节点;扩大副本资源;修复网络和磁盘;拆分大 Key;不把副本 lag 当成瞬时抖动忽略;业务读取时增加版本或时间要求。
验证:复制 lag 稳定,主从 offset 差距不再持续扩大,关键读满足一致性要求。
2.22 Sentinel 不切换
先确认 quorum、Sentinel 互通、主节点可达性和副本状态:
SENTINEL CKQUORUM mymaster
SENTINEL MASTER mymaster
SENTINEL SENTINELS mymaster
SENTINEL REPLICAS mymaster
SENTINEL GET-MASTER-ADDR-BY-NAME mymaster确认条件后再处理 down-after-milliseconds、认证和宣告地址,不能直接手工改主节点绕过 Sentinel 状态机。
现象:主节点不可用,但 Sentinel 没有完成故障转移。
根因:
具体包括:Sentinel 数量不足;quorum 不满足;Sentinel 之间网络不通;Redis 认证错误;副本不可用或过旧;Sentinel 运行在同一故障域;客户端地址写死。
定位:
SENTINEL MASTER mymaster
SENTINEL SENTINELS mymaster
SENTINEL REPLICAS mymaster
SENTINEL CKQUORUM mymaster解决:
具体包括:至少三个 Sentinel;分布到不同故障域;放行 Sentinel 和 Redis 端口;统一认证配置;检查副本复制状态;客户端使用 Sentinel 感知模式。
验证:演练停止主节点,观察 SDOWN、ODOWN、领导者选举、新主提升、副本重配和客户端重连。
2.23 Sentinel 误切换或 TILT
核对 Sentinel 视角、进程状态、时钟和复制关系:
SENTINEL MASTER mymaster
SENTINEL SENTINELS mymaster
INFO server
INFO replication先恢复稳定故障域,再调整超时参数;不能只提高故障判断阈值来掩盖网络抖动、进程阻塞或时钟异常。
现象:主节点没有真正宕机,却频繁 failover,或 Sentinel 进入 TILT。
根因:
具体包括:网络抖动;CPU 长时间阻塞;虚拟机暂停;时钟异常;down-after-milliseconds 太小;Sentinel 自身资源不足。
定位:
SENTINEL MASTER mymaster
INFO server
INFO cpu结合系统日志、网络丢包、时钟同步和 CPU 抢占检查。
解决:修复基础设施,合理调整故障判定窗口,保证 Sentinel 独立资源和时间同步。不能只通过无限增大超时掩盖问题。
验证:网络抖动下误切换下降,真实故障仍能在目标时间内切换。
2.24 Cluster 返回 MOVED、ASK 和 CROSSSLOT
用 Cluster 客户端和槽位命令区分三类错误:
redis-cli -c -h <node-ip> -p 7000 GET prod:user:1001
redis-cli -h <node-ip> -p 7000 CLUSTER KEYSLOT prod:user:1001
redis-cli -h <node-ip> -p 7000 CLUSTER KEYSLOT prod:order:1001MOVED 要刷新客户端槽位,ASK 只对当前迁移请求重定向;CROSSSLOT 通常需要重新设计 Key 或使用相同 Hash Tag。
现象:客户端收到 MOVED、ASK 或 CROSSSLOT。
根因:
具体包括:客户端没有启用 Cluster;客户端拓扑缓存过期;槽位正在迁移;多 Key 不在同一槽位;Hash Tag 设计错误。
定位:
CLUSTER INFO
CLUSTER NODES
CLUSTER SLOTS解决:
具体包括:使用 Cluster 客户端;配置多个初始节点;正确处理 MOVED 和 ASK;相关 Key 使用相同 Hash Tag;无法同槽的操作拆成单 Key;不在 Cluster 中设计跨槽事务。
验证:客户端能刷新拓扑,迁移期间请求可重试,多 Key 操作不再随机出现 CROSSSLOT。
2.25 Cluster 新节点没有槽位
先确认节点已经加入拓扑,再执行槽位迁移:
redis-cli --cluster check <node-ip>:7000
redis-cli -c -h <node-ip> -p 7000 CLUSTER INFO
redis-cli -c -h <node-ip> -p 7000 CLUSTER NODES
redis-cli --cluster reshard <existing-node-ip>:7000add-node 只加入节点,reshard 才会迁移槽位;迁移完成后确认 cluster_state:ok 和槽位完整覆盖。
现象:节点显示加入集群,但没有数据和流量。
根因:add-node 只加入节点,没有迁移槽位。
定位:
redis-cli --cluster check 10.0.0.10:7000
redis-cli -h 10.0.0.13 -p 7000 CLUSTER SLOTS解决:
redis-cli --cluster reshard 10.0.0.10:7000验证:16384 个槽位完整覆盖,新节点拥有预期槽位,读写流量重新分布。
2.26 Cluster bus 不通
同时检查客户端端口和 Cluster bus 端口:
ss -lntp | grep 7000
ss -lntp | grep 17000
nc -vz <peer-ip> 7000
nc -vz <peer-ip> 17000
redis-cli -h <node-ip> -p 7000 CLUSTER NODES核对 cluster-announce-ip、cluster-announce-port 和 cluster-announce-bus-port 是否是其他节点可以访问的地址。
现象:服务端口可连接,但集群状态为 fail,节点频繁 failover。
根因:
具体包括:Cluster bus 端口未放行;cluster-announce-ip 错误;cluster-announce-port 错误;容器地址无法被其他节点访问;跨网段路由不通。
定位:
nc -vz 10.0.0.11 7000
nc -vz 10.0.0.11 17000CLUSTER INFO
CLUSTER NODES解决:同时放行服务端口和 bus 端口,校正节点宣告地址,把主从放在不同故障域。
验证:所有节点互通,cluster_state 为 ok,故障演练只发生预期切换。
2.27 Cluster 迁移期间延迟升高
迁移期间持续观察槽位、内存、复制和延迟:
redis-cli --cluster check <node-ip>:7000
redis-cli -c -h <node-ip> -p 7000 INFO memory
redis-cli -c -h <node-ip> -p 7000 INFO replication
redis-cli -c -h <node-ip> -p 7000 LATENCY LATEST降低并发迁移量并避开业务高峰;发现 cluster_state 异常、复制 lag 扩大或错误率升高时暂停迁移。
现象:reshard 期间应用 P99 升高,节点 CPU、网络和内存上升。
根因:
具体包括:迁移了大 Key;迁移批次过大;目标节点内存不足;网络带宽被占满;客户端处理 ASK 方式不正确;同时进行复制或持久化。
定位:
CLUSTER INFO
CLUSTER NODES
INFO memory
INFO stats
INFO replication解决:
具体包括:拆分大 Key;限制迁移批次;错峰迁移;观察目标节点内存;控制同时迁移的槽位数量;迁移期间降低非必要后台任务。
验证:迁移完成、槽位完整、P99 恢复、节点内存和网络回到稳定范围。
2.28 Stream 消息积压
先区分消费者处理慢、消费者宕机和生产速度过高:
XINFO GROUPS task:events
XPENDING task:events workers
XAUTOCLAIM task:events workers worker-recovery 60000 0-0 COUNT 100
XTRIM task:events MAXLEN ~ 100000转移超时 Pending 前要确认业务幂等;不能直接删除 Stream 掩盖积压,失败消息必须保留补偿入口。
现象:Stream 长度、Pending 数量和最老消息等待时间持续增长。
根因:
具体包括:消费者停止;消费处理时间变长;ACK 没有在成功后执行;消费组数量不足;失败消息没有转移;Stream 没有长度上限。
定位:
XLEN orders:events
XINFO STREAM orders:events
XINFO GROUPS orders:events
XPENDING orders:events order-workers解决:
具体包括:检查消费者进程;检查业务处理耗时;使用 XAUTOCLAIM 转移超时 Pending;增加消费者;失败消息进入补偿流;使用 XTRIM 控制长度。
验证:Pending 下降,消费延迟恢复,重复消费幂等,Stream 内存不再无限增长。
2.29 Lua 脚本阻塞
先确定脚本是否仍在执行,再判断是否可以终止:
SLOWLOG GET 50
LATENCY LATEST
INFO commandstats
SCRIPT LIST
SCRIPT KILLSCRIPT KILL 只适合尚未执行写操作的脚本;已经写入数据的脚本不能安全中断。后续要限制 KEYS、ARGV、循环次数和返回字节数,并用固定规模数据重新压测。
现象:EVAL 或 EVALSHA 执行时全实例延迟升高。
根因:
具体包括:脚本扫描大集合;循环次数无上限;单次修改 Key 太多;返回结果太大;把复杂业务流程放入脚本。
定位:
SLOWLOG GET 50
INFO commandstats
SCRIPT EXISTS <sha1>解决:限制输入、循环和返回大小;把复杂计算移到应用或异步任务;脚本只做短小原子操作。
验证:脚本执行时间有明确上界,异常输入不会无限执行,P99 稳定。
2.30 版本升级后异常
现象:
具体包括:ACL 权限变化;Cluster 重连失败;序列化失败;AOF 恢复时间变长;配置项行为变化;监控指标名称变化。
根因:
具体包括:服务端和客户端版本不兼容;默认配置变化;模块版本不兼容;RDB/AOF 兼容性变化;连接拓扑行为变化。
定位:
INFO server
COMMAND INFO GET SET EVAL
CONFIG GET *解决:
版本升级验证应覆盖服务端与客户端兼容性、命令行为、ACL、序列化格式、RDB/AOF 加载、复制、故障切换、拓扑刷新和关键业务数据模型;先在隔离环境回放真实流量,再执行灰度升级;确认旧客户端和新客户端均能正常读写后,才扩大流量范围。
INFO server
COMMAND INFO GET SET EVAL
CONFIG GET *
INFO persistence
INFO replication升级前后保存这些输出并进行差异比较,再用备份文件和关键业务命令做回放。发现命令、ACL、序列化或拓扑行为变化时,先停止扩大流量并回滚客户端或服务端版本。
验证:覆盖命令、ACL、序列化、持久化恢复、故障切换、拓扑变化和关键业务模型。
