本节摘要:Pulsar 的物理形态是四层:客户端、Broker、BookKeeper、元数据存储,外加可选的 Proxy 门卫。本节从一次真实故障切入,倒推每一层存在的必要性,给全章立一张总图——控制面与数据面分家、计算与存储分家,这两个"分家"是读懂后续所有机制的钥匙。
凌晨两点,值班群弹出告警:某台 Broker 进程失联。换在一个"Broker 既管路由又存数据"的系统里,这是一场事故——它名下分区的数据此刻都在失联的磁盘上,恢复要等数据复制,期间消息堆积、下游断粮。但在 Pulsar 集群里,值班同学的处理是喝完这杯水再看一眼:失联 Broker 名下的主题所有权会在几秒内被其他 Broker 自动接管,消息生产恢复,堆积的下游慢慢追上。字节没有丢,因为字节根本不在这台 Broker 上。
这个场景能帮你一次记住四层分工:客户端层负责协议封装与收发;Broker 层是无状态的调度台,只持有"哪个主题归我管"的认领信息与缓存;BookKeeper 层(由多台 Bookie 组成)是真正的货仓,消息字节以账本形式摊在这里;元数据存储层(早期用 ZooKeeper,新版本已有替代方案)记着全集群的名册——主题归属、命名空间策略、Bookie 存活名单。Proxy 则是可选的第五种角色,站在集群门口统一收客。

客户端把"发一条消息""订阅一个主题"翻译成 Pulsar 的二进制协议,处理连接管理、重试与重连、消息去重的序列号维护。设计上有个关键决定:客户端不感知 Bookie,只跟 Broker 说话。这保证了存储层可以整体升级、搬迁而客户端无感;跨语言客户端(Java、Python、Go、C++)因此行为一致,第 5 章会直接在代码里用到这一点。
Broker 是请求的唯一入口,做三件事:认领主题(谁服务订单主题,查名册说了算)、路由与负载均衡(把主题分摊到集群各台 Broker)、缓存加速(刚写入的条目留在内存里,供追进度的消费者快速读取)。注意它不做的事:不长期持有数据。这让 Broker 层可以像无状态 Web 服务一样横向伸缩——第 2.2 节专门拆这层。
Bookie 不理解"主题""订阅"这些业务概念,它只认识账本(Ledger)与条目(Entry):你给我一段字节和一个账本号,我按法定人数规则把它写到多个盘上,并保证写入顺序严格一致。正因为这层"不懂业务",它才能做到极致简单的读写模型——顺序追加、随机读优化、副本策略全部由账本协议统一裁决。第 2.3 节与第 4 章都会回到这里。
名册官记三类事:集群拓扑(哪些 Broker、哪些 Bookie 活着)、主题归属(每个主题当前由哪台 Broker 服务)、策略配置(命名空间的保留、配额、权限)。它的数据量很小但地位极高——名册官失联,集群就失忆。所以生产部署至少三节点部署它,且绝不与数据盘混用。早期实现绑定 ZooKeeper,社区后来的版本把元数据服务抽象成了可插拔接口,运维上多了一种不依赖 ZK 的选择,但"名册"这一角色本身不变。
⚠️ 常见坑:把元数据存储当成"配置文件"对待,部署时与 Bookie 共用机器与磁盘。名册官对延迟极敏感,一旦被数据流量挤压,全集群的主题切换、Bookie 探活都会迟滞,故障时雪上加霜。生产铁律:控制面与数据面物理隔离。
架构图讲完,补一段部署视角,把"层"翻译成"机器"。物理机上跑的是进程,进程承担角色:Broker 进程、Bookie 进程、元数据存储进程、可选的 Proxy 进程。开发机上四角色合一;生产机上角色分离是铁律——不是因为它们不能合,而是因为合住之后,磁盘归谁、内存归谁、故障时谁背锅全变糊涂账。角色分离的另一层收益是安全边界清晰:对外暴露的只有 Broker 与 Proxy 端口,Bookie 与元数据节点可以完全藏在内网,攻击面小一圈。
理解了角色,再看"层"就有了物理对应:客户端层是别人的机器上的进程;Broker 层是计算节点池;BookKeeper 层是存储节点池;元数据层是小而稳的仲裁节点池。四层各自的机器规格也顺着职责走——Broker 吃 CPU 与网卡,Bookie 吃磁盘与顺序写带宽,元数据节点吃低延迟盘与稳定性。这份对应关系直接服务第 6 章的容量规划,此处先立住概念。
把四层串成一次具体的行程,读图会变成读路。生产者调用 send:客户端层把消息按二进制协议打包,连向 Broker 的服务端口;Broker 收到后先查本地缓存的认领表——这个主题归不归我?归,就继续;不归,返回重定向让客户端改连负责的节点。认领无误后,Broker 把消息组装成 Entry,向该账本的承载组发起写入;几台 Bookie 的预写日志落盘、达到确认份数后,Broker 回执客户端,send 返回。与此同时,消息副本已经躺在多台机器的账本里,Broker 的读缓存也留了一份,等订阅方来取。
行程里值得驻足的细节有好多处。重定向机制让"找谁问"这件事客户端自动完成,业务代码从不需要写负载均衡逻辑;确认份数的判定发生在 Bookie 侧,Broker 只等结果,所以 Broker 的内存里从不滞留"必须由我保管"的数据;回执里携带的 MessageId 是下游一切操作(精确重投、Reader 起点、幂等判重)的通用坐标。这条行程在后面每一章都会换个角度重走——第 3 章走它的语义面(确认与重投),第 4 章走它的存储面(条带与副本),第 5 章走它的代码面(参数与回调)。此处把骨架立起来,后面三章只是给同一段路换上不同的导游词。
顺带交代 Proxy 的出场时机。单集群内部直连 Broker 即可,但两种情况门卫有用:客户端来源分散且不允许直连内网全部节点(统一出口收敛连接);跨地域访问需要就近接入点(Proxy 部署在远端机房,转发回源集群)。Proxy 是转发者不是存储者,它宕了只影响新连接,重连即恢复——与 Broker 的无状态一脉相承。
下一站蹲守调度台:Broker 无状态的"无"字到底意味着什么,主题归属如何在集群里自动均衡。