1. 为什么我把主从配置当作Redis高可用的第一课先讲个我自己的经历。之前负责的一个项目缓存层就一台Redis实例读写都靠它撑着。一开始流量不大单机吃得住没人觉得这是个问题。后来有一次机房巡检几台宿主机要做例行重启Redis所在那台机器恰好也在计划内。结果重启那一刻整个服务端的缓存全部清空数据库连接数直接被打满线上接口的响应时间从几十毫秒飙到几秒。虽然重启很快恢复了但那次故障让我意识到Redis单点部署平时看着没事出问题的时候就是全站陪葬。后来我做了两件事第一给Redis配上持久化第二就是搭了主从复制。很多人一听主从就想到集群、分片、Sentinel这些名字觉得高可用是个大工程。但从实际投入产出比看主从配置是性价比最高的一步。它不需要额外引入什么复杂中间件改动量小却能解决两个最痛的问题一是读多写少的业务场景下把读压力从主节点分流出去二是主节点如果宕机从节点手里还有一份完整数据配合哨兵可以做故障切换不至于缓存层瞬间全灭。当然主从不是万能药。它不解决写入瓶颈也不自动完成故障转移更不是数据强一致的方案。但它是后续所有高可用体系的基石哨兵监听的是一主多从的结构集群的每个分片内部也是主从复制。如果主从复制这块的原理和配置没吃透后面玩哨兵、玩Cluster遇到问题照样抓瞎。这篇文章不打算讲太深的理论重点放在主从配置怎么落地、复制原理怎么理解、实际项目里哪些参数必须调、以及我踩过的那些坑。如果你正准备给Redis做主从或者已经在用但某些配置项只敢照抄不敢改这篇应该能帮上忙。2. 从零搭建主从两种不绕路的部署方式2.1 同一台机器跑两个实例的配置方法先明确一点Redis主从的主和从本质上是两个独立的Redis进程只是各自角色不同。角色由配置项决定而不是由机器的物理位置决定。所以最简单的实验环境就是在一台机器上跑两个Redis实例一个端口6379一个端口6380。具体操作如下先准备两份配置文件我从一份redis.conf复制出redis-6380.conf然后做几处关键修改cp /etc/redis/redis.conf /etc/redis/redis-6380.conf修改redis-6380.conf里的内容port 6380 pidfile /var/run/redis_6380.pid logfile /var/log/redis_6380.log dir /var/lib/redis/6380 replicaof 127.0.0.1 6379这里最关键的就是replicaof这一行。它的语法是replicaof masterip masterport含义是我是从节点我去连接这个主节点。注意Redis 5.0之前的版本里这个配置项叫slaveof5.0之后改名为replicaof语义上更准确也去掉了带有历史包袱的叫法。两者目前都还能用但新配置建议一律用replicaof。启动主节点redis-server /etc/redis/redis.conf启动从节点redis-server /etc/redis/redis-6380.conf然后连接到从节点上检查状态redis-cli -p 6380 info replication你会看到类似这样的输出# Replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_read_only:1master_link_status:up说明从节点已经连上主节点复制链路是通的。到这一步主从的最小配置就算完成了。有一个细节要注意dir这个目录如果不存在从节点启动时会报错因为Redis要往里面写RDB文件。我在配置的时候习惯为每个实例单独建目录避免多个实例共用一个数据目录后来数据文件把彼此覆盖的情况我在生产环境里是见过的。2.2 用Docker搭一主一从的实操如果不想在宿主机上手动维护两个进程Docker是另一个很顺手的方案。尤其是测试环境里要反复销毁重建容器方式干净很多。下面这套我实测过可以直接照着跑。先创建一个Docker网络让两个容器之间可以用容器名通信docker network create redis-replica-net启动主节点docker run -d \ --name redis-master \ --network redis-replica-net \ -p 6379:6379 \ redis:7.0 \ redis-server --port 6379启动从节点docker run -d \ --name redis-slave \ --network redis-replica-net \ -p 6380:6379 \ redis:7.0 \ redis-server --port 6379 --replicaof redis-master 6379注意从节点的参数--replicaof redis-master 6379。这里直接用容器名redis-master代替IPDocker自带的DNS解析会把它转换为主节点容器的实际IP。这样做的好处是容器重建时IP变了也不用改配置。启动完成后检查docker exec redis-slave redis-cli info replication看到master_link_status:up并且master_host:redis-master就说明容器之间的主从关系已经建立。这里有人会问从节点里redis-server --port 6379写的是6379为什么宿主机映射到6380这是两码事。容器内部Redis监听的端口是6379宿主机通过-p 6380:6379把容器的6379映射到宿主机的6380这样你在宿主机上想连从节点用的是redis-cli -p 6380。容器内主从通信走的是容器网络用的是6379完全不受影响。2.3 验证主从是否生效的三个标准动作配置完了怎么知道它真的在干活我一般依次做三件事。第一写入主节点读从节点。连到主节点SET一个key然后连到从节点GET如果能在从节点读到这个值说明数据已经复制过去了。第二看info replication输出。主节点上关注connected_slaves字段如果显示1说明一个从节点挂在下面。从节点上关注master_link_status和slave_repl_offset。第三故意写一下从节点。默认情况下从节点是只读的直接执行写命令会报错(error) READONLY You cant write against a read only replica.看到这个报错反而是好消息说明从节点的只读保护生效了。生产环境中我强烈建议不要把replica-read-only改成no除非你有非常明确的理由。从节点一旦可写主从数据就会分叉后面排查数据不一致会非常痛苦。3. 主从复制的核心原理全量同步与增量同步3.1 先看懂replid和复制偏移量很多教程直接把主从配置给出来就完了但我发现如果不懂复制原理出了问题根本无从下手。主从复制表面上就是把主节点的数据同步到从节点但Redis在实现上分成了两种情况全量同步和增量同步。先看两个关键指标在info replication里能看到主节点视角master_replid:8e2a0a2b3c4d5e6f... master_repl_offset:123456从节点视角master_replid:8e2a0a2b3c4d5e6f... slave_repl_offset:123456master_replid是主节点的复制ID可以理解成一份数据集的身份证号。主节点启动时会随机生成一个只要主节点没有重启这个ID一般不变。全量同步时主节点会把replid发给从节点从节点从此认这份数据集的ID。offset是复制偏移量表示主节点已经产生或从节点已经消费了多少字节的复制数据流。Redis会把所有写命令包装成一种叫复制流的字节序列每写一条命令偏移量就增加对应的字节数。主从对齐的本质就是让从节点的slave_repl_offset追上主节点的master_repl_offset。这两个指标怎么看一个是判断我们是不是同一份数据一个是判断我们同步到哪了。排查主从问题时我第一件事永远是看这两个值。3.2 全量同步为什么要先做RDB快照当从节点第一次连接主节点或者主从之间的复制链断得太久、积压数据已经超出了缓冲区Redis会触发全量同步。这个过程可以拆成几步从节点向主节点发送PSYNC命令请求同步。主节点收到后如果确认需要全量同步会启动一个后台进程把当前内存数据生成一份RDB快照。生成RDB的同时主节点会把新产生的写命令持续写入复制积压缓冲区repl backlog。RDB快照生成完成后通过网络发送给从节点。从节点清空自己的旧数据把RDB加载进内存。主节点把复制积压缓冲区里积压的写命令继续传给从节点从节点逐条执行追平偏移量。这里有一个大家容易忽略的细节RDB生成期间主节点并没有停止服务新的写请求还在持续进来。如果只把RDB发过去从节点加载完之后RDB里没有包括最近几秒的写入数据就少了。所以第3步里的复制积压缓冲区非常关键它记录的是RDB快照那一刻之后主节点新执行的写命令专门用来补偿这个窗口期。全量同步的代价和网络带宽、数据量成正比。如果我有一份20GB的Redis数据全量同步一次从节点要清空重来主节点要fork子进程生成RDB期间可能造成短暂的延迟。所以生产环境里我通常避免让主节点频繁触发全量同步方法就是调大repl-backlog-size。3.3 增量同步为什么会越追越近全量同步完成之后主从之间就进入了增量同步阶段。此时主节点每执行一条写命令除了返回给客户端还会做两件事更新自己的master_repl_offset把命令写入复制积压缓冲区并推送给所有从节点。从节点收到命令流后依次执行同样的命令同时更新自己的slave_repl_offset。这就是为什么主从的数据能保持一致本质上是命令重放到了从节点上。主从之间每秒钟还会有心跳包用来确认链路存活这个时间间隙可以通过repl-ping-replica-period来控制默认10秒一次。增量同步看起来很美好但它依赖一个前提主节点上的复制积压缓冲区还保留着从节点尚未消费的命令数据。如果从节点网络断开太久累计的写入量超过了缓冲区大小主节点就无法只靠增量把数据补齐只能降级成全量同步。这个太久的程度由repl-backlog-size除以平均写入速率决定。我之前遇到过一个真实场景某天凌晨一个从节点所在主机发生了网络分区断了几分钟。恢复之后主节点上积压的数据量正好超过了默认的repl-backlog-size默认1MB结果一重连就触发全量同步几十GB的数据重新传了一次主节点和从节点都出现了一段时间的CPU和I/O飙升。后来我把repl-backlog-size调整到64MB、甚至在写入量特别大的业务上给到256MB这种非必要的全量同步就很少再出现了。这个参数没有标准答案要用写入速率和可容忍的断线时长来推算。如果按每秒10MB的写入量、希望容忍5分钟的断线那至少需要10MB * 300s 3GB的缓冲区。但内存也别盲目的给积压缓冲区占的是主节点的内存太大一样会挤压其他内存空间需要平衡。3.4 一主多从与主从链的取舍主从关系不是只能一主一从一个主节点下面挂多个从节点完全可以connected_slaves会显示具体数量。但要注意所有从节点都直接从主节点拉数据主节点需要往每个从节点各自推一份命令流。从节点数量多了主节点的网络发送压力会上升。另外还存在一种主从链结构A是主B是从B同时又是C的主。配置方法是在C上写replicaof B_IP B_PORT。这种链式结构能缓解主节点的推送压力B可以代替A给C提供复制数据流。代价是数据多跳一层同步延迟会稍微大一点排查问题时路径也更长。我的建议是除非从节点数量特别多或者跨机房场景否则优先用一主多从的结构不要轻易引入链式简单结构更好运维。4. 上线前必须调好的几个关键配置4.1 密码认证配置的组合关系Redis开启密码认证之后主从配置要多一个参数很多新手在这里栽过跟头。主节点的配置requirepass your-strong-password从节点的配置replicaof 127.0.0.1 6379 masterauth your-strong-passwordmasterauth的作用是从节点在连接主节点、以及执行后续复制命令时用这个密码向主节点进行认证。如果只配置了requirepass而漏掉了从节点的masterauth日志里会反复出现Master is trying to PSYNC but authed failed或者类似认证失败的报错从节点的master_link_status会一直不是up。还要注意一点在Redis 6.0之后引入了ACLAccess Control List。如果你用ACL给复制用户分配了权限那么masterauth里配的可能是用户名密码的组合方式或者需要在主节点配置一个专用的复制账户。老项目如果直接从5.x升上来建议把认证相关的配置通读一遍别只看requirepass就以为万事大吉。4.2replica-read-only到底要不要动前面说到默认从节点是只读的这个配置项就是replica-read-only默认值为yes。我的态度很明确不做特殊说明一律保持yes。原因很简单主从复制是单向的只能主推给从。如果从节点上面出现了本地写入那这部分数据永远不会同步回主节点。更麻烦的是如果哪天这个从节点被提升为主节点比如用哨兵或手动REPLICAOF NO ONE时它身上那些本地写入会堂而皇之变成主数据和其他节点的数据产生不可预测的分叉。有同学会想我业务上有一个计算中间结果想暂存在Redis里写在从节点上不是可以省主节点的压力吗这个思路是错的Redis主从没设计成双向同步所有写入都应该走主节点。想隔离压力应该考虑拆一个单独的Redis实例出来给这种场景而不是绕过主从的语义。4.3replica-priority和故障切换的关系replica-priority表面上是个很小的参数但它会影响哨兵做故障切换时选谁当新主。这个参数的默认值是100。数值越小优先级越高。哨兵在选举时先看数据同步谁更接近主节点slave_repl_offset偏移量更大者优先再综合考虑是否在线、优先级等。生产环境里我通常会把不同机房的从节点设置成不同的优先级。比如同机房的从节点优先级设成50跨机房的设成100这样万一主节点挂了哨兵优先把同机房的从节点提升为新的主节点减少跨机房切换带来的额外延迟。这个参数平时不起眼但关键时刻决定了你的故障切换是否聪明。另外如果某个从节点正在执行手动维护或者负载很高可以通过CONFIG SET replica-priority 0临时把它排除在候选列表之外。注意0的含义是永远不参与选举而不是最高优先级这两个含义差别很大。4.4 数据安全兜底min-replicas-to-write和min-replicas-max-lag主从复制不是数据安全的绝对保障。有一种极端情况主节点在短时间内与所有从节点断开连接但自身仍然正常接收写入。此时如果主节点本身出故障宕机后从节点提升上来会发现缺失了刚才那段时间的写入数据。想要降低这个风险可以开启Redis的最少可用从节点保护。配置如下min-replicas-to-write 1 min-replicas-max-lag 10含义是如果主节点可用从节点的数量小于1或者可用从节点的数据延迟超过10秒那么主节点会拒绝执行写命令直接返回错误。这就叫自我保护式降级。启用这个配置要慎重。如果部署在弱网环境从节点经常短暂断连主节点可能动不动就拒绝写入业务上会呈现间歇性写入失败的现象。所以这个参数的定位是最后的兜底而不是标配。我的经验是重要数据场景可以开普通缓存业务不建议开。5. 主从配置常见的坑从日志现象到根因分析5.1 主从配置文件里的bind和protected-mode问题一个很常见的现象主从都是自己本地起的info replication里一切正常但把replicaof的IP从127.0.0.1换成实际内网IP后从节点就是连不上。根因多半出在主节点的bind配置。Redis默认配置里bind 127.0.0.1 -::1意味着只监听本机回环地址。主节点只监听127.0.0.1时其他机器自然连不进来。在主节点的配置里按需修改bind 0.0.0.0 protected-mode no但直接把protected-mode改成no不是好习惯。更好的做法是保留protected-mode yes同时配合requirepass开启密码认证。受保护模式下如果Redis没有配置密码而且绑定的地址对所有网卡可见它只接受来自回环地址的连接。一旦你设了密码保护模式通常是允许外部连接的。所以这个问题的真正解法是绑内网IP或者0.0.0.0并且配上强密码然后确认主从之间的masterauth也正确。排查这种连接问题时我喜欢用两步走先在从节点所在机器上用telnet 主节点IP 6379验证端口通不通再看Redis日志里有没有连接被拒绝的记录。这样能快速区分是网络层问题还是Redis配置问题。5.2 全量同步时从节点替换慢主节点fork阻塞全量同步第一次建立时主节点要fork一个子进程来生成RDB。fork本身的耗时和内存量相关内存越大fork等待时间可能越长。如果主节点内存有几十GB同时又有大量写请求fork瞬间可能阻塞主线程几百毫秒表现为请求延迟抖动。我之前处理过一个大内存实例的全量同步主节点当时内存30GB从节点第一次连接时主节点出现了近1秒的延迟监控图上出现一个明显的毛刺。虽然能接受但如果频繁发生就需要控制了。遇到这种情况优先要避免频繁全量同步其次可以在低峰期再挂新从节点实在不行再考虑用repl-diskless-sync做无盘同步让主节点直接通过网络把内存数据传给从节点减少本地磁盘I/O但这条路径对从节点和网络带宽都有要求。无盘复制开启参数repl-diskless-sync yes开启后主节点不再先把RDB落到本地磁盘而是直接在内存里生成并通过socket流式发送给从节点。好处是省掉一次磁盘写和读对磁盘性能弱的机器有奇效坏处是如果传输失败没有本地RDB文件可以回滚重试。所以这个参数适合网络稳定、带宽充裕的机房环境。5.3 复制延迟排查网络和单线程因素的叠加主从复制是异步的主节点写完命令后不会等待从节点回复就继续执行。所以在高并发写入下从节点天然有延迟这是正常现象。真正需要排查的是从节点持续落后且越来越严重。从节点单线程执行复制命令流如果从节点上还有别的重负载操作——比如执行了大KEYS、SMEMBERS等阻塞命令或者被某个慢查询拖住——那么它处理复制流的速度就会变慢偏移量偏差就会累积。另外如果从节点所在机器性能比主节点差好几个档次也会出现追不上的情况。排查时我会在从节点上执行redis-cli -p 6380 info replication重点看slave_repl_offset和主节点的master_repl_offset之差以及master_last_io_seconds_ago。如果slave_repl_offset长时间不增长说明复制流卡住了。另外用SLOWLOG GET看看从节点有没有慢命令也是一个高效手段。5.4 主从复制和键过期时间的时钟典故有个容易踩的坑key的过期时间。主从复制下键的过期行为是由主节点决定的。主节点在键真正过期时会生成一条DEL命令追加到复制流从节点收到后才会删除这个key。这带来两个影响。一是如果从节点和主节点的机器时钟差异过大从节点可能无法在精确时间点触发惰性删除需要等主节点的DEL命令通知。不过Redis主从复制里从节点是不会主动去判断某个key是否已经过期的它有自己的一套逻辑但本质依赖主节点的DEL。所以最保险的实践是所有部署Redis的服务器统一用NTP同步时间。这个问题平时不痛不痒但一旦牵涉过期key和缓存一致性的判断会非常难排查。第二个影响是淘汰策略。主节点内存达到maxmemory后会按maxmemory-policy淘汰key淘汰产生的删除操作也会同步给从节点。如果从节点自己内存不够还是得靠主节点的淘汰策略来控制。所以主从实例的maxmemory设置要保持一致否则会出现一种诡异的现象主节点淘汰了key从节点又继续保留两边数据不一致。5.5 分布式锁在主从架构下的没法100%安全有些业务会直接用Redis实现分布式锁最常见的是用SET key value NX EX timeout。但在主从架构下这种锁有一个很经典的安全缺口。流程是这样的客户端A在主节点上成功加锁。锁的key还没来得及同步给从节点主节点宕机了。哨兵把从节点提升为新的主节点。此时客户端B来加同一个锁发现新主节点上根本没有这个锁的key于是也成功加锁。两个客户端同时持有一把锁分布式锁的互斥性就被打破了。这个问题并不是主从配置本身能解决的而是CAP理论在分布式锁场景下的必然取舍。如果你对锁安全性要求极高要么使用Redis官方推荐的RedLock方案但这个方案在业界也有争议要么考虑用etcd、ZooKeeper这类带有强一致语义的协调服务。如果只是在业务里防一下并发重复提交不涉及资金类安全场景那Redis主从加分布式锁仍然是个性价比很高的方案。关键是你心里得有这个底锁在极端故障下可能失效不能把它当作唯一的防御手段。6. 从一主一从到多从实例扩容和读写分离细节6.1 在线加从节点的常用步骤生产环境的主从并不是配置好就一劳永逸。流量增长后需要在不重启主节点的前提下增加新的从节点。过程其实很简单准备一份从节点配置replicaof指向现有主节点。启动从节点实例等待全量同步完成。确认master_link_status:up新从节点接入复制拓扑。在需要把读流量接入新从节点时让负载均衡或服务配置指向新地址。在从节点执行CONFIG REWRITE把运行时配置持久化到配置文件避免重启后配置丢失。第5步很多人会漏掉。通过命令行CONFIG SET replicaof动态修改过配置的实例如果不执行CONFIG REWRITE重启后配置会还原成旧状态。尤其是有些云厂商提供的Redis实例控制台上可以改从节点归属但底层改的是运行态配置。等下次实例迁移或重启主从关系就神秘失效了。6.2 写流量不能走从节点读流量怎么分配主从配置完成之后最常遇到的需求是读写分离。Redis本身并没有内置负载均衡从节点不会自动分摊流量。你需要自己做流量分发。常见的方案有几种在业务代码里做双客户端主客户端写从客户端读。通过代理层比如Codis、Predixy这类中间件透明转发。在应用层的配置中心里分别配置读写地址由服务框架读取。我一般不太推荐在应用代码里写死两个Redis地址因为一旦发生故障切换新主节点地址变化代码需要跟着改。更灵活的做法是把地址交给配置中心管理切换后只更新配置不用发版。另外要记住一点主从复制的延迟虽然通常很小但并不是零。读写分离后刚写完的值可能在极短时间里从从节点读不到这属于正常现象。如果业务对一致性敏感比如库存、订单状态要么强制走主节点要么在写入后做短暂缓存别指望从节点是实时的。6.3 用监控手段盯住复制延迟和主从状态主从架构搭建完毕监控必须跟上。我最常看的指标包括master_link_status主从链路是否在线。一旦不是up基本就是网络抖动或主节点出问题了。master_repl_offset与slave_repl_offset的差值差值持续增大说明复制延迟在累积。connected_slaves从节点数量是否跌破预期。主节点rejected_connections如果开启了min-replicas-to-write这个指标能反映出主节点是否在拒绝写入。监控告警的阈值我给过一个实用化的建议复制延迟超过30秒就告警因为30秒已经超过很多业务可接受的范围。主从断连超过1分钟告警说明复制链路已经不健康。至于要不要激进到秒级看业务敏感度但告警太频繁会让人麻木阈值也别拍脑袋定。一些自建监控平台会用Prometheus加redis_exporter来采集这些指标性价比很高一套下来基本能满足中小团队的观测需求。云厂商的Redis控制台一般也自带这些监控项盯住延迟和连接数基本就够用。7. 为主从配置画上句号我从一手一脚搭起来里学到的三件事配置Redis主从这件事难度并不高真正的考验是理解它背后的边界。第一主从复制解决的是读扩展和数据冗余问题解决不了写入瓶颈和自动故障转移。如果你后面要做真正的Redis高可用还需要在主从之上部署哨兵或者直接上Cluster。主从是地基但地基之上还得有建筑。第二所有配置一定不要无脑照抄。repl-backlog-size、min-replicas-to-write、replica-priority这些参数不同的业务场景答案完全不同。我见过有团队把min-replicas-to-write开在一个写入量很大但网络不稳定的IDC里结果平时没问题一到网络抖动就大量写入失败排查了半天才定位到是这个自我保护在作怪。第三主从配置完成后请务必做一次故障演练。最简单的演练方式把主节点关掉看看从节点是不是还活着然后执行REPLICAOF NO ONE把一个从节点提升为主节点再验证业务读写是否正常。这一步很多人从来不做等到真出事才在慌乱中翻文档那就太晚了。主从只是起点不是终点。但把这一步走扎实你会发现后面接触到哨兵、Cluster时很多概念会突然变得顺理成章。架构演进从来不是一蹴而就的都是从最小的可用单元开始一步步加固起来的。