1. 单机 Redis 为什么会顶不住主从复制到底在解决什么问题Redis 主从复制这个话题几乎每个用缓存的人都会碰到但真正把它讲透的人不多。很多人对它的印象停留在配一行replicaof就完事结果是线上主库一挂从库要么数据不全要么根本提不上来最后连夜回滚。主从复制解决的其实是一个非常朴素的问题让一份数据同时存在于多台机器上写入只走一台读取可以分摊到多台并且当主力那台出问题时还有备胎能顶上去。它适合谁适合所有把 Redis 当作核心链路缓存、甚至当作轻量存储在用的人从刚接触 Redis 配置文件的初学者到需要做容量规划与故障预案的运维和后端开发都能从这套机制里拿到自己需要的东西。但要说清楚它得先回到它诞生的原因。Redis 是单线程处理命令的6.0 之后网络 IO 多线程命令执行仍然是单线程这意味着不管你的机器有多少个核同一时刻能处理命令的只有一个线程。这个设计带来了极简的数据结构和可预测的延迟但也划出了一条明确的天花板。我见过太多团队在压测时发现 QPS 卡在某个数值上不去第一反应是加 CPU加完发现毫无变化因为瓶颈根本不在 CPU 核数上。理解主从复制本质上就是理解这条天花板在哪里以及怎么在它下面把系统撑住。1.1 三个把单机 Redis 逼到墙角的典型场景第一个场景是读多写少的业务。商品详情页、用户资料、配置中心这类场景读写比动辄几十比一单机的读能力很快就不够用了。你当然可以升级机器但单机性能提升是线性的、有上限的而业务流量增长往往是指数的。更要命的是大 key 或者热 key一个明星商品的缓存被几十万请求同时打到同一台机器上CPU 曲线直接拉成一条直线这时候再怎么扩机器规格都没用只能把读流量分散出去。第二个场景是数据可靠性焦虑。单机 Redis 把数据放在内存里虽然有 RDB 和 AOF 做持久化但磁盘文件终究是滞后的。机器整机故障、磁盘损坏、机房断电恢复时间往往以小时计。对于缓存来说最坏结果是回源数据库被打穿对于那些把 Redis 当存储用的业务比如分布式锁的元数据、会话信息那就是实打实的数据丢失。主从复制给出的答案是至少有一个实时同步的副本在另一台机器上主库没了副本还在。第三个场景是运维操作带来的可用性中断。升级版本、调整配置、迁移机器单机环境下每次操作都得停服。有了主从你可以先把从库提上来再折腾原来那台。这些场景合在一起构成了主从复制最核心的价值主张读扩展、数据冗余、运维窗口。注意主从复制不是高可用的完整答案。它能提供副本但主库挂了自动切换这件事主从复制本身并不负责那需要额外的故障转移机制。只配主从而不做切换方案等于买了保险却没填受益人。1.2 主从复制的定位它是地基不是整栋楼很多人对主从复制的期待过高以为配上了就等于高可用。实际上它在整个可用性体系里处于最底层它只解决数据有几份的问题不解决谁来当新主的问题也不解决客户端怎么知道新主在哪的问题。往上还有故障检测与自动切换、客户端路由、读写分离代理等一整套东西。搞清楚这个分层你在设计架构时就不会把不该它承担的责任压给它。另一件需要提前认清的事是主从复制是异步的。主库执行完写命令把命令放进复制缓冲区就返回给客户端了至于从库什么时候收到、什么时候执行完主库不管。这个设计换来了低延迟代价是主库崩溃时那些已经返回成功但还没传播到从库的写入会永久丢失。这个窗口有多大取决于网络 RTT、从库处理速度和主库的写入压力正常情况是毫秒级但在主库被大事务或者慢查询拖住时会显著放大。理解这个异步特性是后面所有参数调优和故障预案的前提。2. 整体设计与核心机制拆解Redis 的复制协议经历过几次大改从最早的SYNC每次断线都全量重传堪称灾难到 2.8 引入的PSYNC再到 4.0 的 PSYNC2 支持故障切换后的部分重同步。今天你用的任何一个 5.x、6.x、7.x 版本跑的都是 PSYNC 这一套。它的设计思路可以用一句话概括能不传全量就不传全量必须传全量时尽量少占资源。所有复杂机制都是围绕这个原则展开的。这套设计的精妙之处在于它把数据同步拆成了两个可以独立优化的阶段。第一阶段是全量成本高但能保证基线一致第二阶段是增量成本低但依赖于一个能缓冲中断的队列。两个阶段之间用一个偏移量offset衔接让从库能精确地告诉主库我收到了哪里。这个思路在今天看很常规但在当年是一个相当务实的选择——它没有追求强一致而是用尽力而为 可检测的偏差换取了吞吐。2.1 角色划分与数据单向流动主从复制的拓扑里角色非常清晰一个 master 接受读写N 个 replica 默认只读。数据流是严格单向的从 master 流向 replica从不反向。这个约束看起来简单但它决定了后面很多事情从库不能直接写入否则主从数据会分叉从库的过期键不能自己删必须等主库的删除命令传播过来从库的FLUSHALL也不会传播回主库。单向流动还带来一个隐蔽但重要的行为从库上的键过期是被动处理的。从库不会主动清理过期键它等主库删完之后把DEL或UNLINK命令同步过来。但在等待期间从库读取这些键时会做逻辑判断发现已过期就返回空。所以你DBSIZE看到的数量和实际可读的键数量可能对不上别慌这是设计如此。如果你在从库上看到一堆本该过期却还在的键先别急着排查检查一下主库那边是不是没触发删除。拓扑形态上除了最基础的一主多从还支持链式从库的从库和树状。链式的价值在于分担主库的复制压力主库只需要喂一个从库剩下的由这个从库往下喂。代价是数据延迟会逐层累加链越深末端从库越滞后。我个人的经验是链路深度不要超过两层再深了延迟就不可控而且任何一层出问题都会影响下游一大片。2.2 全量同步与增量同步的两段式设计从库第一次连上主库或者断线太久导致偏移量已经不在主库的缓冲范围内就会触发全量同步。流程大致是从库发PSYNC ? -1主库回FULLRESYNC runid offset然后 fork 一个子进程做 BGSAVE 生成 RDB 快照同时把这段时间新来的写命令攒进复制缓冲区。RDB 生成完毕后发给从库从库先清空自己的数据加载 RDB然后再把缓冲区里攒的那批命令重放一遍。整个过程结束后主从状态才算对齐。这里有几个容易被忽略的细节。第一加载 RDB 期间从库是阻塞的如果你的数据集有几十 G这个阻塞可能长达几分钟期间从库完全不响应请求除非你配置了允许读旧数据。第二fork 子进程本身有开销在内存大的实例上 fork 可能卡住主库几百毫秒这就是所谓的fork 抖动。第三RDB 传输期间主库的内存会涨因为要额外维护复制缓冲区如果从库网络慢缓冲区可能撑爆触发主库断开连接。增量同步则是日常状态主库每执行一个写命令就把它写进复制积压队列replication backlog一个固定大小的环形缓冲区同时异步发给所有从库。从库收到后执行并定期回报自己当前的偏移量。断线重连时从库发PSYNC runid offset主库检查两件事runid 对不对得上offset 是否还在积压队列的有效范围内。都满足就回CONTINUE只补发缺失的那一段但凡有一个不满足退回全量。2.3 复制积压队列、偏移量与 runid 三件套这三个东西是增量同步能成立的基石值得单独拆开讲。偏移量offset是一条只增不减的计数器主库每传播一个字节就加一从库也维护自己的 offset表示我已经处理到哪里。主库通过比对从库上报的 offset 和自己的 master_repl_offset就能算出这个从库落后了多少字节。复制积压队列repl-backlog是一个固定大小的环形缓冲区默认 1MB。它保存最近传播的命令流用于断线期间的补发。大小怎么定公式很朴素backlog_size ≥ 平均写入速率 × 你能容忍的最长断线时间。假设你的实例每秒写入 500KB希望从库断线 60 秒内还能增量追上那至少需要 30MB留点余量配 64MB 比较稳妥。配小了会频繁触发全量同步配大了只是多占点内存权衡下来宁大勿小。runid是每个 Redis 实例启动时生成的 40 位随机字符串用于标识这次生命周期。为什么要这个因为主库重启后数据可能变了光看 offset 会误判。PSYNC2 还引入了replid2和second_replid_offset专门解决主从切换后新主能不能和旧从库做部分重同步的问题。没有这层设计一次故障切换就会导致所有从库全量重同步那画面太美不敢看。3. 从握手到命令传播链路上的关键细节知道机制和能调好它是两回事。这一节把整条链路拆到可操作的粒度包括握手、全量、增量、心跳四个阶段以及每个阶段你能调的参数、该看什么指标。我见过的大部分复制问题本质都是没搞清楚某个阶段在干什么导致排查方向完全跑偏。举一个典型的误判从库的master_link_status显示 down很多人的第一反应是网络不通。但实际排查下来可能是主库设置了requirepass而从库没配masterauth认证失败也可能是主库的client-output-buffer-limit replica把从库踢了还可能是从库加载 RDB 太久超时了。这三种情况在日志里的表现完全不同但对外的症状都是连不上。所以下面按阶段拆就是为了让你能对上号。3.1 建立连接时到底发生了什么从库启动时读配置里的replicaof masterip masterport主动向主库发起 TCP 连接。连上之后不是直接同步数据而是先走一套握手从库发PING确认主库活着主库回PONG如果主库配了密码从库发AUTH然后从库发REPLCONF listening-port port告诉主库自己的端口方便主库在INFO里展示再发REPLCONF capa eof capa psync2声明自己支持的能力集这一步决定了能不能用上 PSYNC2 的部分重同步特性。握手完成后才发PSYNC。这一整套流程走下来大概是 1 到 2 个 RTT 的时间正常情况下几十毫秒。如果你在日志里看到MASTER - REPLICA sync started之后长时间没有Sync with master: ... done那问题就在握手或者全量阶段而不是网络层。这时候该看的不是ping通不通而是主库的日志里有没有对应的连接记录、从库的master_last_io_seconds_ago是不是在持续增长。提示replicaof和slaveof是同一个东西后者是历史遗留写法。新版本配置文件里官方推荐replicaof但命令行里两条命令都还能用。别因为看到两套名字就以为是两种机制。3.2 全量同步阶段的三个关键动作全量同步的第一步是 fork。主库调用fork()创建子进程子进程负责把当前内存中的数据写成 RDB 文件。Linux 的 fork 是写时复制Copy-On-Write理论上很快但页表拷贝的成本和内存大小成正比一个 64GB 的实例 fork 一次可能要几百毫秒。这段时间主库是阻塞的所有请求都会被卡住。这就是为什么大内存实例要避免频繁全量同步也是为什么repl-backlog-size要给足。第二步是传输。传统方式是子进程写磁盘文件主进程读文件发送给从库这叫 disk-backed。Redis 4.0 引入了无盘传输diskless子进程直接把 RDB 数据通过 socket 写给从库省掉了磁盘 IO。6.0 默认关闭这个特性7.0 起默认开启。无盘传输的坑在于它要求 socket 在传输期间保持稳定如果从库网络抖动这次传输就废了得从头再来。而且它有个repl-diskless-sync-delay参数默认 5 秒意思是主库收到第一个全量请求后不急着开始先等 5 秒看看还有没有别的从库也要同步凑一批一起做。这个设计专门用来对付主库重启后几十个从库同时来要全量的复制风暴。第三步是从库加载。从库收到 RDB 后先FLUSHALL清空自己的数据然后加载。这个过程中如果配置了replica-serve-stale-data yes默认从库对读请求会返回旧数据或者错误如果配成 no从库会直接对除INFO、REPLCONF之外的所有命令返回SYNC with master in progress错误。生产环境里建议保持 yes至少让读请求有个兜底除非你的业务完全不能容忍脏读。3.3 命令传播与心跳机制进入稳定状态后主库每执行一个写命令都会同时做两件事写进自己的 AOF如果开了和写进复制积压队列然后异步推给所有从库。注意写进积压队列这个动作是无条件的即使当前一个从库都没有backlog 也在累积直到repl-backlog-ttl默认 3600 秒到期且没有从库连接时才释放。心跳分两个方向。主库每隔repl-ping-replica-period默认 10 秒向从库发一个PING确认从库还活着。从库则每隔 1 秒向主库发REPLCONF ACK offset汇报自己最新的偏移量。这个 ACK 有两个用途一是让主库知道从库进度INFO replication里的slave0: ...,offsetxxx,lagx就是这么来的二是配合min-replicas-to-write和min-replicas-max-lag实现写入保护——当健康从库数量不足或者滞后超过阈值时主库直接拒绝写请求。这个保护机制值得多说一句。它的价值在于把数据可能丢失这件事从隐性变成显性与其悄悄丢数据不如直接报错让上游知道。但它的代价是可用性下降主库可能因为从库抖动而拒绝服务。我的建议是只在真正不能丢数据的业务上开而且阈值要放宽一些比如min-replicas-to-write 1配min-replicas-max-lag 10不要设成必须两个从库都在线那样太脆。3.4 关键参数怎么配、数值怎么算参数不是照抄模板就行的每个数值背后都得有依据。下面这张表是我在多个项目里反复调整后沉淀下来的数值只是参考具体还得按你的写入速率和网络条件算。参数默认值建议值调整依据repl-backlog-size1mb64mb ~ 256mb平均写入速率 × 最大容忍断线秒数 × 2repl-backlog-ttl36003600 ~ 7200无从库连接后保留时长太短会导致重连即全量repl-timeout6060 ~ 120大 RDB 传输时容易误判超时数据集大就调大repl-ping-replica-period1010保持默认即可调小会增加无谓流量repl-diskless-sync6.0 为 no7.0 为 yesyes省磁盘 IO前提是网络稳定repl-diskless-sync-delay55 ~ 10从库数量多时调大凑批减少全量次数client-output-buffer-limit replica256mb 64mb 60按实例内存调太小会踢掉慢从库太大会 OOMreplica-read-onlyyesyes除非有特殊需求否则不要改成 noreplica-serve-stale-datayesyes全量期间仍可提供读服务repl-backlog-size的计算我再强调一遍因为这是最容易配错的一个。公式是平均写入字节速率 × 最大可容忍断线时长。你的实例如果 QPS 一万、平均每个写命令 100 字节那就是 1MB/s希望断线两分钟内能增量追上就得配 120MB 以上。配成默认的 1MB意味着断线一秒多就要全量那这个实例基本上一直在全量同步和准备全量同步之间循环主库的 fork 开销能把 CPU 吃满。4. 一主两从环境实操搭建、验证、故障演练理论说再多不如动手跑一遍。这一节用单机多端口模拟一主两从所有配置和命令都是可以直接抄的。虽然生产上肯定是多机部署但单机多实例能让你在几分钟内把整个链路走通包括故障演练这种在真实环境里不敢随便做的操作。环境规划很简单主库 6379两个从库 6380 和 6381数据目录分开日志分开。这么做的目的是让你能在同一个终端里同时观察三个实例的INFO replication输出主从之间 offset 怎么变化、状态怎么流转一目了然。等你把这套流程走熟再上多机部署无非就是改改 IP。4.1 环境规划与配置文件先把目录建好主库和从库的数据目录必须独立否则 RDB 文件会互相覆盖这个错误我在新手那里见过不止一次。mkdir -p /data/redis/{6379,6380,6381}主库配置文件关键片段port 6379 daemonize yes pidfile /var/run/redis_6379.pid logfile /data/redis/6379/redis.log dir /data/redis/6379 requirepass YourStrongPass123 repl-backlog-size 64mb repl-backlog-ttl 3600 repl-timeout 120 client-output-buffer-limit replica 512mb 128mb 120从库配置文件以 6380 为例6381 只需改端口和目录port 6380 daemonize yes pidfile /var/run/redis_6380.pid logfile /data/redis/6380/redis.log dir /data/redis/6380 replicaof 127.0.0.1 6379 masterauth YourStrongPass123 requirepass YourStrongPass123 replica-read-only yes replica-serve-stale-data yes repl-backlog-size 64mb几个细节值得单独讲。masterauth必须配否则从库连上主库后认证失败master_link_status会一直是 down而日志里只给你一句Unable to AUTH to MASTER不看日志根本猜不到。从库自己也设requirepass是为了防止有人绕过应用直连从库写入虽然只读模式下写不进去但读也是要保护的。repl-timeout我调到 120是因为全量同步传 RDB 时如果数据集有几个 G60 秒很容易不够超时会导致主库主动断开然后无限重试。4.2 启动顺序与状态验证启动顺序严格来说不重要但从库启动时如果主库还没起来它会一直重试并刷日志看着糟心。所以先起主库再起从库。redis-server /etc/redis/6379.conf redis-server /etc/redis/6380.conf redis-server /etc/redis/6381.conf验证分三步走。第一步确认进程都在redis-cli -p 6379 -a YourStrongPass123 ping第二步看主库的复制视图应该能看到两个从库状态是onlineredis-cli -p 6379 -a YourStrongPass123 info replication输出里关注这几行role:master、connected_slaves:2以及每个slaveN后面的ip127.0.0.1,port6380,stateonline,offsetxxx,lag0。state是 online 且lag是 0 或者个位数说明链路健康。第三步看从库视角redis-cli -p 6380 -a YourStrongPass123 info replication关键字段是master_link_status:up、master_last_io_seconds_ago应该是个小数字几秒内、slave_repl_offset和主库的master_repl_offset应该很接近。如果master_link_status是 down先看master_last_io_seconds_ago如果这个数字不涨说明连都没连上问题在握手阶段如果这个数字一直在涨说明连上了但没数据过来问题在同步阶段。然后做个数据验证往主库写一个 key立刻去从库读redis-cli -p 6379 -a YourStrongPass123 set demo:key hello-replication redis-cli -p 6380 -a YourStrongPass123 get demo:key redis-cli -p 6381 -a YourStrongPass123 get demo:key再验证只读往从库写应该报错READONLY You cant write against a read only replica.。这个报错要记住线上如果看到它说明有客户端连错端口了。4.3 故障演练主库挂了怎么手动切这是主从复制里最需要提前练熟的一环因为真出事的时候你只有几分钟窗口。手动切换的步骤是确认主库确实挂了选一个从库提升为主库把其他从库挂到新主库上最后通知客户端改连接地址。第一步模拟主库宕机redis-cli -p 6379 -a YourStrongPass123 shutdown nosave第二步在 6380 上确认主库状态redis-cli -p 6380 -a YourStrongPass123 info replication这时候master_link_status会变成 downmaster_last_io_seconds_ago持续增长。确认之后执行提升redis-cli -p 6380 -a YourStrongPass123 replicaof no one执行完再查info replicationrole应该变成masterconnected_slaves是 0。这一步的依据是replicaof no one会让当前实例停止复制并把自己当作独立主库它会保留已经同步到的全部数据同时生成新的 replid。第三步把 6381 挂到新的 6380 上redis-cli -p 6381 -a YourStrongPass123 replicaof 127.0.0.1 6380这里有个容易踩的坑6381 原来的配置里masterauth是原来的密码如果新主库密码没变直接切就行如果变了得先CONFIG SET masterauth newpass再切否则同样卡在认证上。第四步别忘了持久化。replicaof命令是运行时生效的重启就没了。所以切换完成后一定要CONFIG REWRITE把新配置写到文件里否则一次重启打回原形。注意手动切换期间是有数据丢失窗口的。主库宕机那一刻还没传播到从库的写入永久没了。切完之后最好拿主库最近的 AOF 或者备份对一下看看丢了哪些 key必要的话补数据。这个动作不要省。4.4 读写分离落地与从库只读主从搭好了怎么让读流量真正分出去是另一件事。最土的办法是在客户端代码里维护两套连接池写走主库读走从库按方法名或者注解来区分。这个方案简单直接缺点是业务代码里到处是判断逻辑一旦有人写错读请求打到主库上等于白干。更常见的做法是加一层代理或者在客户端 SDK 里封装路由。不管哪种方案有两个必须解决的问题。第一个是主从延迟。刚写完立刻读从库可能还没收到这就是经典的写后读不一致。解决办法要么是写完之后的一段时间内强制读主库要么是业务上能容忍这种延迟比如商品详情页晚几百毫秒看到新价格一般没事。第二个是延迟监控。从库的INFO replication里有master_last_io_seconds_ago和slave_repl_offset与主库master_repl_offset的差值这两个指标要接进监控。差值持续增大说明从库跟不上这时候如果还往上压读流量只会让它更慢。我的经验是给延迟设个阈值比如超过 5MB 或者超过 3 秒就把这台从库从读池里摘掉等追上了再放回来。这个策略比硬扛着强至少不会让用户看到明显的脏数据。5. 常见问题与排查技巧实录复制出问题的表现就那么几种但原因可能天差地别。这一节把高频问题整理成速查表再补几个特别容易踩的坑。排查的基本思路是先定位在哪个阶段握手、全量、增量、心跳再看这个阶段相关的指标和日志不要一上来就怀疑网络。5.1 高频问题速查表现象最可能的原因排查动作处理方式master_link_status:down且master_last_io_seconds_ago不增长连接没建立认证或网络问题看从库日志有无Unable to AUTH或Connection refused补masterauth检查端口和防火墙master_link_status:down但数字持续增长连上了但同步卡住看主库info clients和缓冲区占用检查client-output-buffer-limit是否踢人反复全量同步日志刷Full resyncbacklog 太小或repl-timeout太短对比master_repl_offset与 backlog 大小调大repl-backlog-size和repl-timeout从库内存比主库大很多从库保留了大量待删除的过期键对比主从dbsize和used_memory正常现象等待主库 DEL 传播主库 CPU 周期性飙高全量同步 fork 抖动看latest_fork_usec指标减少全量频率考虑无盘传输从库 offset 落后持续增大从库处理能力不足或网络带宽打满看从库 CPU 和网卡流量减少该从库读流量或换更强机器主从数据不一致从库被误写或主库发生了未传播的操作抽样比对 key重新全量同步排查误连表里最后一行值得单拎出来说。主从数据不一致在生产上是个很麻烦的事因为它是隐性的可能跑了几个月才被发现。常见成因有三个一是有客户端绕过了路由层直连从库写入只读模式下写不进去但如果谁把replica-read-only改成了 no那就完了二是主库执行了某些不传播的命令比如FLUSHALL在某些老版本的行为差异三是从库手动执行了写操作。防这个的办法是从库永远保持只读所有写操作强制走主库并且在监控里加一条从库收到写命令的告警。5.2 几个特别容易踩的坑第一个坑是用KEYS *或者SCAN在大实例上比对主从数据。这个操作本身就可能导致主库阻塞而且比对出来的差异往往是因为从库的过期键还没被删属于正常现象。真要核对用抽样几个业务 key 的方式别全量扫。第二个坑是主库重启后所有从库同时来要全量。这就是复制风暴几十个从库同时发 PSYNC主库要 fork 几十次或者一次 fork 但传几十份 RDB带宽和 CPU 瞬间打满。缓解手段是开repl-diskless-sync-delay让主库等几秒凑一批更好的办法是做树状复制让从库去喂从库把压力分散。第三个坑是把repl-timeout设得太小。默认 60 秒看着挺宽裕但如果你在做全量同步传一个 20G 的 RDB60 秒根本传不完主库会认为从库超时主动断开然后从库重连再来一次全量无限循环。表现就是日志里反复出现Full resync和Connection with master lost。这种情况要么调大超时要么开无盘传输减少传输时间要么干脆缩小实例的数据量。第四个坑是扩容从库时没考虑主库的 fork 压力。加一个从库不是零成本的它会触发一次全量同步主库要 fork 一次。如果你的主库内存是 64Gfork 一次卡 300 毫秒业务侧的 P99 延迟会立刻体现出来。所以扩容操作要么放在低峰期要么先用一台中间实例做中转别直接往核心主库上挂。提示排查复制问题时INFO replication只是第一层。真正定位还得看INFO stats里的sync_full和sync_partial_ok计数这两个数字能告诉你到底发生了多少次全量和多少次成功的部分重同步。如果sync_full一直在涨那问题就很明确了。6. 生产环境的取舍什么时候该上、什么时候别硬上主从复制是个好东西但它不是万能药。我见过一些团队不管三七二十一先配个一主三从结果主库的写压力一点没减从库闲在那里故障切换还得手动投入产出比很难看。所以在落地之前先想清楚你的瓶颈到底在哪是读扛不住还是怕数据丢还是想要运维窗口。不同诉求对应的方案侧重点完全不同。如果你的瓶颈是读主从是有效的但要配合读写分离和延迟监控否则从库加了也白加。如果你的瓶颈是写主从帮不上忙写永远只有主库一个入口这时候要考虑的是分片或者业务层拆分。如果你只是怕数据丢那一主一从加一个靠谱的持久化配置就够了不用搞一堆从库。如果是要做故障自动切换那主从只是地基上面还得有额外的组件来负责检测和切换。6.1 主从复制的三个硬伤第一个硬伤是写能力无法横向扩展。所有的写都必须经过主库从库再多也只是读的分担。当你的写入 QPS 超过单实例上限时主从复制一点忙都帮不上。这时候要么上分片要么在业务层做拆分没有第三条路。第二个硬伤是异步复制带来的数据丢失窗口。前面说过主库返回成功到从库确认收到之间有个窗口这个窗口关不掉只能缩小。min-replicas-to-write和WAIT命令能让主库等从库确认但它们都会显著增加写延迟而且不能保证绝对不丢因为从库确认了也可能在提升为主之前挂掉。所以如果你的业务真的不能丢数据那 Redis 本身就不是合适的存储别硬用它扛。第三个硬伤是故障切换不是它的事。主库挂了从库不会自动顶上客户端也不会自动找到新主。这套东西需要额外的机制来做而任何额外的机制都意味着额外的复杂度和新的故障点。手动切换虽然土但在很多中小规模场景下反而是最可控的选择——至少你知道自己在干什么。6.2 参数调优与容量规划心得最后分享几条我在实际调优里攒下来的经验都是踩过坑之后总结的文档上不一定写。第一条从库的maxmemory一定要配得比主库大。因为从库会保留一些主库已经删掉但删除命令还没传播过来的键实际内存占用可能比主库高出 10% 到 20%。如果主从配一样大从库很容易先触发淘汰或者 OOM然后同步就断了接着就是无限全量。我一般给从库留 1.3 倍的空间。第二条repl-backlog-size按写入速率算别拍脑袋。前面给了公式这里再补一句如果你的实例有明显的写入高峰比如每天上午十点那就按高峰值算别用平均值。宁可多占几十兆内存也别在高峰期让从库掉队。第三条给复制单独做监控。除了前面说的 offset 差值和sync_full计数还要监控master_link_status、master_last_io_seconds_ago、latest_fork_usec这三个指标。latest_fork_usec特别有用它能直接告诉你上次 fork 卡了多久超过 500 毫秒就该警惕了。第四条别在主库上跑重活。BGSAVE、BGREWRITEAOF、全量同步的 fork这三件事都会抢主库的资源。如果能用从库做备份就别在主库上做如果必须做就错开时间别让它们撞在一起。第五条定期做切换演练。手动切换的步骤看着简单但真到故障现场手忙脚乱的时候很容易漏掉CONFIG REWRITE或者忘了通知客户端。每隔一个季度演练一次把流程写成脚本或者文档让团队里至少两个人能独立完成。这件事的价值不在于技术难度而在于把肌肉记忆练出来。踩过几次坑之后我越来越觉得主从复制这套机制的复杂度不在于它本身而在于它处在一个异步、分布式的环境里任何一个环节的抖动都会传导到最终的数据一致性上。所以别指望配完就一劳永逸把它当成一个需要持续观察和调整的系统该看的指标看住该调的参数调对剩下的就是接受它的边界在边界内把事做好。