PAYATHON 2026

NATS JetStream 跨 domain sourcing 能否将消费者 ACK 回传源 stream?

数据小许

结论

仅靠 sourcing 配置,无法让 L2A 消费者的 ACK 沿 L2A → HA → L1A 自动回传。ACK 更新的是具体 consumer 的投递状态;确认 L2A 上的消息,不会自动确认 L1A 上某个 consumer 的投递。

如果必须更新 L1A 上 consumer 的确认状态,可以在权限允许时直接消费 L1A。如果需要保留当前的本地消费拓扑,可以增加业务处理回执,单独记录完成状态。NATS 的 source/mirror 用于单向复制消息,不提供上下游消费状态的联动同步。官方 sourcing 设计说明

为什么 ACK 不会逐级回传?

当前消息链路是:

L1 domain          Hub domain          L2 domain
   L1A ──source──> HA ──source──> L2A ──> consumer

L1A、HA 和 L2A 是各自独立的 stream。消费者从 L2A 接收消息后,ACK 更新的是该 consumer 在 L2A 上的确认状态。

复制过程中的流控或确认,即使存在,也只用于描述复制进度,不能代表 L2 上的业务处理已经完成。将 source 换成 mirror 同样不能让业务 ACK 逐级回传。跨账户复制与确认机制

ACK 和删除消息也不是同一操作。ACK 是否会导致消息从所在 stream 中删除,取决于 retention policy。

方案一:在 L2 直接消费 L1A

如果要求是“L2 完成处理后,更新 L1A 上 consumer 的确认状态”,可以让 L2 应用访问 L1 domain 中的 L1A consumer,而不是消费 L2A 的副本。

实施步骤如下:

  1. 在 L1A 上准备使用显式确认的 durable consumer。
  2. 在 L2 应用的 JetStream 客户端中指定 L1 的 domain 或对应 API prefix。
  3. 通过已授权的 leafnode 路由访问该 consumer,处理成功后确认收到的原始投递消息。
  4. 根据跨节点处理时长设置 AckWait,并让业务处理支持幂等,以应对超时重投。

可以继续使用不同的 domains,但指定 domain 不会自动赋予访问权限。相关 API 请求、消息投递和 ACK 的往返都需要获得授权;如果还跨 account,则需要配置相应的 exports/imports。已有 sourcing 能正常工作,也不代表远程消费所需的权限已经齐全。远程 consumer 所需 subjects

该方案依赖到 L1 的连接。上游断开时,应用无法继续依靠 L2A 的本地副本消费。这个方案也可能不符合当前安全边界,应以已有授权范围为前提。

方案二:保留 sourcing,增加持久化业务回执

如果 L2 必须消费本地 L2A,可以新增回执链路,将“业务处理完成”作为独立消息传回 L1。

建议流程是:

  1. 发布原始消息时,在消息体中携带稳定的业务标识,例如 event_id,并确保复制过程中保留该字段。
  2. L2 完成业务处理后,将成功回执写入专用 JetStream stream,等待发布确认。
  3. 回执确认入库后,再 ACK L2A 的消息。
  4. 通过单独配置的反向 sourcing 链路将回执传到 L1,由 L1 的回执处理服务更新业务状态。

以下是应用自定义的回执格式,不是 NATS 内置协议:

{
  "event_id": "evt-123",
  "origin_stream": "L1A",
  "processor": "L2",
  "status": "processed"
}

处理顺序可以用以下伪代码表达,其中函数均需由应用实现:

message = receive_from_L2A()
event_id = read_event_id(message)

process_business_idempotently(event_id, message)
persist_completion_receipt_and_wait_for_puback(event_id)
ack_L2A_message(message)

回执链路需要单独设置 subjects、stream 和权限,并覆盖断线期间的持久化、恢复与重试。回执在 L2 入库只说明本地保存成功;L1 收到并处理回执后,才能据此认定业务完成。

业务成功后,进程可能在发送回执前退出,也可能在回执入库后、ACK 前退出。因此,业务处理和回执处理都需要幂等。若业务状态保存在数据库中,可以考虑用事务性 outbox 保存待发送回执。

实现时还需要注意什么?

  • 不要改写 ACK subject 来模拟回传。L2A 的投递没有天然对应的 L1A consumer 投递上下文,不能通过替换 stream 名或 domain 拼出有效的上游确认。
  • 不要假设各层 stream sequence 相同。Sourcing 会为目标 stream 分配序号,跨层关联应使用稳定的业务标识。source 与 mirror 的序号区别
  • 收到回执不等于执行了 L1A consumer ACK。L1 可以据此记录完成状态;如果需要清理源消息,应另外设计可审计的清理逻辑,并准确定位原消息。
  • 明确源消息的保留目标。LimitsPolicy 不会因为 consumer ACK 就删除消息;WorkQueuePolicy 和 InterestPolicy 有各自的删除条件。原问题没有提供 retention policy,不能直接断言 ACK 后 L1A 应当删除消息。官方 retention policy 说明

备注:内容仅供参考。