尧图精选

Redis主从复制与集群搭建实战:从单机到高可用架构

🕒 发布时间:2026/9/11 2:31:18 📁 来源:尧图网络
Redis主从复制与集群搭建从单机瓶颈到高可用架构的完整实战先问个直击灵魂的问题你的Redis还在单机裸奔吗如果是那当它宕机的那一刻你大概率是手忙脚乱地在重启、在翻日志、在群里喊“缓存挂了”。我2019年接手的一个项目就是这样Redis单点部署某天下午直接内存爆掉进程退出结果所有请求全打到MySQL上数据库CPU瞬间跑满整个服务瘫痪了将近半小时。那次事故之后我对Redis高可用的态度就变成了一句话没有主从复制的Redis根本不配叫生产环境。这篇文章不整虚的全程围绕主从复制和集群搭建这条主线讲清楚三件事主从复制到底在复制什么、背后是什么原理Sentinel怎么做故障转移Redis Cluster怎么把数据分散到多台机器上还能自动容错。内容兼顾原理和实操适合正在从单机走向分布式的后端开发、运维同学也适合那些概念都知道但没亲手搭过的Redis用户。1. 单机Redis的瓶颈在哪搞清楚主从复制和集群分别解决什么问题很多人一上来就开搞集群结果发现根本不知道自己为什么要搭。我建议先把问题想明白因为主从复制和集群是两套不同的解决方案解决的问题完全不一样。1.1 单机Redis的三个致命痛点数据丢失风险Redis默认开启RDB和AOF持久化但数据是写进磁盘的磁盘坏了呢机器被误销毁呢进程异常退出了呢重启之后数据可能只是恢复到上次持久化的点中间的数据照样丢。读性能瓶颈单台Redis的QPS大概在8万到10万左右根据数据规模和机器性能浮动如果你的业务读多写少比如商品详情缓存、用户会话信息读QPS一上来单机CPU就扛不住了。容量上限单台机器的内存是有限的比如你是云上ECS内存最大就256GB再大规模的数据缓存怎么办超卖场景下需要更大容量的缓存存储单机横向扩展不了。这三个痛点对应两个解决方案如果核心诉求是数据备份 读流量分摊本质上是横向扩展读能力让数据变多份那主从复制就够了。如果核心诉求是海量数据存储 自动分片 高可用数据总量超过了单机内存上限那必须上Redis Cluster。1.2 我见过的“滥用集群”典型场景有一个朋友的电商项目Redis里的数据其实很小一共才2GBQPS峰值也就2万左右。结果他花了一天时间按照网上的教程搞了个三主三从的Cluster最后发现不但没变快反而因为多了一次节点间的重定向跳转部分操作延迟反而上升了。原因是他在业务代码里大量使用了KEYS和MGET这类跨slot的多key操作在Cluster模式下直接报CROSSSLOT Keys in request dont hash to the same slot改代码改了一个礼拜。所以我给个判断标准Redis数据量 单机内存且主要痛点是高可用用主从 Sentinel。Redis数据量 单机内存或者单节点写并发高到CPU扛不住用Cluster。只是想要一个数据备份不在乎自动切换只做主从就够了哨兵可以后加体量小的人工切换也能接受。1.3 整体架构概念图谱先用大白话把几个核心概念捋一遍主从复制一台Master节点负责写多台Slave节点负责读数据实时从Master同步到Slave。Sentinel一个“监工”进程专门盯着Master和Slave如果Master挂了自动把某个Slave提升为新的Master。Cluster把整个数据集拆成16384个槽Hash Slot分布在多个Master节点上每个Master还可以挂Slave做备份任何一个主节点挂了它的从节点在感知到后会自动顶上。这三者的关系是递进的主从复制提供数据冗余和读扩展Sentinel在主从复制基础上提供自动故障转移Cluster在内部分片基础上自己实现了故障转移不依赖Sentinel。注意这里提前说清楚一个点Cluster模式下不需要额外部署Sentinel它内部有一套gossip协议负责节点状态管理。2. 主从复制底层机制和踩坑排查从“看不懂日志”到“一眼定位问题”主从复制不仅是配几条命令那么简单。理解它的底层机制你才能在生产环境出问题时不会像无头苍蝇一样乱试一通。2.1 全量复制与部分复制的完整演进过程我第一次做主从复制的时候在Master上敲了一行SLAVEOF看到Slave的日志里打印MASTER - REPLICA sync started觉得也就这么回事。直到有一次线上出现网络抖动从库立刻触发了一次全量同步几GB数据在两台机器之间直接复制了一遍导致主库CPU飙高。那时候我才意识到主从复制内部远比“实时同步”四个字复杂。主从复制的核心演进点有三个阶段早期版本2.8之前从库断线重连之后只能做全量重同步。不管断了一秒还是一分钟主库都要重新生成一份RDB快照发给从库网络开销极大。2.8版本引入PSYNC支持部分重同步。主库会维护一个复制积压缓冲区repl_backlog默认大小1MB只要从库断线期间的数据还在这个缓冲区里就能通过偏移量读取缺失部分只做增量同步。4.0版本引入PSYNC2解决了从库晋升主库之后其他从库需要全量同步的问题。通过记录复制IDreplid实现了跨主从切换的部分重同步。实际执行全量同步时流程是这样的从库发送PSYNC ? -1表示自己什么都不知道请求全量同步。主库执行BGSAVE生成RDB快照同时把后续的新写命令写入复制缓冲区。RDB生成完成后通过网络发给从库从库清空自己旧数据再加载RDB。主库把复制缓冲区里的增量命令继续发给从库最终达到两边数据一致的完成态。这里有个关键性能点全量同步期间主库的RDB快照生成会消耗磁盘IO和内存如果数据量大建议用子进程方式开启RDB的生成并且尽量在业务低峰期进行从库的首次连接。2.2 主从复制的三种模式对比命令传播、AOF、RDB的选择建议很多人选主从复制模式时很随意其实这直接决定了主从延迟和数据一致性表现。我整理了实践中用得最多的三种模式对照模式原理适用场景缺点命令传播默认主库执行写命令生成命令流发送到从库大多数读多写少的业务断电可能导致从库数据滞后RDB全量同步主库BGSAVE快照发送给从库初次建立主从关系、从库重启网络开销大不能高频使用AOF增量同步基于AOF日志重放对数据一致性要求极高的场景对磁盘和CPU有一定开销大多数情况下默认的命令传播方式就够用了但有个场景我强烈建议用AOF模式金融交易类的关键缓存数据。因为命令传播模式下如果从库连接断了很久重连时的部分复制可能会因为积压缓冲区溢出而回退为全量复制中间如果有主库未持久化的数据还是会存在丢失窗口。而AOF模式下数据的可靠性会高一个量级。2.3 实操三行命令搭好一主一从最朴素的搭法直接上命令在Slave节点上执行redis-cli SLAVEOF MasterIP 6379或者在redis.conf里配置replicaof MasterIP 6379注意新版本Redis5.0及以后把slaveof改名成了replicaof旧的slaveof命令还在只是官方推荐用新的。如果配置写在配置文件里修改后需要重启Redis进程。验证主从状态用这个命令redis-cli INFO replication输出结果关注这几个字段role:master connected_slaves:1 slave0:ip192.168.1.102,port6379,stateonline,offset180697,lag0lag表示从库同步延迟秒数正常情况下是0。如果lag持续大于5就要关注是不是网络带宽不够或者从库主线程被阻塞了。2.4 我排查过的两个高频主从故障与处理思路故障一主从数据不一致从库读到的数据比主库旧这个场景我遇到好几次最坑的是没有报错日志只有被动对比数据时才能发现。后来定位到原因主库开启了AOF但是刷盘策略设置的是everysec主库做了写操作从库通过命令传播执行也要时间同时从库所在的机器IO能力弱。解决办法是对从库做CONFIG SET auto-aof-rewrite-min-size之类的写入优化或者直接给从库换一台高IOPS的机器。故障二主从连接反复断开重连排查过程分三步第一步看主库日志发现有MASTER timeout字样说明TCP长连接被RST掉了。第二步检查主从之间的最大空闲连接时间结果发现中间有一层云负载均衡组配置了超时断开空闲连接策略Redis的长连接长时间空闲就容易被服务端杀掉。第三步调整负载均衡的空闲连接超时时间或者启用Redis自带的tcp-keepalive参数两边不一致的问题当场解决。这个排查链路不难关键是遇到问题先自己捋一下“网络链路里有哪些中间设备可能干扰长连接”比直接去改Redis参数高效得多。3. 生产级高可用落地方案主从复制与Sentinel哨兵协同部署主从复制搭建完之后还有个工程上不能回避的问题谁来保证主节点宕机能自动恢复如果全靠人肉盯那跟没有高可没什么本质区别。Sentinel就是解决这个问题的。3.1 Sentinel的“三个角色”和“一个Quorum”硬核拆解Sentinel本身也是一个Redis进程但它不存储业务数据只干三件事监控每隔1秒向Master、Slave和其他Sentinel发送PING确认它们是否存活。通知当被监控的Redis节点出现异常时Sentinel之间通过发布订阅机制互相通知。自动故障转移当Master被判定为主观下线SDOWN后经过多台Sentinel确认达到客观下线ODOWN条件就会自动从Slave中选出一个作为新的Master并修改其他Slave的复制目标。同时如果旧Master恢复了会发现自己的主角色已经被顶替自动降级为新Master的从节点。这里有个最关键的概念是quorum。它表示至少需要多少个Sentinel节点同意才能判定Master客观下线。在生产环境的推荐配置是至少部署3个Sentinel实例quorum设为2。为什么是3因为如果只有2个Sentinel当其中一个Sentinel挂掉了剩下的那个即使同意也无法构成多数派故障转移根本没法启动。3个节点最多允许挂1个仍然能正常工作。3.2 Sentinel配置文件里那些容易被忽略的参数Sentinel最常用的配置是这些port 26379 sentinel monitor mymaster 192.168.1.100 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000 sentinel parallel-syncs mymaster 1逐行解释一下sentinel monitor这是一个Sentinel要监控的Redis主节点后面跟的2就是quorum。down-after-millisecondsSentinel连续多久联系不上Master才算Master挂了。这个值设太短容易误判设太长又会导致故障转移太慢一般建议5到10秒。failover-timeout故障转移的超时时间如果15秒内没完成切换会重新触发新一轮选举。parallel-syncs故障转移完成后最多允许多少个从节点同时向新主节点发起复制的请求。设成1是最稳妥的避免多个从节点同时全量复制把新主节点打垮。还有一个很多人不知道的参数sentinel auth-pass mymaster 密码如果你的Master设置了requirepass必须配置这一项否则Sentinel无法和Master建立完整连接。3.3 故障转移过程的完整流程演示我特意做过一次手动停机演练把整个故障转移过程从头到尾记录下来kill掉Master的Redis进程。大约5秒后down-after-milliseconds设置的5秒Sentinel日志出现sdown标记主观下线。因为配置了3个Sentinel另外两个也发现Master无响应先后标记sdown。当quroum2判定达成后日志出现odown标记客观下线。Sentinel开始选举Leader在3个Sentinel之间通过Raft算法选出一个Leader。Leader从存活节点中选出优先级最高的Slave执行SLAVEOF NO ONE让它变成Master。剩余的Slave自动执行SLAVEOF 新Master重新建立主从关系。整个完成时间实测在9秒左右相比人工干预快了至少10倍。这一步操作下来你才能真正理解为什么生产环境要部署奇数个哨兵节点——核心是保证Leader选举时能形成多数派。3.4 客户端接入Sentinel的正确姿势很多人在这一步犯了个低级错误客户端直接连接Master的IP和端口结果Master切到从节点之后应用全部写入失败。正确做法是客户端连接Sentinel的地址通过Sentinel获取当前Master的地址。以Java的Lettuce为例RedisURI sentinelUri RedisURI.sentinel(192.168.1.101, 26379, mymaster); RedisClient client RedisClient.create(sentinelUri);这样客户端会自动从Sentinel获取当前Master的IP和端口并在发生故障转移后自动更新连接目标。如果你的项目用的是Spring Boot建议直接使用spring.redis.sentinel.mastermymaster和spring.redis.sentinel.nodes192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379配置。4. 搭建Redis Cluster从9001端口开始的分片世界前面的主从Sentinel方案解决的是高可用问题但有个天花板绕不开单机内存容量上限。假设你单机内存有32GB数据量到了30GB再涨就难受了扩容还得停机换机器。Redis Cluster就是冲着这个痛点来的。4.1 Cluster核心概念三件套槽、分片、总线端口先说槽Hash Slot。Redis Cluster把整个数据集固定划分为16384个槽每个Master节点负责其中一段连续或不连续的槽范围。对key计算槽位用的是CRC16算法slot CRC16(key) % 16384例如某个key计算出来落到槽1000槽1000在节点A的管理范围内那这个key就只能存储在节点A上。其它节点收到针对这个key的请求时会返回一个MOVED错误携带目标节点地址客户端根据这个地址重新发起请求。再说分片。三个Master节点就可以手动用redis-cli --cluster create命令分配槽位标准做法是每节点平均约分5461个槽。每个Master还可以搭配至少一个Slave形成三主三从的经典架构。在槽分配和主从绑定的基础上Cluster还指定了一个额外的总线端口通常默认是数据端口10000比如数据端口是6379总线端口就是16379节点之间通过这个端口互相通信、交换心跳和故障信息。4.2 三主三从Cluster搭建的完整实操准备6台机器或者一台机器上起6个Redis实例用不同端口区分这里以一台机器六个实例为例更省成本创建6个目录分别存放配置文件mkdir -p /data/redis-cluster/7001 /data/redis-cluster/7002 /data/redis-cluster/7003 mkdir -p /data/redis-cluster/7004 /data/redis-cluster/7005 /data/redis-cluster/7006以7001为例写一份最小配置port 7001 cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 5000 appendonly yes daemonize no protected-mode no注意几点cluster-enabled yes必须设置否则这个实例就是一个普通Redis不参与Cluster。cluster-config-file是集群的节点状态持久化文件每次启动都会从这里恢复集群拓扑。cluster-node-timeout节点超时时间如果超过5秒联系不上一个节点Cluster会认为该节点挂了。如果机器有外网防火墙一定记得把数据端口和总线端口都放通。将配置分别改端口后启动6个实例redis-server /data/redis-cluster/7001/redis.conf6个进程起来之后用自带的集群管理工具把它们拉进一个集群redis-cli --cluster create \ 192.168.1.101:7001 192.168.1.101:7002 192.168.1.101:7003 \ 192.168.1.101:7004 192.168.1.101:7005 192.168.1.101:7006 \ --cluster-replicas 1--cluster-replicas 1表示每个主节点带1个从节点。执行后工具会自动给三个主节点分配槽位并安排三个从节点作为对应主节点的备份。期间会询问你是否接受这个分配方案输入yes确认。查看集群状态redis-cli --cluster check 192.168.1.101:7001正常的输出里每个Master和Slave应该是OK状态槽位分布能正确显示在每个Master名下且总数加起来刚好是16384。4.3 为什么Cluster访问时会出现MOVED和ASK以及客户端怎么应对这是新手遇到最多的“异常”其实它根本不是异常而是Cluster的正常工作机制。MOVED当客户端连接的是节点A而key的槽位属于节点B时节点A返回MOVED 槽位号 节点B的IP:端口客户端需要重新连接B并再次发送命令。成熟的客户端如JedisCluster、Lettuce、Redisson会缓存槽位映射关系避免每次都重定向。ASK出现在槽位迁移过程中请求落在正在迁移的槽上。节点A会返回ASK 槽位号 节点C的IP:端口表示你要去节点C再问一次。和MOVED最大的区别是ASK不会更新客户端的槽位缓存只是一次性的临时转向。这一点我个人在真实项目里踩过坑刚开始用Cluster时业务代码里用了原生的Jedis没有用JedisCluster结果大量MOVED异常全打到日志里。后来才明白必须使用官方支持的集群客户端用普通客户端连Cluster就是自找麻烦。4.4 Cluster模式下的多key操作限制与应对因为槽位算法只认key本身所以MGET key1 key2如果key1和key2不在同一个槽就直接报错。解决办法有用Hash Tag把key写成{user:123}:name和{user:123}:email这样的格式因为花括号里的部分会被用来计算槽位只要花括号内容相同即使前缀后缀不一样也能落到同一个槽。用Pipeline分段执行把多key操作提前拆分成基于槽位的分组每次对同一组执行Pipeline结果在客户端汇总。放弃跨key事务手动补偿一些复杂的跨槽事务操作在Cluster模式下建议改成应用层事务补偿机制。这些操作规范必须在设计阶段就想清楚等代码上线后再改key格式数据迁移工作量极大。5. 实操经验沉淀通过Docker快速搭建主从和Cluster以及监控策略如果觉得裸机配置太繁琐Docker是一条特别快的路。我把常用的Docker Compose模板和监控踩坑经验一并分享出来。5.1 Docker Compose快速编排主从Sentinel直接用一个docker-compose.yml把主从加哨兵全部串起来version: 3 services: redis-master: image: redis:7 container_name: redis-master command: [redis-server, --appendonly, yes, --requirepass, mypassword] ports: - 6379:6379 redis-slave: image: redis:7 container_name: redis-slave command: [redis-server, --slaveof, redis-master, 6379, --masterauth, mypassword] depends_on: - redis-master ports: - 6380:6379 sentinel: image: redis:7 container_name: redis-sentinel command: redis-sentinel /usr/local/etc/redis/sentinel.conf volumes: - ./sentinel.conf:/usr/local/etc/redis/sentinel.conf depends_on: - redis-master - redis-slave ports: - 26379:26379对应的sentinel.conf长这样port 26379 sentinel monitor mymaster redis-master 6379 1 sentinel auth-pass mymaster mypassword sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 10000注意这里用的是Docker内部的主机名redis-master和redis-slave在Docker网络里可以互相解析。5.2 生产环境监控Redis的几个关键指标有了主从复制和集群如果监控跟不上故障发生时你依然是瞎子。我的建议是至少关注以下指标指标查看命令安全阈值主从同步延迟INFO replication的lag字段小于5秒持久化失败次数INFO persistence的rdb_last_bgsave_status必须为ok内存碎片率INFO memory的mem_fragmentation_ratio1.0到1.5之间命中率INFO stats的keyspace_hits和keyspace_misses计算大于85%集群节点在线数CLUSTER INFO的cluster_state必须是ok集群槽位覆盖CLUSTER INFO的cluster_slots_ok必须等于16384我分别踩过这几个坑某个Slave因为磁盘空间满了RDB加载失败但主从的TCP连接还在表面看Lag是0实际Slave上的数据已经停了很久。排查半天才从日志里发现RDB加载错误的报错。所以monitor一定要把rdb_last_bgsave_status纳入报警。内存碎片率长时间高于1.5多半是大量数据过期淘汰导致的。这种现象在缓存频繁写入和删除的业务中很常见可以考虑用CONFIG SET activedefrag yes开启自动碎片整理。5.3 主从复制和Cluster模式下的持久化策略差异主从架构中建议Master不开启AOF只依赖Slave落地持久化因为这样可以把磁盘IO压力分摊到Slave节点上。但有一个前提必须确保Slave的持久化是开启的且禁止执行SLAVEOF NO ONE之类的操作打断同步。Cluster模式下每个节点都承担读写职责写操作只落在key对应的Master但读可以配置replica支持不能依赖某个单独的Slave做持久化。如果某个Master挂了它的Slave顶上成为新Master如果新Master的持久化配置不全数据就可能直接空掉。所以Cluster模式建议每个节点都开启AOF并配合RDB做冷备。5.4 扩容缩容实操一键完成也不神秘Cluster的一个突出优势是支持在线扩容。比如现在三主三从想再加一主一从启动两个新实例后执行redis-cli --cluster add-node 192.168.1.101:7007 192.168.1.101:7001 redis-cli --cluster add-node --slave --master-id 新Master的ID 192.168.1.101:7008 192.168.1.101:7001然后把部分槽位迁移到新节点上用--cluster reshard命令工具会交互式地询问迁移多少槽位、把槽迁移给哪个节点。实测在线迁移几千个槽对业务影响几乎可以忽略不计只有少量键在迁移期间会有短暂的ASK重定向开销。但这里也提醒一下不要频繁做reshard因为每次槽迁移都会触发一部分键的序列化、传输、反序列化如果业务写入量很大迁移期间的CPU和带宽消耗不容小觑。最好把扩缩容安排在业务低峰期。6. 结合业务把主从和集群变成稳定底座配置调优与选型建议最后聊点工程化的东西。很多开发者把主从或者Cluster搭好之后就觉得万事大吉了其实后面的配置调优和选型才是真正拉开差距的地方。6.1 主从和集群在配置上容易忽视的优化项在主从复制模式下一个常见问题是主从数据延迟尤其是大量写操作时lag会持续走高。我遇到过的最离谱场景是从库3分钟内都没有赶上主库的最新数据。排查后确认不是网络问题而是主库开启了持久化和慢查询导致命令传播的产出速度跟不上写入速度。解决方法是给主库单独配置maxmemory-policy allkeys-lru把过期的冷数据及时淘汰出去实践下来能显著降低主库压力。另外检查一下主库是否配置了repl-backlog-size默认1MB太小建议设置为64MB甚至更高减少网络抖动时的全量同步概率。Cluster模式下关注cluster-require-full-coverage这个参数。它默认是yes意思是只要集群中有任何一个槽位没有可用节点在服务整个集群就拒绝请求。这在一些严格要求高可用的场景是灾难性的——某个槽的Master和Slave同时挂了整个集群对外都不服务了。如果你的业务能接受部分数据短暂不可用建议显式设置为no。6.2 三种方案到底怎么选一张决策表直接给你经常有同事问我主从、Sentinel、Cluster到底选哪个。我统一给个决策表业务场景推荐方案理由个人项目单机缓存主从复制最轻量容易维护公司内部系统数据量小但要求高可用主从复制 Sentinel自动切换故障恢复快电商、社交类应用读多写少主从复制多只读副本 Sentinel读扩展能力强延迟低大规模数据容量要求高Redis Cluster分片存储突破单机容量上限高并发写单Master扛不住Redis Cluster多Master分散写压力跨地域容灾主从复制的跨机房方案或自研双写Cluster官方不支持跨地域自动同步6.3 我踩过的最贵的一个设计失误最后说一个让我印象深刻的教训。之前给一个做促销活动的业务设计了Redis集群架构数据量不大但是QPS峰值很高。我当时图省事直接用了三主三从Cluster没有细想业务对数据强一致性的要求。结果活动上线后集群里一个主节点挂了自动切换之后发现部分从节点上数据有丢失参与活动的用户看到自己的优惠券状态回退到了几分钟前。复盘时发现根因是Cluster模式下主从复制默认是异步的Master先返回写成功给客户端再异步同步到Slave。当Master在同步完成前宕机这部分写操作就永久丢失。如果业务要求“写成功后必须保证数据可查”那Cluster就不合适或者需要业务层做补偿逻辑。所以我的建议是不要在架构设计阶段就迷信某一个技术方案先问清楚业务底线的数据语义。Redis能帮你解决性能问题但解决不了绝对的数据不丢问题。分布式系统里的CAPRedis主从和Cluster都优先保障的是AP。每套方案用到极致都需要对它的原理和短板有清醒认知。希望这篇文章能把Redis主从复制和集群搭建的细节讲透也能帮你少走一些我当年走过的弯路。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →