行业资讯
📅 2026/9/7 23:21:51
Redis主从复制实战:原理、配置与高可用架构详解
开始正文先说句实在话很多人在Redis刚起步的时候都用单机数据量不大、并发不高单点部署完全够用平时写个缓存、存个SessionRedis自身性能又那么猛感觉不出什么毛病。但等到流量真正上来或者线上出过一次宕机之后你才会意识到Redis单点是多么脆弱的一件事。进程一挂、机器重启、网络抖动缓存直接全凉后面数据库瞬间被击穿那场面只要经历过一次就忘不掉。而“redis主从节点”这套玩法就是解决这类问题的最基础、也最核心的手段。这篇文章我把Redis主从节点的原理、配置、坑、排查思路一次性讲透。不扯虚的先把它能解决什么问题说清楚再手把手教你怎么搭接着分析它内部是怎么工作的最后把我在实际运维里踩过的坑和排查技巧都整理出来。无论你是刚接触Redis的新手还是已经在生产环境用了一段时间但没认真抠过细节的开发者这篇都值得花几分钟看完。1. 主从节点到底解决了什么问题1.1 单点Redis的尴尬处境先还原一个典型场景。你的系统用Redis扛热点数据比如商品详情、用户登录态、接口频控计数日常请求过来先打Redis打不到再落数据库。平时一切正常Redis的QPS能轻松顶到十万以上数据库压力很小大家都很开心。但突然某一天Redis所在的机器CPU飙高接着内存告警还没来得及处理进程直接OOM挂掉了。这时候发生什么所有请求全部穿透到数据库。数据库连接池瞬间被打满慢查询堆积CPU飙升整个服务跟着雪崩。哪怕你快速把Redis拉起来缓存里也是空的重启后那几分钟数据库依然在裸奔状态下承受着全部流量压力风险极高。这个问题的本质是什么系统的核心状态被集中到了一个节点上这个节点一旦出问题整个链路就瘫痪。单机的处理能力再强它也是个单点改变不了故障域过大的事实。Redis官方其实早就意识到了这个问题并且给出了最基础的解决思路把数据复制到多个节点上每个节点都存一份完整数据客户端读写时可以分散到不同节点任何一个节点挂了其他节点还能顶上。1.2 主从架构的设计思路Redis主从复制的核心思路其实很朴素就是“一主多从”。主节点负责处理写请求从节点通过复制主节点的数据来保持同步同时可以分担读请求。写入操作在主节点上执行数据变更记录会异步同步给从节点这样从节点手里的数据就和主节点基本保持一致。用生活化的例子类比一下“主节点”有点像一个公司里的正式数据库管理员所有重要的修改、写入都从他这里走“从节点”就像每天定时拿着管理员给的备份资料去各分部门同步信息的人。各部门日常查询时不需要再去打扰管理员本人直接看本地同步好的资料就行。偶尔管理员休假或者出了状况其他部门至少手里还有一份最新资料不会两眼一抹黑。延迟极低、吞吐量极高的读写分离架构在高并发场景下尤其吃得开。主库专注处理写操作从库水平扩展承担读流量这一套搭配下来能扛住的读QPS是非常可观的。而且因为每个从库都存了全量数据等于天然的多副本备份主库数据盘坏了也能用从库恢复安全性和容灾能力提升得很明显。1.3 主从不是万能药理解它的边界很多初学者容易产生一个美好幻想上了主从就等于高可用主库挂了系统还能自动切换。这里必须把话说清楚单纯的主从架构并不提供自动故障转移能力。主库挂掉之后从库不会主动升级为主库上层应用也不会自动感知并切换连接地址整个流程都需要人工介入处理。但是生产者是数据库消费者是多个从库Redis在实现这个生产者消费者模型的时候并没有引入类似消息队列中“确认回执”和“持久化队列”等复杂机制。它的复制协议默认是异步的简单直接换来的是性能开销很小、对主库的影响非常有限。你写入主库主库返回OK数据同步在后台进行不会因为某个从库过于缓慢就阻塞主库的正常写入。另一个边界是主从复制解决的是“数据副本冗余”和“读压力分散”它不解决数据容量无限扩展的问题。因为每个从库都保存全量数据如果你要缓存的数据总量已经超出单机内存上限靠加从库是加不出来一个无限大的缓存的。这时候要想的是Redis Cluster集群模式把数据分片打散到多台机器那是另一个话题了。但在绝大多数中小规模场景下主从节点这套方案性价比极高理解了它的原理后面再去玩哨兵、玩集群你都会觉得顺理成章因为那些高级方案本质上都是在主从复制这个地基上盖楼。2. 从零搭建一套Redis主从环境2.1 环境准备与版本选择先交代一下环境。我用的是两台Linux服务器来演示操作系统是CentOS 7.9Redis版本为7.0.14。生产环境我建议优先选择官方稳定版7.x现在非常成熟性能和稳定性都比6.x有明显提升而且内置了很多新特性。如果你还在用5.x或者更早的版本建议及早安排升级计划毕竟老版本在安全漏洞修复和功能迭代上确实跟不上了。这里补充一个实际经验主从节点的Redis版本尽量保持一致。虽然Redis允许主从之间跨小版本同步跨大版本也基本兼容但为了避免一些不可控的坑比如不同版本之间复制协议的细微差异、某些命令行为的变化最好还是统一版本。我在测试环境见过主库7.0、从库6.2的组合平时跑着没问题但在全量同步的某些边界条件下会冒出奇怪的现象排查起来很费劲。安装Redis的方式有很多种yum、apt、源码编译我都用过。如果你有干净的测试环境我推荐源码编译安装原因很简单你能确切地知道自己装的是什么版本、编译了哪些参数出了问题也更容易排查。当然用包管理器安装图个省事也行不影响后面的主从配置演示。# 下载源码并编译安装 wget https://download.redis.io/releases/redis-7.0.14.tar.gz tar xzf redis-7.0.14.tar.gz cd redis-7.0.14 make -j4 make install编译完成后Redis可执行文件会安装到/usr/local/bin目录下包括redis-server、redis-cli这些常用工具。如果要调整安装路径可以在make阶段指定PREFIX比如make PREFIX/opt/redis install。2.2 主库基础配置安装完成后先配置主库。Redis的默认配置文件在源码目录的redis.conf里建议复制一份到/etc/redis/目录下统一管理。mkdir -p /etc/redis cp redis.conf /etc/redis/redis-6379.conf打开配置文件下面几个关键项需要认真调整。# 监听地址建议明确指定内网IP不要用0.0.0.0裸奔 bind 0.0.0.0 # 保护模式生产环境建议设为no但前提是配好了防火墙 protected-mode no # 端口号默认6379 port 6379 # 后台运行 daemonize yes # 进程文件 pidfile /var/run/redis_6379.pid # 日志级别和日志文件 loglevel notice logfile /var/log/redis_6379.log # 持久化配置生产环境建议同时开启RDB和AOF save 900 1 save 300 10 save 60 10000 appendonly yes appendfilename appendonly.aof # 设置密码访问 requirepass your-strong-password这里有个值得说明的点protected-mode这个参数默认是yes意思是在没有密码且没有bind特定网卡的情况下Redis只允许本机访问。很多人图省事直接把它设置成no但如果没有云安全组或防火墙兜底端口暴露到公网之后极易被扫描爆破。我的习惯是受信任内网环境可以设no但密码一定要设如果是跨网络边界部署建议用防火墙或安全组策略把6379端口限制到只允许业务服务器访问。另外要强调一下requirepass。主从架构下主库设置了密码从库同步时也要带着密码才能通过认证这个后面配置从库的时候会用到。很多人在这上面栽过跟头主库有密码从库的masterauth没配结果日志里反复报NOAUTH Authentication required复制链路一直建立不起来。配置改完之后启动主库redis-server /etc/redis/redis-6379.conf redis-cli -a your-strong-password ping能收到PONG就说明主库已经正常工作了。2.3 从库配置与常见方式接下来配置从库。假设第二台服务器上已经同样安装好了Redis端口我们也保持6379不变只需要在配置里指定主库的地址和认证信息就可以了。在从库的redis.conf中需要加入以下几个关键配置# 声明自己是某个实例的从库 replicaof 192.168.1.10 6379 # 主库访问密码 masterauth your-strong-password # 从库默认只读建议保持开启 replica-read-only yes注意配置项用的是replicaof而不是早期版本里的slaveof。Redis 5.0开始就将相关术语做了替换从库节点叫replica主从复制叫replication语义更准确。不过slaveof在后续版本中依然作为兼容别名保留我还是建议直接写新写法。除了改配置文件还有两种常见方式可以达到同样的效果。一种是在命令行直接发送命令不需要重启进程适合临时调整或者初始化配置时使用redis-cli -a your-strong-password replicaof 192.168.1.10 6379另一种是启动时通过参数指定redis-server --port 6380 --replicaof 192.168.1.10 6379 --masterauth your-strong-password我个人在演示和测试环境比较喜欢第一种方式因为不用重启就能生效排查问题时特别好用。但如果你希望每次启动都自动建立复制关系还是老老实实写进配置文件里一劳永逸。这里再提醒一个容易被忽略的点如果从库之前自己也有数据执行replicaof之后旧数据会被清空然后开始全量同步主库的数据。做这个操作前一定要确认从库上的旧数据没有价值别把人家的数据干没了才后悔。2.4 启动验证如何判断复制已建立配置完成后启动从库然后登录从库检查复制状态。redis-server /etc/redis/redis-6379.conf redis-cli -a your-strong-password info replication在返回的信息里重点看几个字段# Replication role:slave master_host:192.168.1.10 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_read_only:1 slave_repl_offset:123456 slave_priority:100这几个字段的含义分别是什么role标明当前节点角色是从库master_host和master_port是主库地址master_link_status是主从链路状态up代表正常down代表断了这是你最需要盯着的字段master_last_io_seconds_ago表示距离上次和主库通信过了多少秒如果这个值持续增大说明链路可能出了问题slave_repl_offset是从库当前的复制偏移量。在主库上执行info replication你还能看到每个从库的IP、端口、状态和偏移量。再做一个更直观的验证。在主库上写入一个测试keyredis-cli -a your-strong-password set hello world然后到从库上读redis-cli -a your-strong-password get hello如果返回world说明数据已经成功同步到从库整条复制链路是通的。到这一步一套最基础的Redis主从架构就搭起来了。主库可以正常读写从库自动同步数据并且提供读取能力整体可能只花几分钟。但搭建只是起点想让它稳定运行你还得理解它内部是怎么复制的以及什么情况下会出问题。下一段我讲原理这部分是实操排查的基础值得认真看看。3. 主从复制的核心原理看懂了才能玩明白3.1 全量同步第一次连接走的完整流程第一次建立主从关系时从库对主库的数据一无所知这时候必须做一次全量同步。整个过程可以拆成几个阶段。从库启动后向主库发送PSYNC命令请求同步数据。主库接收到PSYNC请求后如果判定需要做全量同步就会执行BGSAVE在后台生成一份RDB快照文件同时主库会为这个从库开启一个缓冲区用来记录从快照开始之后产生的所有写命令。RDB文件生成完毕后主库通过网络把它发送给从库。从库收到RDB文件后先清空自己本地的旧数据然后把RDB加载进内存完成一次全量数据的对齐。RDB加载完成后主库会把在快照期间新产生的写命令缓冲数据再发给从库从库逐条执行这些命令最终数据状态和主库完全一致。这里值得展开说一下第四步“清空本地旧数据”这个动作。如果从库原本是某个业务的独立缓存节点里面有一些未被持久化的热数据一旦你把它切换成另一个主库的从库它立刻就会把内存清空再重新加载主库的全量数据。这个过程不是瞬间完成的如果RDB文件很大加载期间从库处于不可服务状态对业务是有影响的。所以我前面特意提醒过切换从属关系前必须评估从库上的数据价值和业务的容忍度。全量同步的代价主要在两个方面。主库要执行一次BGSAVE这个操作会fork子进程去写RDB文件虽然fork之后有写时复制机制兜底但在大实例上fork瞬间的CPU和内存开销依然不可忽视。同时RDB文件要完整地通过网络传一遍如果数据量几十GB耗时会非常可观期间网络带宽会被占满进而影响主库同机其他业务。所以全量同步不是一个能随随便便频繁触发的事情生产环境要尽量避免不必要的全量重同步。3.2 增量同步断线续传的秘密如果每次主从连接断开之后都要重新全量同步那代价就太大了。好在Redis在主从复制协议里实现了部分重同步也就是断线后续传的能力。它的核心依赖三个要素主从各自的复制偏移量、主库的复制积压缓冲区、实例的复制IDreplid。先解释一下复制积压缓冲区。主库会维护一个固定大小的内存环形缓冲区写命令在执行之后除了写入内存数据外还会被同时写入这个缓冲区供后续同步给从库用。它的大小由配置项repl-backlog-size决定默认是1MB。再来看偏移量。可以把它想象成一个不断增加的序号每个字节的写命令都能对应到一个偏移量。主库每写一条命令自己的偏移量就增长从库每收到并执行一条命令自己的偏移量也跟着增长。正常情况下主从偏移量保持一致。当从库和主库的连接断开后它还会继续尝试重连。重新连接上时从库会带上自己当前的复制偏移量和复制ID再次发起PSYNC请求。主库拿到这个偏移量之后检查这段偏移量的数据是否还在自己的复制积压缓冲区里。如果在主库就只把缓冲区里从那个偏移量开始的所有命令发给从库从库依次执行很快就能追平这就叫部分重同步。如果从库落后太多了偏移量对应的数据已经不在缓冲区里那没办法只能退回全量同步。这里有个明显可以优化的点就是repl-backlog-size的大小。默认1MB对写操作不频繁的场景还算够用但在高并发写入的环境下1MB可能几秒钟就写满了。一旦写满环形缓冲区会覆盖旧数据如果此时恰好有从库断线它就很可能追不上偏移量被迫发起全量重同步。所以如果你的业务写流量大建议把repl-backlog-size调大一些比如64MB甚至128MB给断线重连留出缓冲余地。这个调大带来的内存成本是可控的但全量同步带来的风险成本是很高的两相权衡非常值得。3.3 复制ID从库切换主库时最关键的身份标识提到复制偏移量就不能不提replid。每个Redis实例在启动时都会生成一个40位的十六进制字符串作为自己的复制ID代表这个实例的复制历史身份。在一主多从架构中所有从库的replid会与主库保持一致因为它们的“数据血脉”来自同一个主库。replid这个标识在其中起到什么作用这就牵涉到主从切换场景了。假设主库A挂掉了我们手工把一个从库B提升为新的主库。其他从库C、D还在傻乎乎地认为自己应该跟着A的replid走但现在B已经变成了新的“血脉源头”。当C、D重连到B时B会首先检查它们的复制ID发现自己和它们的replid不一致就知道它们是自己转正之前收编的小弟此时B如果配置正确会接受它们的全量同步请求把它们纳入自己的复制体系然后重新生成统一的复制历史。如果B和C、D的重连过程中B已经接收了新的写入而且C、D此前断线期间还没来得及同步的数据不能靠增量补齐那就必须做全量同步来对齐。这也是为什么在主从切换之后经常会出现一次全量同步的原因属于正常现象。可以把replid的情况类比成同一家公司换了新的法定代表人原来的员工都只认旧法人的签字新法人上台之后要先给所有人重新发一遍工牌、重新做一次档案登记大家才能继续顺畅协作。Redis里的复制ID完全就是这个思路。3.4 异步复制的延迟问题最后必须说清楚一点默认情况下Redis主从复制是异步的。也就是说主库执行完SET命令返回给客户端“写入成功”的时候这个数据可能还没有同步到任何一个从库上。主库会在执行写命令之后把命令发送给从库但这个过程不阻塞主库自身的写入响应。这带来一个典型的风险场景主库刚执行完写操作就宕机了写命令还没来得及发给从库这个数据就永久丢失了。虽然窗口期非常短通常只有毫秒级甚至更短但在某些对数据完整性要求极高的场景下这就是不可接受的一环。Redis也考虑到了这一点提供了WAIT命令可以在一定条件下等待指定数量的从库确认写入完成后再返回但这会阻塞写入、牺牲延迟实际应用并不普及。绝大多数场景下大家接受这个异步窗口因为它的性能收益实在太明显了。从库上的数据延迟还会带来另一个现象如果你开启了读写分离在从库上读到刚写入的数据可能会出现短暂失败。比如用户注册完跳到登录页如果登录页校验走的是从库而注册数据的同步还没完成就会报“用户不存在”。解决这类问题的思路一般是关键数据写入后强制走主库读或者业务层面保证读不到时等一小段时间再重试又或者使用Redis 7.0新增的replica-serve-stale-data相关的精确控制参数做更细颗粒度的策略定制。这个问题我后面还会在读写分离部分再展开讲。复制原理这一层理解透了后面遇到任何同步异常你至少知道该往哪个方向去排查而不是两眼一抹黑到处瞎试。4. 主从配置调优与读写分离落地4.1 从库只读、过期键与数据一致性主从架构中有一个默认的行为值得重点强调从库默认是只读的。你在从库上执行SET、DEL这类写命令会直接报错。这个限制是合理的因为从库的数据来自主库的复制流如果允许从库自己写数据那主从之间的数据很快就会分叉同步机制也就彻底乱套了。虽然可以通过配置replica-read-only no把从库改成可写但我强烈不建议你这么做。我见过有人在从库上直接写数据结果下一次全量同步直接覆盖数据没了还得花很长时间排查到底哪里出了问题。想要向从库写入的唯一理由是做一些特殊的本地扩展比如给从库加一个只在本机生效的标记key但这种场景极其少见没必要为了这种需求破坏架构的一致性。还有一个细节是过期键的处理。K-V存储系统在清理过期键时通常有两种思路惰性删除和定期删除。Redis的主库两者都会用但主从架构中过期键的删除不会主动发同步命令给从库。比如主库有一个SET key value EX 10的键10秒后过期了。从库并不知道这个键过期了它依然会返回这个键的旧值。什么时候从库才真正删掉它只有当客户端访问这个键的时候从库会检查一下当前时间是否超过了过期时间超过了就返回空并顺带把它清理掉。也就是说从库采用的是惰性删除机制不主动通知只在访问时感知。这个差异会造成什么影响在主库上键已经过期但同步不及时的从库上还能短暂读到旧值这就有了数据不一致的风险。如果你的业务对过期键的读一致性要求比较高需要注意这个机制必要时在业务层面对过期数据的处理逻辑做兜底。4.2 读写分离的落地姿势主从架构最核心的用途就是读写分离把写流量引导到主库把读流量分散到多个从库。在落地时有几个关键问题需要考虑清楚。第一连接层的读写分离应该在哪里做如果你的应用直接使用Redis客户端可以在代码里封装两个连接池一个主库连接池负责写操作一个从库连接池负责读操作。以Java项目为例可以定义两个RedisTemplate一个配置主库地址一个配置从库地址然后根据操作类型选择不同的template执行。第二如果使用的是Spring Boot可以考虑引入Redisson或者读写分离相关的组件它们对读写分离有更好的支持。像Redisson在RedisClient的配置里可以配置多个从节点地址读操作默认走从库写操作走主库并且在主库不可用时还能自动进行故障转移。第三也是最关键的业务层必须明确哪些操作可以容忍延迟、哪些不能。用户登录后写Session这个Session数据如果被路由到从库去读用户可能登录不上秒杀场景下的库存写操作更是必须强一致地走主库。通常的做法是将读写分离的开关做成可控的对核心的、强一致的读操作全部打标强制走主库其余普通的读请求才允许走从库。我自己在项目里落地的姿势是做一个简单的RedisRouter内部维护两个连接池对外提供get(key, boolean strong)接口。strongtrue时强制走主库保证强一致strongfalse时优先走从库牺牲一点点一致性换取更大的读吞吐。这样改造起来的成本不高但可以精确控制每条读请求的一致性级别线上运行下来很稳。4.3 主从延迟的监控手段读写分离的底层隐患是主从延迟任何人都无法完全消除。所以监控延迟指标是一件必须做的基本功不要等出了线上事故才开始关注。最简单的延迟测量方法是比较主从的偏移量差值。每台从库通过info replication都能看到slave_repl_offset这个值表示从库已经复制的偏移量。在主库执行info replication也能看到每个从库的偏移量。差值越大说明从库落后主库越多。脚本上可以定时去主库抓一次从库列表和偏移量再定时去每台从库抓一次当前偏移量两个值一减就得到了当前的同步滞后字节数。更精细的做法是测量“时间维度的延迟”。比如主库写入一个带时间戳的key然后定时去从库读这个key对比当前时间和写入时间的差值就能估算出从库的大概延迟秒数。这类工具在开源社区有现成的实现比如redis-faina和redis-rdb-tools可以直接拉下来用。设置延迟告警阈值我是这样做的字节数延迟超过10MB或者时间延迟超过5秒就要触发告警。如果延迟持续升高说明从库的处理能力已经跟不上主库的写入速度了这时候要么减少该从库承担的压力要么考虑提升从库的机器配置要么优化主库的写入模式减少大key和慢命令总之不能一直拖下去。5. 主从之上Sentinel哨兵与高可用雏形5.1 光有主从不够故障转移还得靠哨兵前面反复提过单纯主从架构里主库挂了从库不会自动顶上去。你要想让系统做到主库故障时自动完成切换需要引入Redis Sentinel哨兵机制。哨兵本身是一个独立的Redis进程它的职责是监控所有Redis节点的健康状况并且在检测到主库不可用时自动执行故障转移把某个从库提升为新的主库再让其他从库重新指向新主库完成重新同步。对客户端而言它还可以通过哨兵获取当前主库的地址从而始终连接到最新可用的主节点上。生产环境部署哨兵有一个铁律至少三个哨兵实例而且最好部署在互相独立的机器上。为什么至少要三个因为哨兵判断主库是否故障需要遵循“多数派原则”。如果一个哨兵发现主库联系不上它不会立刻认定主库挂了而是会和其他哨兵互相沟通只有当超过半数哨兵都认为主库不可用时才会真正触发故障转移。如果有两个哨兵其中一个觉得主库挂了另一个不这么认为那永远凑不出多数派故障转移就不会触发。所以三是个底线保证任意一台哨兵机器挂掉时剩下的哨兵还能凑出多数派做决策。5.2 三节点哨兵快速搭建假设你现在有两台Redis实例一主一从地址分别为192.168.1.10和192.168.1.11。计划用三台机器部署三套哨兵哨兵的端口号是26379。每台哨兵机器上新建一个sentinel.conf配置文件内容如下port 26379 daemonize yes logfile /var/log/redis-sentinel.log pidfile /var/run/redis-sentinel.pid # 监控主库mymaster是自定义的名字2是判定故障所需的最少投票数 sentinel monitor mymaster 192.168.1.10 6379 2 sentinel auth-pass mymaster your-strong-password # 主库无响应多少毫秒后判定为主观下线 sentinel down-after-milliseconds mymaster 5000 # 故障转移后从库重新同步新主库的最大并发数 sentinel parallel-syncs mymaster 1 # 故障转移超时时间 sentinel failover-timeout mymaster 15000down-after-milliseconds是判定主观下线的超时时间5秒比较适中太短容易误判太长会让故障恢复时间变长。parallel-syncs设为1的意思是故障转移时只允许一个从库同时参与同步避免多个从库同时全量复制新主库把刚转正的主库给压垮。failover-timeout控制故障转移各阶段的总超时时间默认15秒够用。三个哨兵配置文件内容基本一样唯一不同的可能只有日志文件路径之类的机器相关配置。分别启动redis-sentinel /etc/redis/sentinel.conf启动后用redis-cli -p 26379 info sentinel检查哨兵状态。正常情况下可以看到sentinel_masters:1并且能列出当前监控的主库地址和从库信息。5.3 故障转移实测记录哨兵部署完成之后我建议你在测试环境亲手做一次故障转移演练这会让你对整个流程建立非常直观的认知。我的演练步骤是这样的主库先正常提供服务哨兵正常监控中。然后在主库上手动执行redis-cli shutdown模拟宕机。此时观察哨兵日志大约5秒后也就是down-after-milliseconds设置的值哨兵开始宣称主库主观下线然后多个哨兵之间互相协商超过半数后判定客观下线接着发出选举通知选出一个哨兵主导故障转移流程再从活跃的从库中选一个提升为主库。这里有个细节正常流程下Redis会优先选择数据最完整的从库也就是复制偏移量最大的那个从库作为新主库。被提升的从库会执行REPLICAOF NO ONE命令断开与旧主库的复制关系并禁止自己继续作为从库运行。与此同时其他从库会自动重新指向新的主库重新开始同步数据。整个故障转移过程大约在15秒左右完成。之后客户端如果在连接层接入了哨兵就能自动感知到新主库的地址并继续工作整个系统的可用性就有了质的提升。我强烈建议你在生产环境每一次变更之后都手动做一次故障转移演练把流程跑熟把耗时量清楚别等到真出事的时候抓瞎。另外哨兵本身也不是无限高可用的它自己也会挂所以生产环境至少要有三个哨兵或者更多总之保证多数派原则始终成立。6. 常见问题与排查实录6.1 复制断开的典型场景我在实际运维中见过不少主从断开的情况汇总下来高频的有以下几种。第一种是网络抖动。主从之间如果跨机房或者跨可用区部署网络延迟和抖动会比同机房高很多。一旦发生持续几秒的丢包主库和从库就会断开连接。正常场景下从库会按repl-backlog里的数据做增量续传但如果抖动时间过长或者累积写命令超过积压缓冲区大小从库就只能做全量重同步了。排查时可以看从库日志搜索Full resync还是Partial resync能快速定位断线后的恢复方式。对网络不稳定的环境我的建议是从库和主库尽量部署在同一个网络域内有条件的用内网专线别把跨公网的访问用于生产环境的复制链路。第二种是主库执行了FLUSHALL或者FLUSHDB这些清空命令同样会同步到从库导致从库数据被清空。这在很多公司都发生过事故最典型的情况是有人在测试库执行了清空操作结果连的是生产主库一瞬间生产数据全部清掉了。如果你有这类风险建议直接开启rename-command FLUSHALL 禁掉这个命令或者用高权限的账号体系严格控制谁有权限执行。第三种是复制过程中的慢命令比如在从库上加载一个大key。从库的repl-backlog如果能追上还好追不上的话从库会掉队严重时会触发大量过期键清理导致主库CPU飙升。这类问题的排查重点是找出那些执行时间特别长的命令可以用SLOWLOG命令在主库上查看慢查询记录再针对性地优化业务写入逻辑比如对大集合类型的操作改成分批处理。6.2 从库数据不一致排查经常有人来问为什么我的从库数据和主库对不上排查思路按优先级排列如下。先查主从延迟。执行redis-cli -p 从库端口 info replication看master_last_io_seconds_ago如果这个值已经很大说明从库很久没和主库通信了数据落后是正常的。再用redis-cli -p 从库端口 debug digest对比主从的key校验值能快速判断整体数据是否一致。再查是不是有非预期写入。检查从库的replica-read-only配置确认从库没有被改成可写。如果有应用连接到了从库并执行了写操作那从库数据必然和主库产生分叉。这种情况通常是因为连接配置错误或者客户端直连了从库地址去写数据排查时需要核对应用的Redis连接配置。还要查过期键的影响。前面说过从库对过期键的淘汰是惰性的过期键在主库已经消失但从库还会短暂存在。如果你在主库刚删掉一个key立刻去从库读还能读到旧值这不代表主从数据真正出现不一致只是过期机制的差异导致的短暂窗口。业务上接受不了这个窗口就得在代码层面做规避。6.3 安全与运维避坑备忘最后整理一批我踩过的坑每条都是真实教训。密码认证必须统一下来。如果你给主库配了requirepass那么从库必须配masterauth哨兵配置里也必须指定auth-pass三处密码对不上任何一个复制链路就建不起来。这个配置项之间的关系新手第一次搭的时候很容易漏。protected-mode的坑要重视。默认开启时如果你没有bind到指定网卡且没有设置密码Redis会拒绝外部连接。如果你bind了0.0.0.0又把保护模式设置成了no那Redis等于完全裸露在网络上这是很危险的一件事。我的建议是密码必设防火墙必开bind必须精确到业务网段三项缺一不可。从库执行INFO命令可以看到很多诊断信息但注意别把从库当作监控数据的主要来源。生产环境的监控采集建议从主库拿到从库列表和复制状态再从每台从库采集各自的指标两边交叉对比这样才能及时发现某台从库掉队的苗头。还有一点是主从节点尽量不要部署在同一台物理机。很多测试环境为了省钱把主从都装在一台机器上这样做完全失去了容灾的意义机器本身挂了主从一起完蛋。哪怕是用虚拟机多开几个实例、放到不同的宿主机上也比全挤在一起强。关于大key和全量同步的连锁反应我再提醒一下如果从库因为某些原因触发了全量重同步主库会执行BGSAVE生成RDB快照这个操作会对主库的内存和CPU产生明显压力。如果你的主库内存已经占到物理内存的70%以上BGSAVE触发fork时很容易导致内存暴涨甚至被系统OOM杀死。遇到这种情况要么给主库预留更充足的内存余量要么规划好全量重同步的操作窗口避免在业务高峰期触发。6.4 关于expire和LRU淘汰的一些补充再补充一个在主从环境下经常被忽略的细节内存淘汰策略。Redis在单机模式下如果达到maxmemory限制会根据配置的淘汰策略主动清理部分数据。但在主从架构下LRU淘汰命令并不会直接同步给从库。跟过期键类似从库的数据淘汰也是“被动”的——当从库发现某个key已经过期时才会去删除它它不会主动去清理那些被主库LRU淘汰的key。这会导致什么现象如果主库的内存很大从库的内存也很大主库触发淘汰策略清理掉了一批key但主库内存和从库内存的容量并不完全一致从库可能还没有到触发自己淘汰策略的程度所以这批被删除的key在从库上依然存在一段时间。等从库自己内存吃紧、触发淘汰时才可能处理掉它们。这个行为在主从环境里造成的最终表象就是即使主从延迟为0两边遍历出来的数据也可能不一样。要彻底规避这个问题比较麻烦但绝大多数业务场景下这种短时间的不一致影响并不严重。你只要知道它存在别在排查数据不一致问题时对这个点纠结过头就可以了。7. 实操总结一套模板化的主从搭建流程最后把这套搭建流程整理成一个可以直接照抄的模板方便你新建环境或排查问题的时候参考。准备两台或以上Linux服务器统一Redis版本以7.0.x为例先完成Redis的安装和基础配置。第一台作为主库修改redis.conf中的bind、requirepass和持久化相关选项第二台及更多机器作为从库在配置中加入replicaof指向主库地址和端口并配置masterauth密码。配置完成后分别启动服务在主库或从库执行info replication验证复制状态确认master_link_status为up。在从库上执行一次读操作确认数据同步正常。如果需要自动故障转移再部署至少三台哨兵统一监控主库地址并设置好密码和投票阈值。线上环境中主库建议开启RDB快照和AOF追加写两种持久化保证数据安全从库可以根据需求调整是否开启RDB。从库默认只读不要随意修改。另外建议建一个简单的监控脚本每30秒或1分钟采集一次主从的复制状态和偏移量差值设置好告警阈值。哪怕只是最粗糙的告警也比半夜被用户投诉之后才发现主从断了强得多。我在实际使用主从架构的几年里最深的体会是这套方案虽然看起来简单但真正考验人的地方在细节比如密码配置的一致性、从库只读的约束、全量重同步的条件、哨兵多数派的设计原则任何一个环节没想明白线上都可能出一个不大不小的事故。基础架构没有捷径把原理吃透把每一步配置背后的原因搞清楚比背多少面试题都管用。最后再分享一个小技巧如果你需要临时验证主从数据的一致性不需要写复杂的比对程序直接在从库上执行redis-cli --rdb /tmp/dump.rdb导出一份数据然后使用redis-rdb-tools工具对比主库的rdb文件即可。不过这个操作会对从库产生一定的IO压力建议在业务低峰期操作而且建议只用于定期的、非实时的数据核对不要设成高频例行任务。