
做Redis运维或者面试准备持久化和集群这两块早晚要啃下来。Redis 5.0.4算是我在生产环境里用得比较久的一个版本既有5.0带来的Stream等新特性又不像后面6.x、7.x那样在配置和兼容性上折腾人。所以拿这个版本把持久化和集群完整讲一遍再合适不过。Redis持久化解决的是“进程重启后数据还在不在”的问题集群解决的是“单机内存不够、单点故障怎么办”的问题。这两件事经常被分开讨论但实际生产里必须一起设计主从节点各自该怎么落盘、集群脑裂时会不会丢数据、AOF重写会不会拖垮主节点、槽迁移期间key阻塞多久……这些坑我基本都踩过一遍。这篇文章就把我自己的实操过程、参数选择和排查经验完整梳理出来适合刚接触Redis的读者也适合已经能跑起单机Redis、但还不清楚主从和Cluster怎么玩的同学。1. 为什么5.0.4这个版本值得聊1.1 版本定位与选型背景Redis 5.0.x是整个5.x系列的稳定分支5.0.4属于其中比较成熟的小版本。我选择它有几个原因首先5.0的集群管理命令已经从ruby脚本迁移到了redis-cli内置命令不再需要额外安装redis-trib.rb这对部署环境干净程度要求低了不少其次5.0.4对之前5.0.0到5.0.3的一些内存和网络问题做了修复稳定性和性能在当时的业务压力下都能扛住最后很多公司到现在仍然有大量5.x存量环境学会这一套之后不管面对旧集群还是新搭建都能无缝上手。这个版本跟6.x、7.x最大的区别在于它的许多运维习惯和配置项仍是“经典时代”的风格比如replicaof还以slaveof兼容、aof-use-rdb-preamble默认开启但可以显式关闭。用5.0.4作为学习样板你能把RDB、AOF、主从复制、Cluster这套底层机制看得更清楚而不是被一堆新特性分散注意力。实际选型的时候新项目我建议直接用7.x但如果你手头正好是5.x集群或者需要在老环境上做改造这篇文章里的思路完全适用。1.2 持久化和集群的关系持久化和集群不是两条平行线。最典型的例子是主从复制中的全量同步从节点第一次连接主节点时主节点会做一次bgsave生成RDB快照发给从节点这个过程如果磁盘压力大、RDB文件大就可能拖慢主节点。而集群模式下每个主节点都有自己独立的持久化配置一个节点宕机后从节点晋升为主节点它本地的RDB/AOF文件是否完整直接决定了提升后数据是否可用。另一个角度是备份策略。单机Redis你只需要考虑“今天全量备份每天增量备份”但集群里至少6个节点如果每个节点都做同样的全量备份磁盘和带宽开销会翻好几倍。我后来习惯只对主节点做定时RDB备份从节点关闭RDB、只开AOF再用脚本从从节点拉取备份文件既保证主节点性能又保证备份链路不依赖主节点IO。这个思路在后面的章节会展开讲。2. RDB持久化从原理到实操2.1 RDB触发机制与配置RDB本质上是某个时间点的内存快照主进程通过fork出一个子进程由子进程把内存里的全量数据写入临时RDB文件写完再原子替换旧文件。这个过程中主进程依然可以处理请求靠的是fork子进程后操作系统的写时复制机制。哪怕你对RDB原理只记一句话也必须记住这个关键词fork。它决定了RDB的最大隐患——fork瞬间如果内存太大或者系统内存不足主进程会卡住。RDB触发方式分自动和手动。自动触发靠redis.conf里的save配置save 900 1 save 300 10 save 60 10000意思分别是900秒内至少有1个key变化、300秒内至少有10个key变化、60秒内至少有10000个key变化时触发一次bgsave。这三个条件只要满足任意一个就会执行。实际配置时别照搬默认值要根据写入量调整。比如我有个业务每小时只有5000次写入那我更倾向于保留save 900 1和save 300 10把save 60 10000去掉避免夜间高峰频繁触发不必要的快照。手动触发用bgsave命令也可以直接用save命令但save是阻塞式生成RDB生产环境基本不用。我之前见过有人图省事在脚本里写save结果每秒几万QPS的服务直接卡了几秒钟这是非常危险的。手动bgsave的典型场景是准备上线发版前、准备扩缩容前、或者需要备份副本时。2.2 RDB恢复和注意事项Redis启动时如果配置了持久化目录它会自动检查是否存在dump.rdb文件并加载。但有个优先级问题如果AOF也开启了Redis会优先加载AOF文件因为AOF里的数据通常更新、更完整。这一点经常导致误解有人以为关了AOF就能从RDB恢复结果启动后发现数据还是老样子其实是因为appendonly yes时Redis根本不会读取dump.rdb。先说几个RDB相关的调优参数。rdbcompression yes默认开启会使用LZF压缩建议保持开启压缩率大致能到60%到70%代价是CPU多消耗一点rdbchecksum yes会对RDB文件做CRC64校验加载时校验文件完整性如果你的RDB文件特别大、加载慢可以临时关掉但生产不建议。stop-writes-on-bgsave-error yes这个参数更关键bgsave失败时Redis会拒绝写入防止主从数据差距越来越大。有一次线上遇到磁盘写满Redis直接报错不让写一开始我还以为是业务问题后来查日志才发现是RDB落盘失败导致的写入保护。所以要特别注意它保护的是数据一致性不是“让你能继续写”。再说一个实操经验。检查RDB文件是否损坏可以用Redis自带的工具redis-check-rdb /data/redis/dump.rdb它会输出文件中的key数量、过期时间、是否包含AOF头部等信息。如果RDB文件损坏但你又没有其他备份别急着放弃可以尝试用redis-check-rdb --fix恢复一部分数据但恢复出来的数据可能不完整只能作为最后手段。3. AOF持久化追加日志的细节3.1 AOF配置与刷盘策略AOF的核心思想是记录每一次写命令写到日志末尾重启时重新执行这些命令来恢复数据。它的最大优势是数据安全性比RDB高丢失窗口可以控制在一秒以内甚至零丢失。但代价是文件会比RDB大得多所以必须有rewrite机制。配置层面首先开启appendonly yes appendfilename appendonly.aof appendfsync everysecappendfsync有三个选项always表示每次写命令都调用fsync刷盘最安全但性能最差everysec表示每秒刷盘一次最多丢一秒数据生产最常用no表示交给操作系统刷盘性能最好但可能丢几秒甚至更多数据。我一般默认用everysec只有对账、流水这种核心场景才可能考虑always但需要先做压测评估性能损耗。AOF文件会不断增长必须通过rewrite机制压缩。rewrite原理不是读旧AOF去重而是fork子进程后把当前内存状态直接生成最小命令集写入临时文件期间新写入的命令会同时缓存在rewrite buffer里子进程写完后再合并。自动触发条件auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb表示AOF文件比上次rewrite后增长100%翻倍且超过64MB时触发重写。这两个参数不建议改得太激进频繁rewrite会消耗CPU和磁盘IO。我之前把percentage改成50结果高峰期AOF文件几乎每隔几十分钟就rewrite一次后来调回100之后才稳定。手动触发rewrite用bgrewriteaof。发布上线前或者准备对集群做备份时手动执行一次bgrewriteaof会让AOF文件更紧凑后续恢复也更快。3.2 混合持久化的取舍Redis 4.0开始引入混合持久化5.0.4默认开启。也就是当AOF rewrite发生时生成的AOF文件头部是一段RDB格式的二进制数据后面再跟上rewrite期间的新写入命令。这样重启恢复时先加载RDB格式部分再执行后面的AOF增量加载速度比纯AOF快很多文件体积也小很多。配置项是aof-use-rdb-preamble yes这个模式的问题在于如果你需要把AOF文件拿来做异平台导入或者用老版本的redis-check-aof工具去分析会遇到“文件是二进制格式”的困惑。有一回我把AOF文件传给别人排查问题对方直接打开一看是乱码以为文件坏了。其实只要文件头部是“REDIS”开头的RDB格式就说明开启了混合持久化不是损坏。如何取舍如果追求恢复速度和磁盘空间保持默认开启如果你需要频繁用文本方式审计AOF内容或者要兼容Redis 4.0之前的旧版本那就显式关闭它。我自己的标准是生产环境保持开启毕竟恢复速度对故障时长影响太大审计需求可以用keyspace notifications或其他方式替代。AOF还有一个常见问题AOF文件尾部如果写入不完整比如宕机时最后几条命令只写了一部分Redis启动时会报错并拒绝加载。这时候可以用redis-check-aof --fix自动修复它会截断尾部不完整的数据启动后最多丢失最后几笔写入。这个工具我建议每个运维都提前备好不要等故障了才想起来。4. Redis集群从主从到Cluster4.1 主从复制基础与常见坑在聊Cluster之前先得把主从复制讲清楚因为Cluster里的每个分片本质上就是一组主从节点。主从复制的核心流程是从节点发送psync命令主节点根据replid和offset决定做全量同步还是部分同步。全量同步指主节点生成RDB发给从节点并继续发送后续增量命令部分同步则是在网络断开后从节点回到之前的位置主节点从repl_backlog缓冲里补发数据。配置主从非常简单5.0.4里可以用slaveof也可以用新的replicaofreplicaof 192.168.1.10 6379这块常见的坑有几个。第一主节点开启持久化而从节点不开启会导致主节点重启后数据为空从节点同步时把从节点数据也清空。5.0.4默认没这么智能不会自动避免这种坑。第二全量同步对主节点压力极大因为要bgsave还要传输大文件如果多个从节点同时重连主节点可能连续做多次全量同步内存和带宽直接被打满这就是“复制风暴”。第三网络抖动导致从节点频繁断线重连如果repl_backlog太小每次都会退化成全量同步。所以我的建议是主从都开持久化至少从节点要开AOFrepl_backlog_size调大一些默认1MB太保守写频繁的业务至少调到64MB以上否则断线几十秒就可能触发全量同步。配置如下repl-backlog-size 64mb repl-backlog-ttl 3600 replica-read-only yes4.2 Cluster哈希槽与数据分片Redis Cluster把整个keyspace分成16384个槽位每个key通过CRC16(key) % 16384确定归属槽再根据槽的分布找到对应节点。为什么是16384而不是65536或更多因为集群节点之间通过Gossip协议交换信息心跳包要携带节点和槽位信息槽位越多消息体越大网络开销越高。16384在节点数量不超过1000时足够且消息紧凑这是作者根据工程经验权衡出来的设计面试经常问。槽有三种分配状态未分配、已分配、迁移中。客户端访问key时如果节点不是该key的归属节点会返回MOVED错误并携带正确节点地址如果槽正在迁移则返回ASK错误客户端需要先发送ASKING再执行命令。这也是为什么使用Cluster时客户端必须使用支持集群模式的Redis客户端比如Redis的Cluster客户端或Lettuce、Jedis的Cluster模式否则你会在生产环境看到一堆MOVED报错。Cluster有个非常实用的功能是哈希标签。用花括号{}把key的一部分括起来比如{user:1001}.profile和{user:1001}.cartCRC16只对{}内的内容计算这样这两个key就能保证落在同一个槽位避免跨节点事务无法执行的问题。这是我强烈建议团队统一遵守的规范否则以后做按用户维度的操作时经常会遇到MULTI不跨节点、Lua脚本跨节点报错这类麻烦。4.3 Gossip协议与故障转移Cluster节点之间用Gossip协议交互每个节点每秒都会向其他节点发送PING并随机携带一部分其他节点的状态信息。当某个节点超过cluster-node-timeout时间没有响应会被标记为疑似下线如果超过半数主节点都认为该节点疑似下线它就会被标记为下线并触发从节点的故障转移选举。选举机制类似Raft从节点发起投票主节点投票得票超过主节点总数一半的从节点晋升为新的主节点。这里有个容易被忽略的点cluster-node-timeout设置太短网络抖动会引发误判频繁故障转移反而降低可用性设置太长真宕机时恢复时间又太长。默认15秒我之前在跨机房部署时把心跳频率调高cluster-node-timeout设成20秒结果一次网络抖动仍触发了主从切换后来改成30秒才稳定。实际上5.0.4里还有一些细节参数如cluster-replica-validity-factor、cluster-migration-barrier会影响从节点能否参与选举和做自动迁移生产环境建议先保持默认等熟悉集群行为后再调整。Cluster还有一个让我印象深刻的坑cluster-require-full-coverage默认yes意思是所有16384个槽都覆盖正常时集群才提供服务。如果某个分片的所有主从节点都挂了哪怕你还有大量可用节点整个集群也会拒绝写入。这在多机房容灾场景下非常危险我后来按照推荐调整为no让集群在部分槽不可用时仍能对外提供服务同时通过监控告警及时人工介入。5. 集群搭建实操5.0.4版本5.1 环境准备和配置我以一台机器上起6个Redis实例为例模拟3主3从的集群。生产环境不建议单机多实例但用来学习原理足够。先编译安装Redis 5.0.4wget https://download.redis.io/releases/redis-5.0.4.tar.gz tar xzf redis-5.0.4.tar.gz cd redis-5.0.4 make -j4 make install然后准备6个配置文件端口从7000到7005。最小化配置如下port 7000 daemonize yes dir /data/redis/7000 pidfile /data/redis/7000/redis.pid logfile /data/redis/7000/redis.log cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 15000 appendonly yes appendfilename appendonly-7000.aof有几个细节值得注意。daemonize yes会后台运行便于用脚本管理dir目录必须存在且每个实例单独一个目录因为nodes.conf和AOF文件会以你配置的文件名生成目录混在一起容易覆盖。cluster-config-file虽然叫“配置文件”实际是集群节点状态自动保存的文件不要手动编辑。appendonly建议直接开启集群重启后能更快恢复数据避免启动后数据为空再触发全量同步。5.2 初始化集群和验证6个实例都启动后开始创建集群。5.0.4开始用redis-cli内置命令即可redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1它会自动把前三个节点设为主节点后三个设为对应的从节点然后分配16384个槽位。执行时会有交互确认输入yes即可。创建过程中会输出每个主节点分配到的槽范围和对应的从节点建议截图或记录下来后面扩缩容要用。创建完成后验证redis-cli -p 7000 cluster info redis-cli -p 7000 cluster nodescluster info里cluster_state:ok说明槽全部覆盖cluster_slots_assigned等于16384cluster nodes能看到每个节点的ID、角色、是否在线、从属于谁。我当时第一次搭集群时cluster_state一直显示fail查了半天发现是7004端口没监听redis-cli --cluster create时它已经启动但进程崩了导致槽分配异常。所以验证时一定要每个节点都确认在线别只看主节点。5.3 扩容缩容与槽迁移集群搭建不是终点扩容和缩容才是日常。扩容一个主节点redis-cli -p 7000 cluster meet 127.0.0.1:7006 redis-cli -p 7000 cluster addslots 0 1 2 ... 100但5.0.4有更简单的命令它会自动迁移槽redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 redis-cli --cluster reshard 127.0.0.1:7000 --cluster-from 000... --cluster-to 111... --cluster-slots 1000 --cluster-yesreshard的--cluster-from可以写多个节点ID表示从哪些节点移出槽--cluster-to写目标新节点ID--cluster-slots写要迁移多少个槽。迁移过程中涉及迁移的key在旧节点会进入migrating状态新节点进入importing状态。对于正在访问这些key的客户端可能会有短暂的延迟因为需要先让旧节点把key序列化传给新节点。我在迁移一个约500万key的分片时1秒大约迁移几百到几千个key大key会更慢整个过程持续了十几分钟期间业务有几个请求出现毫秒级延迟但整体可接受。缩容则要先把被下线节点上的槽全部迁走再执行redis-cli --cluster del-node 127.0.0.1:7006 nodeId注意如果节点上还有槽del-node会直接报错这是好事防止误操作。从节点下线不需要迁槽直接del-node即可。5.4 运维参数推荐经过几次实战我整理了一套适合5.0.4 Cluster的推荐配置直接在redis.conf里覆盖参数推荐值说明cluster-node-timeout15000-20000网络抖动容忍度cluster-require-full-coverageno避免单分片故障拖垮集群cluster-migration-barrier1允许主节点迁移从节点appendonlyyes主从都建议开启appendfsynceverysec性能与安全平衡auto-aof-rewrite-percentage100避免频繁rewriteauto-aof-rewrite-min-size128mb小实例可适当调低repl-backlog-size64mb降低全量同步概率maxmemory-policy按业务确定至少准备好淘汰策略这里面cluster-migration-barrier是很多新手没注意的。它表示一个主节点至少保留多少个健康从节点如果为0从节点不会自动迁移如果为1某个主节点失去所有从节点时集群会从其他有冗余从节点的分片迁移一个从节点过来。生产环境我推荐设1增加容错性。6. 常见问题与排查实录6.1 持久化相关的问题先整理一份排查速查表是我这些年遇到的高频问题现象可能原因解决思路Redis自动拒绝写入磁盘满或bgsave失败清理磁盘检查stop-writes-on-bgsave-error重启后数据是旧的AOF和RDB同时开启AOF文件不完整redis-check-aof --fix修复AOF重启恢复非常慢RDB文件过大或混合持久化关闭开启aof-use-rdb-preamble出现卡顿尖刺fork耗时太长查看info stats的latest_fork_usec考虑内存淘汰或限流AOF文件是乱码开启了混合持久化用redis-check-aof检查非损坏有一个很隐蔽的问题想多说几句当你在写脚本做备份时直接cp dump.rdb可能copy到一个正在写入的临时文件。正确做法是先执行bgsave等待bgsave完成后再把dump.rdb复制走或者使用redis-cli --rdb方式导出。我之前就是因为偷懒直接cp备份文件偶尔损坏恢复时白白浪费了几个小时。6.2 集群相关的问题集群日志里最常见的就是“Cluster state changed: ok”和“fail”来回切换。这种通常不是真的节点宕机而是网络抖动导致的误判。排查步骤我总结为三步先看各节点cluster nodes里其他节点是否处于fail或pfail状态再看系统日志有没有TCP重传、丢包最后查cluster-node-timeout是否设置得太小。如果是网络抖动适当增大超时时间同时降低cluster-require-full-coverageyes带来的连锁风险。另一个高频问题是客户端报MOVED/ASK错误。如果用的是普通单机客户端连接集群节点就会频繁遇到。解决方案是换用支持集群的客户端比如Lettuce、JedisCluster。还有一个坑节点的IP如果绑定了127.0.0.1客户端从外部访问时会收到MOVED到127.0.0.1的地址导致连接失败。此时需要在配置里设置cluster-announce-ip和cluster-announce-port或者在启动时绑定对外IP。故障转移触发慢也是常见问题。比如主节点宕机从节点升级用了超过30秒业务已经超时。这时要检查cluster-node-timeout是否过大还要看从节点是否有足够的投票权。一个我踩过的坑是节点都设置了requirepass但masterauth配置不一致导致从节点无法与主节点认证全量同步一直失败故障转移也一直失败。记得所有节点都要配置masterauth并且密码要一致。6.3 实际场景思考与踩坑笔记因为经常有人问我Redis分布式锁在集群里到底能不能用这里也给一个个人结论Redis Cluster模式下如果只是简单用SETNX加锁主从切换时会存在锁丢失的竞态因为主节点写入后还没同步给从节点主节点就挂了从节点晋升后锁就不存在了。所以要么用RedLock这类算法要么接受极小概率的锁失效并在业务侧做好幂等兜底。这个问题跟持久化也有关系如果主从都开启了AOF everysec至少能把丢失窗口控制在一秒内但不能完全消除。持久化备份的恢复演练也建议定期做。我之前遇到过集群正常运行但从节点一直没有成功同步原因竟然是从节点的磁盘空间不够导致AOF rewrite失败。由于从节点一直报错主节点却浑然不知集群看似健康实际已经处于单点风险中。后来我养成了一个习惯每周至少检查一次所有从节点的复制偏移量是否跟主节点一致同时做一次主节点bgsave 从节点bgrewriteaof确认备份链路没有断掉。序列化问题在集群场景下更容易暴露。比如业务用Jackson把对象序列化成字符串存在Redis里但key里混入了类名、包名或特殊符号不同节点上的CRC16分布没有规律一旦做reshard序列化格式不一致的对象数据可能被迁到不同节点客户端反序列化就报错。所以团队内部能统一用JSON/Protobuf就最好尽量避免Java原生的序列化方式。Redis Desktop Manager这类可视化工具在集群模式下能看到槽位和节点状态对排查分布不均有一定帮助但真正定位问题还是得靠命令行。最后再分享一个小技巧每次改完集群或持久化配置我都习惯用config rewrite命令把运行期配置持久化到配置文件然后检查cluster nodes里节点状态是否正常。别小看这个动作它让我避免过很多次“配置临时生效、重启又变回去”的尴尬。Redis 5.0.4虽然已经不那么年轻了但把它的持久化和集群机制摸透很多底层原理放到7.x、甚至其他分布式系统里依然通用。我建议你搭好一个3主3从的测试集群后故意kill掉一个主节点观察从节点多久能提升成功观察数据有没有丢。这套实验做完你对Redis的认知会比看十篇文章都深。