本节摘要:Pulsar 里每条消息进门先登记三级户籍:租户代表"哪家组织",命名空间代表"组织的哪块业务",主题代表"具体的漂流河道"。本节讲清三级结构各自管什么、为什么非要三层,并带你用命令行把订单事件的户籍真正建出来,最后看看一个 Java 生产者是如何拼出完整主题地址的。
Pulsar 是从多团队共用一个集群的真实需求里长出来的,所以它的第一身份不是"更快的水道",而是"能被许多组织安全共享的水道"。共享的前提是权责分明,于是有了三级户籍:
**租户(tenant)**是最高一级,代表资源的归属组织。它可以是一家公司的某个事业部,也可以是一个独立的业务线。配额、权限、集群可达范围,都可以按租户划线。你可以把租户理解为"在同一个集群里各自开伙的伙食团"——锅是同一口,菜单与账本各管各的。
**命名空间(namespace)**是租户内的管理单元,把一组相关主题圈在一起统一治理。保留策略、TTL、副本放置、配额,都以命名空间为最小管理粒度。一个订单租户里,可以分出"交易域""履约域""风控域"等命名空间,每个域根据自己的流量形状设定不同的策略。
**主题(topic)**才是消息真正漂流的河道,命名由命名空间加一个自定义名称构成。主题还分持久化与非持久化、分区与非分区(第 3 章细讲),本章先把户籍关系立住:任何主题的完整名称都长得像下面这样——
persistent://trade-order/transaction/order-events └────────┘ └──────┬─────┘└─────┬──────┘└────┬─────┘ 存储类型 租户名 命名空间名 主题名
三级结构不是官僚设计,而是把"谁的资源、谁的家规、哪条河道"分层拆开:租户回答归属,命名空间承载策略,主题承载流量。少了任何一层,共享集群都会退化成弱肉强食的公共牧场。
口说无凭,我们用 Pulsar 自带的管理命令把"订单事件流"的户籍建出来。假设本机已启动 Pulsar 单机模式,下面的会话可以在终端里原样复现:
# 创建租户:允许在本机集群上活动,管理员角色为 admin $ pulsar-admin tenants create trade-order \ --admin-roles admin \ --allowed-clusters standalone # 创建命名空间:挂在 trade-order 租户下,名为 transaction $ pulsar-admin namespaces create trade-order/transaction # 创建持久化主题:订单事件流 $ pulsar-admin topics create persistent://trade-order/transaction/order-events # 验证:列出该命名空间下的全部主题 $ pulsar-admin topics list trade-order/transaction # 输出应包含: # "persistent://trade-order/transaction/order-events"
三条命令对应三级户籍,顺序不能颠倒:租户不存在时建命名空间会直接报错,命名空间不存在时建主题同样报错。这套校验在命令行里体现为报错信息,在生产集群里则体现为权限体系——没有租户授权的角色,连建命名空间的资格都没有。
⚠️ 常见坑:初学者最容易在主题全称上栽跟头。Pulsar 的主题名必须以存储类型开头(persistent 或 non-persistent),客户端代码里少写这一段,有的 SDK 会自动补全,有的直接抛参数异常——排查半天,问题往往只是地址少了个前缀。
户籍建好,就可以从代码里发消息了。下面这段 Java 代码可以在工程里直接跑起来(Maven 引入 Pulsar 客户端依赖后即可编译运行):
PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar://localhost:6650") // Broker 的服务地址 .build(); Producer<byte[]> producer = client.newProducer() // 完整主题地址:存储类型 + 租户 + 命名空间 + 主题名 .topic("persistent://trade-order/transaction/order-events") .create(); MessageId msgId = producer.newMessage() .key("order-1024") // 业务键,后续顺序与分区路由都靠它 .value("{\"orderId\":\"1024\",\"amount\":19900,\"status\":\"CREATED\"}") .send(); // 同步发送,阻塞直到 Broker 确认 System.out.println("消息已确认,MessageId = " + msgId); producer.close(); client.close();
运行后控制台会打印类似 消息已确认,MessageId = trade-order/transaction/order-events/ledger-5/entry-0 的输出。这个 MessageId 本身就是户籍制度的活证据:它由主题、账本号、条目号组成,意味着这条消息已经被登记进某个具体的账本位置——至于账本是什么、写在哪块盘上,正是第 2 章与第 4 章要拆的内容。
把服务地址、租户、命名空间、主题四样东西连起来看,你会发现 Pulsar 的寻址逻辑非常直白:客户端只认地址,Broker 只管查表。哪台 Broker 为哪个主题服务,是运行时分配的元数据,客户端完全不感知——这个"客户端不关心落在哪台机器"的特性,就是第 1.1 节说的存算分离在寻址层的投影。
| 层级 | 回答的问题 | 典型管理动作 |
|---|---|---|
| 租户 | 资源归谁 | 分配管理员角色、划定可用集群 |
| 命名空间 | 按什么家规治理 | 设保留策略、TTL、配额、副本放置策略 |
| 主题 | 消息漂在哪条河道 | 创建分区、查看游标与堆积、收发消息 |
用订单事件流的话说:trade-order 租户声明"这是交易团队的地盘",transaction 命名空间规定"交易域的保留策略与配额",order-events 主题则是"订单已创建"这类事件真正流淌的河道。下游的风控、物流各自再开主题订阅,互不干扰——这套多团队共享的秩序,正是 Pulsar 区别于许多前辈消息系统的第一特征。
下一站把 Pulsar 拉去和 Kafka、RabbitMQ 同台比一比:同样一条订单事件,交给三代水道各会发生什么。