资讯动态

Pulsar跨地域复制深度解析:架构、配置与容灾切换

发布时间:2026/10/22 23:42:42 来源:尧图企业网站定制
Pulsar 的跨地域复制Geo Replication是我见过少有的、把“多机房容灾”直接做成原生能力的消息中间件功能但它也是被误解最多的功能之一。很多人看到“复制”两个字就下意识以为跟数据库主从同步一样主库写完从库立刻就有还有人以为两个集群可以当成一个集群随便读写。这些理解一旦落到生产环境都会被现实教育一遍。这篇文章我会用 8 张图拆解的方式把 Pulsar 跨地域复制的整体架构、消息流转链路、配置方法、消费语义、故障切换和排障经验完整过一遍。不管你是正在选型还是已经在生产上维护多套 Pulsar 集群应该都能在里面找到点能直接用的东西。下面没有贴现成的架构截图但我会把每张图需要哪些框、哪些箭头、关注哪些指标讲清楚你顺着思路在脑子里把图画出来理解效果比直接看图更深。1. 跨地域复制到底复制了什么先搞懂整体架构1.1 第1张图两个独立集群之间的复制模型Pulsar 的跨地域复制本质上是“两个独立集群之间的异步数据流复制”不是共享存储。所谓独立集群意味着每个集群都有自己的一套 broker、BookKeeper 存储节点和元数据服务地域A的数据跟地域B的数据是分开保存的两个机房之间没有存储层面的直接关联。我最早接触这个功能是在一个需要把订单事件同时提供给两个机房消费者的项目里。当时我画的第1张图里就三条主线左边是地域A的 producer 写入地域A集群中间是地域A集群 broker 内部跑着的复制器右边是复制器把消息写到地域B集群然后地域B的 consumer 从本地读取消息。这第1张图里最关键的一点是两个集群的存储层不共享。复制不是让B集群“指到”A集群的数据文件上而是让B集群真正存一份几乎一样的数据。这样做的好处是网络抖动不会直接影响存储层读写坏处就是复制一定会有延迟而且每条消息在两个集群分别写两遍写放大是实打实的。你还要记住复制器不是独立部署的进程而是跑在 broker 进程内部的组件。它本质上是“一个内部消费者加一个内部生产者”的组合从本集群的 topic 读取消息再以生产者的身份把消息发送到远端集群的同一个 topic。架构上少了一个单独组件部署和运维都省了不少事。1.2 它不是“消息同步”对一致性要有清醒认知Pulsar 的跨地域复制是异步的这一点再怎么强调都不为过。生产者在A集群发送一条消息broker 返回 ack只代表这条消息已经在A集群的 BookKeeper 上持久化成功了不代表B集群也已经收到了。B集群的数据是A集群后台慢慢复制过去的。这就像你在发件城市的快递点寄了个包裹快递点给你回执只说明包裹已经在当地揽收系统里了收件城市那边还没影。中间要经历装车、干线运输、派送、签收每一步都可能排队。放到跨地域复制上就是本地写入、复制器读取、网络传输、远端写入确认这四个环节串起来。所以如果你在业务上要求“两地都确认写入成功才算发送成功”那 Pulsar 的跨地域复制不适合你得自己做双确认或者换用跨地域强同步的存储方案。Pulsar 的跨地域复制目标是容灾、就近消费、异步分发不是一个跨地域强一致的消息存储。把期望值放在这里后面所有设计都不会跑偏。1.3 第2张图什么时候该上跨地域复制第2张图是场景图我通常画四个框容灾主备、双活近读、边缘汇聚、集群迁移。容灾主备最典型正常情况下只有地域A在生产地域B通过复制保留一份完整数据。地域A的机房真出了故障把客户端切到地域B业务还能继续。备集群平时可以承担只读的流量比如数据分析、离线任务别让它完全空转。双活近读则是两个地域都可以读写但要注意“双活”不代表“双写同一个 topic”。Pulsar 复制是异步的如果两个地域同时写同一个 topic又没有业务层做冲突处理就会产生混乱。我见过比较稳的双活架构都是按业务模块切分比如订单模块的地域A写、库存模块的地域B写两边各自从对方复制数据做本地消费。边缘汇聚场景更适合物联网或者多分支架构每个边缘集群只跟中心集群复制边缘之间不直接互复。消息从边缘汇到中心由中心统一分发或者存储。集群迁移也是很多人忽略的用法新老集群同时跑一段时间复制策略让新集群把老数据全部拉过去拉平之后切换客户端比导数据要平滑得多。2. 消息在两个集群之间流转完整链路与延迟构成2.1 第3张图从本地 Ledger 到远端 Topic 的复制链路第3张图是链路图画出消息从进A集群到出现在B集群之间的完整路径。这条路径分成四段。第一段是生产者写入A集群。producer 连接地域A broker把消息写入本地的 BookKeeper ledger本地副本落盘完成生产者收到确认。这段是所有消息都必须走的正常路径不管后面要不要跨地域复制它都要先持久化。第二段是复制器读取。地域A broker 上会因为 namespace 配置了复制策略自动为需要复制的 topic 启动复制器。复制器从本地 topic 读取消息并且维护自己的复制游标记录“我已经复制到哪条消息了”。这段读取实际上是走内部消费者逻辑所以本地订阅的积压、消费位点这些机制都会用到。第三段是跨地域传输。复制器连接地域B broker以生产者的身份把消息发过去。如果远端不可达或者远端写入失败消息会继续停留在本地复制器的游标也不推进。表现就是复制积压越来越大但上游业务完全感受不到因为producer 早就拿到 ack 了。第四段是远端写入。地域B broker 收到复制过来的消息写入地域B的 BookKeeper ledger落盘失败会重试成功之后复制器才更新游标继续读下一条。这个链路里最容易忽略的是“复制游标”这个东西。它决定了复制从哪里继续也决定了延迟是多少。想看复制积压最直接的方式就是查 topic 统计信息pulsar-admin topics stats persistent://tenant/ns/order-events输出里能找到类似replicationBacklog的字段它表示还有多少消息等着复制到远端集群。如果这个值长期不为零或者一直涨复制链路肯定有问题。2.2 第4张图复制延迟从哪里来第4张图是延迟拆解图。跨地域复制的端到端延迟可以近似看成四个部分相加本地写确认延迟、复制器批次延迟、网络往返 RTT、远端写确认延迟。本地写确认延迟取决于 BookKeeper 的 fsync 策略和副本数通常几毫秒到几十毫秒。复制器批次延迟取决于 Pulsar broker 上复制相关参数的批处理设置它是攒一批消息再发不是来一条发一条所以吞吐高了延迟自然会被拉高。网络 RTT 是物理距离决定的跨机房的 RTT 几十毫秒很常见极端情况可以到几百毫秒。远端写确认延迟跟本地类似也要过一遍 BookKeeper 持久化。把这四个加起来你会明白跨地域复制的延迟在一个正常环境里会达到秒级甚至分钟级别再拿它跟同机房的 topic 读写延迟做对比。第4张图画出来以后我建议你把“复制延迟高”的排障思路跟着图走先看本地写入慢不慢再看复制积压涨不涨再看网络 RTT最后看远端写入负载。按环节切分比瞎调参数有用得多。还有一个参数容易踩坑就是 broker 配置里的replicationStartAt。它决定复制器启动时从哪个位置开始读可以理解为“从最早开始补数据”还是“从最新开始只复制新消息”。如果你在存量数据已经写了很多之后才打开复制并且它配置的是从最新开始那么历史消息一条都不会复制到远端。这一点经常被当成“复制不生效”来排查实际是消费位点没选对。2.3 复制消息头上的交通标记Pulsar 在把消息复制到远端集群时会给消息加一个标记常见的是replicatedFrom之类的属性用来标识这条消息是从哪个集群复制过来的。消费者在某个集群上读到消息时如果看到这个属性有值就知道它是远端复制过来的而不是本地生产的。这个标记有两层用处。第一层是排查数据来源。双活或者边缘汇聚场景里一个 topic 里的消息可能一部分是本地生产的一部分是别的集群复制来的。消费端把消息属性打出来一眼就能判断消息到底来自哪里定位问题会快很多。第二层是防止循环复制。在全互联拓扑下A集群复制到B集群B集群可能又复制回A集群如果没有防循环机制消息就会在两个集群之间无限转发。Pulsar 的做法就是靠replicatedFrom标记B集群发现这条消息本身就是从A集群来的就不会再往A集群回传。了解这个标记你就不会在配置全互联拓扑的时候担心数据风暴。3. 复制策略配置与拓扑规划命令、参数、取舍3.1 第5张图Namespace 级别的复制策略配置流程第5张图是配置流程图。Pulsar 的跨地域复制是 namespace 级别的不是 topic 级别的。也就是说你给一个 namespace 配置了复制集群列表这个 namespace 下面所有 topic 都会按照这个列表复制。这个设计有好处也有坑好处是批量配置、管理简单坑是你没法直接说“只复制这几个 topic”。配置步骤基本就三步。第一步确认两个集群在彼此的集群列表里能互相访问。第二步给 namespace 设置复制集群列表pulsar-admin namespaces set-clusters \ --clusters cluster-a,cluster-b \ public/order-ns第三步查看策略确认是否生效pulsar-admin namespaces get-policies public/order-ns输出里会看到replication_clusters字段里面应该包含cluster-a和cluster-b。这里有个细节要注意--clusters参数列出的集群集合里包括本地集群自己。比如cluster-a的 broker 在执行这个命令时列表里要同时有cluster-a和cluster-b这个 namespace 的消息才会被复制到cluster-b。如果你图省事只写了目标集群很可能复制策略是残缺的。策略改变之后broker 上生效并不是毫秒级的事它有一个轮询检查策略变更的机制。所以执行完命令等个十几秒再看复制是否启动都正常别一秒钟没动静就开始重启 broker。如果业务上确实只需要复制某些 topic我的建议是不要跟别人共用 namespace单独开一个 namespace 给这批需要跨地域复制的 topic。配置策略和后续维护都会清爽很多。3.2 三种复制拓扑的选型与配置细节第5张图的下半部分其实是三种常见的复制拓扑。主备拓扑 namespace 的复制集群列表包含主备两个集群但只有主集群接收生产写入备集群平时只读。配置最简单故障切换时人工介入做客户端切换。风险点是备集群平时没有真实流量一旦切换可能暴露消费代码没验证过的问题。全互联拓扑所有集群两两复制任何一个集群都可以被生产写入。配置上就是在每个集群都设置包含所有集群的复制列表。这种拓扑要特别注意业务层写冲突我的经验是必须按 Topic 或者按业务模块分配“生产归属地”不要让两个地域同时写同一个业务 topic。星型拓扑中心集群与每一个边缘集群复制边缘集群之间不建立复制关系。比如边缘集群配置的复制列表是“边缘集群中心集群”中心集群配置的复制列表是“中心集群所有边缘集群”。边缘的消息会汇总到中心中心的消息会分发到各个边缘。这种结构适合中心化分发和监管场景但中心集群会成为网络和存储瓶颈容量规划要提前做。三种拓扑没有绝对好坏关键看业务对数据流向的要求。选型时用一张小表格把约束条件列出来谁能写、谁能读、谁能容忍延迟、谁能容忍重复比直接照搬网络上的架构图要靠谱得多。3.3 配置粒度与团队协作的取舍复制策略是 namespace 级别的这在多团队共用一个集群的环境里很容易出问题。比如 A 业务只需要复制到备集群B 业务不想复制但 A 和 B 共用一个 namespace。只要负责 A 业务的人执行了set-clustersB 业务的 topic 也会跟着复制数据被复制到不期望的机房合规和成本都受影响。我经手的一个项目里出现过一次“复制策略被误伤”的事故。当时一个运维执行命令时把整个public/defaultnamespace 加进了跨地域复制集群列表结果几乎所有共享这个 namespace 的 topic 都开始往备集群同步。后续所有人的消费端都看到了大量历史消息重复消费光是清理就折腾了很长时间。从那以后我们定的规矩是每个业务线至少一个独立 namespace复制策略必须跟着 namespace 走变更复制策略要走审批。另外老集群上的旧 topic 如果还留在 default namespace 里尽量迁走。跨地域复制并不是“配一下就完事”的静态配置它需要长期维护和治理。4. 跨地域消费订阅状态、复制订阅模式与重复消息4.1 默认情况下订阅游标是各集群独立的配置好复制策略之后消息确实能从A集群到B集群但消费端的行为很多人不了解。默认情况下Pulsar 每个集群上的订阅游标是独立的。A集群的消费者订阅了某个 topic消费到第 100 条B集群上同样的消费组名可能根本不存在或者就算存在游标也是从自己的初始位点开始算的。A和B之间订阅状态并不会自动同步。这个特性带来的直接影响就是故障切换。假设A集群挂了你把消费者切到B集群如果B集群上这个订阅名之前没创建过消费者创建新订阅时只能按照 broker 的默认位点开始读。默认是 earliest 就可能从头消费大量历史数据默认是 latest 就可能跳过A集群已经复制过来但还没消费的尾部消息。这两种结果要么重复量巨大要么丢数据感知上吓人。所以只配置复制策略不解决消费端切换的连续性。要让切换更平滑你得了解并配置 replicated subscription也就是复制订阅。4.2 第6张图Replicated Subscription 两种模式怎么选复制订阅解决的是“订阅游标在跨地域之间怎么同步”的问题。第6张图里可以画两个分支左边是 Serialized 模式右边是 Independent 模式我用下来之后的体会差别很大。Serialized 模式的目标是“一个订阅名全局只有一份消费位置”。它通过跨集群协调保证同一个订阅同一时刻只能在一个集群上拉取消息其他集群上的消费者会等待。故障切换时新接管的集群能接着原游标消费重复范围控制得很小。代价是消息流转被全局串行化还有一个消息在途之类的限制跨地域网络一抖动消费吞吐和延迟立刻受影响。除非业务对重复极其敏感我一般不推荐一上来就开这个模式。Independent 模式则是每个集群各自维护消费游标互不协调。配置和运维都要简单很多消息重复的可能性更大但至少不会因为跨集群协调导致消费卡死。第6张图右边我会画一个“幂等层”的框意思是用 Independent 模式业务端必须自己做好幂等。配置复制订阅时不同版本的 Pulsar 在 namespace 策略里的字段略有差异字段位置和命名以你当前版本的官方文档为准。我的建议是生产环境优先从 Independent 模式起步等确实遇到重复消息造成的资损问题再评估是否升级到 Serialized。不要为了追求“看起来最精确的交付”给自己上一把跨地域锁。4.3 正确理解重复与丢失的边界跨地域复制场景下消息重复是常态不是 Bug。这句话写出来可能让人觉得我在为系统缺陷开脱但从原理上确实如此。从设计目标看Pulsar 跨地域复制不会丢消息生产者在源集群收到 ack说明消息已经持久化复制器之后会把它读到并推到远端。如果复制器在推进游标之前崩溃它重启后会把那条消息再读一遍、再推一遍。所以源集群成功写入的逻辑传到远端时保守设计一定是“可能重复一次”。消费者要做的是在消息处理链路里加上业务幂等。比如用订单 ID、消息 ID 做去重表或者保证处理操作本身就是幂等的“设置状态为已支付”比“余额加 10 元”更利于重复消费。如果你把分布式事务的可靠消息和跨地域复制叠加在一起幂等的重要性会被进一步放大。真要讨论极端丢消息只有一种情况源集群消息已经写入成功但在复制到远端之前整个源集群发生了不可恢复的数据损坏比如多副本全部丢失。这是任何异步容灾方案都逃不掉的理论边界。你需要做的是在项目评审时把这个边界告诉业务方而不是等故障发生之后再去解释。5. 故障切换与灾难恢复不是简单改个连接地址5.1 第7张图切换流程检查清单第7张图我画的是故障切换流程图每个步骤都踩过坑所以拿出来单独讲。第一步先确认故障边界。是有任何消息都写不进去了还是只有某条 topic 写入超时是 broker 进程挂了还是整个机房网络都断了如果只是某台 broker 的问题Pulsar 自己会做负载转移根本不需要切换。确认边界这步没做好后面切换就是一次降低可用性的主动操作。第二步检查复制 backlog 是否追平。用pulsar-admin topics stats看远端复制积压。正常情况下切换前应该等积压消息基本清空可接受的 RPO 是多少要预先跟业务约定。如果源集群已经完全不可用backlog 追不平那就得认账丢失这一部分尾部消息。第三步检查目标集群的订阅是否就绪。我在一次演练里吃过亏当时备集群一切正常但切过去之后消费者拉不到数据最后发现是备集群上压根没建过订阅消费者是从头开始读的把历史消息全部重放了一遍。从那之后备集群的订阅预创建成了切换清单里的必检项。第四步消费端切换。把读取流量慢慢切到备集群观察 consumer 的连接数、消息速率、消费 lag。消费端能正常跑起来再进入下一步不要一上来就同时切生产和消费。第五步生产端切换。让 producer 也指向备集群新消息开始写入备集群。这时候备集群既是生产写入端又是消费读取端同时还有来自旧集群的复制尾巴在赶过来。这一步最需要盯的就是备集群的存储负载和网络带宽。第六步观察与回切。确认业务稳定运行一个周期后再按同样清单反向操作回到原集群或者保持现在的拓扑。5.2 切换前就要做好的运维储备跨地域切换不是故障当下就能全部准备的。很多事情要提前做否则现场根本没有时间补救。首先是 namespace、topic、ACL、租户这些元数据不会自动跟着复制策略从A集群同步到B集群。如果B集群从来没创建过对应的 namespace 和 ACL消费者切过去会发现连权限都没有。提前把配置同步纳入发布流程而不是靠某人做个一次性脚本。其次是认证和授权策略在每个集群要独立维护。Pulsar 的复制策略只负责消息数据不负责权限元数据。备集群的权限必须跟主集群一致或者至少在应急预案里有明确的执行步骤。然后是健康检查。不要只检查 broker 进程是否活着要定期在备集群做真实的消息写入和读取验证。很多备机隐患只有真正让数据流经过才知道。最后是切换演练。这个不能省。我见过太多团队把切换文档写得非常完整但从来没演练过真到故障那天连执行文档的第一步命令都会输错。三个月一次每次把切换清单跑一遍发现问题改文档这是最值得投入的运维成本。5.3 回切的经验教训回切很容易被当成“再来一次切换”实际操作比第一次切换更麻烦因为备集群运行期间攒了新的数据。一个比较稳的回切流程是这样的先让备集群把消费 backlog 全部消化完或者跟业务确认可以跳过这部分的尾部数据然后把消费端先切回主集群观察消费位点是否合理最后再把生产端切回主集群。不要在备集群还有积压的时候就贸然来回切否则消费者会看到消息顺序错乱、重复消费一堆。回切过程中可以使用一个一次性测试订阅来验证主集群的消息可用性。这个测试订阅不会污染正式订阅游标发现问题可以随时删掉重建。验证通过后再把正式订阅的消费端迁回来。还有一条经验回切后不要马上删掉备集群的订阅或复制策略至少保留一个观察周期确认没有逻辑问题再清理。备份一旦提前清理了第二次故障就没有退路了。6. 常见问题速查与监控排障6.1 复制不生效一个排查表解决八成问题我整理了一个排查表覆盖跨地域复制“配了但没反应”的大多数情况。先对号入座再动手改配置。现象常见原因排查与解决新消息不复制namespace 复制列表没包含目标集群执行get-policies看replication_clusters用set-clusters补全所有 topic 都不复制broker 配置了disableReplicationtrue检查 broker.conf改成 false 并滚动重启只复制新消息历史消息没有补replicationStartAt配置为 latest确认业务需求必要时改为 earliest 并触发补复制复制断断续续Backlog 增长跨地域网络不通或带宽打满查看 broker 复制器日志检查网络 RTT 和丢包部分 topic 复制部分不复制策略还没轮到或 namespace 共用导致误配置检查 namespace 粒度确认 topic 属于哪个 namespace遇到复制不生效我建议最先查的是 namespace 策略因为这是配置最常出错的地方。先get-policies看复制集群列表不要一上来就看 broker 日志。6.2 第8张图复制延迟高怎么定位复制延迟高第8张图能帮上大忙。这张图我画了一个监控面板上面有三根核心曲线源集群写入速率、复制速率、目标集群写入速率。定位延迟高的问题先看这三根线谁跟谁不匹配。如果源集群写入速率正常但复制速率明显偏低优先检查复制器所在 broker 的 CPU、内存、网络连接数另外看一下复制相关批次参数是不是太小。如果复制速率和目标集群写入速率都正常但复制 backlog 还是持续上涨大概率是跨地域链路抖动或者目标集群写远端 BookKeeper 的耗时变大。如果 backlog 一会儿涨一会儿掉更像是消息到达不均匀或者批量参数设置导致复制波动。先观察不要急着重启。复制延迟高不一定是故障很多情况下就是跨地域带宽打满了消息在排队。这时候加机器不如先做流量削峰或者压缩消息体积。监控面板上我会额外放两个指标待复制消息数backlog和落后时长估算。Pulsar 的 topic stats 里有 replication backlog 和复制速率直接用这两个值算“追平时间”就够了。想象一下backlog 有 100 万条复制速率每秒 1 万条那追平时间大约 100 秒。用这个估算来回答业务方的“还要多久”比一句“快了”有说服力得多。6.3 消费者重复消费的排查思路跨地域场景下消费者重复消费原因主要分两类一类是生产端的重复复制一类是消费端订阅游标切换造成重放。生产端的重复多见于复制器游标重置。比如复制器读了一条消息已经推到远端但还没更新本地游标这时进程重启它会把这条消息再推一次。这种情况通过复制机制本身没办法完全消除消息端到端必然存在“至少一次”语义业务幂等是唯一出路。消费端的重复则跟切换有关。如果没开复制订阅或者用的 Independent 模式故障切换后消费者在备集群的消费起点可能跟旧集群的游标对不上导致一部分消息被重新拉取。排查时可以对比两个集群上同一个订阅的 LastConsumedTimestamp 或者最近消费位置看看差在哪里。如果业务要求极端稳定建议为每个消费者在消息处理层加上全局去重键。去重键不要用 Pulsar 的 message ID因为复制到远端后 message ID 可能变化要用业务里的唯一字段比如订单号、事件 ID。6.4 定期切换演练比紧急排障更重要我在实际项目里踩过最痛的一次就是演练时发现备集群从未创建过订阅切换后所有消费者从头到尾把历史消息重新拉了一遍线上流量瞬间被打挂。这个问题的根子不在 Pulsar在于我们太久没有做演练所有备集群配置都是纸上谈兵。后来我们定了一条规矩所有启用了跨地域复制的集群每个月做一次只读演练每个季度做一次完整切换演练。只读演练只验证消费者能在备集群拉数据完整切换则是连生产写入一起切过去让业务真实跑一段时间。跨地域复制本身不复杂复杂的是它牵涉的消费端、ACL、网络、订阅策略这些外围环节。每演练一次都会发现一处文档没覆盖到的细节。等到故障真的来了你靠的不是临场反应而是那一叠已经被反复修正过的切换清单。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑