PAYATHON 2026

NServiceBus Automatic subscription 不工作

架构老王

结论

自动订阅没有触发时,先检查 NServiceBus 是否将 OrderAccepted 识别为“事件”。自动订阅只对事件生效。仅实现 IMessage 的类型,在不同版本的 NServiceBus 中未必会被识别为可自动订阅的事件。

还要检查 MessageEndpointMappings 中的 Messages。它通常匹配的是消息契约的程序集名称,不只是 C# 命名空间。

发布者队列中没有残留的 subscription request,也不等于订阅失败。请求可能已经被快速处理,而且某些传输使用原生发布/订阅机制,不会产生这种控制消息。

将消息明确声明为事件

如果当前版本提供 IEvent,可以把契约改为:

namespace BusStop.Sales.Contracts
{
    public class OrderAccepted : IEvent
    {
    }
}

处理器保持不变:

namespace BusStop.Billing
{
    public class OrderAcceptedHandler :
        IHandleMessages<OrderAccepted>
    {
        public void Handle(OrderAccepted message)
        {
            // Handle the event
        }
    }
}

较新的 NServiceBus 版本也可以通过消息约定,把普通 CLR 类型识别为事件。不过,直接实现 IEvent 更便于排除配置问题。

如果使用的是只支持 IMessage 的旧版本,就需要查看该版本的消息分类规则,不能直接照搬新版本文档中的 IEvent 配置。

检查 endpoint mapping

Messages 配置项必须能定位包含 OrderAccepted 的消息程序集。假设契约项目生成的程序集是:

BusStop.Sales.Contracts.dll

配置可以写成:

<UnicastBusConfig>
  <MessageEndpointMappings>
    <add Messages="BusStop.Sales.Contracts"
         Endpoint="BusStop.Backend" />
  </MessageEndpointMappings>
</UnicastBusConfig>

这里的 BusStop.Sales.Contracts 指程序集名称。它可能正好与命名空间相同,但两者不是同一个概念。应检查契约项目的 assembly name,而不能只看:

namespace BusStop.Sales.Contracts

部分旧版本支持按类型或命名空间进一步限定,但具体语法会随版本变化。排查时,可以先使用明确的契约程序集映射。

确认配置文件确实被 Host 加载

使用 NServiceBus.Host.exe 时,很容易改错配置文件。实际生效的文件通常必须放在 Host 可执行文件旁边,并采用 Host 要求的文件名,例如:

NServiceBus.Host.exe.config

还要逐项确认:

  • 配置文件已经复制到实际启动目录。
  • 当前启动的不是另一个目录中的 NServiceBus.Host.exe。
  • XML 节点位于正确的配置层级。
  • 构建或部署过程没有用另一份配置覆盖该文件。
  • BusStop.Sales.Contracts.dll 位于 endpoint 的运行目录,并且能够正常加载。
  • 包含 OrderAcceptedHandler 的程序集也在扫描范围内。

如果 NServiceBus 没有扫描到处理器程序集,就无法发现该 handler,也不会为对应事件建立自动订阅。

不要根据队列中是否留有请求来判断

在使用消息驱动订阅的旧版 MSMQ 模型中,订阅请求会发送到发布者 endpoint,并可能马上被消费。因此,启动完成后再查看 busstop.backend 队列,不一定还能找到该消息。

RabbitMQ、Azure Service Bus 等支持原生发布/订阅的传输,则可能通过 broker 的 topology、exchange、topic 或 subscription 完成订阅,不会向发布者输入队列发送传统的 subscription request。

可以采用以下方法检查:

  1. 暂时将 NServiceBus 日志级别调到 Debug。
  2. 查看启动日志中是否发现了 OrderAcceptedHandler。
  3. 确认 OrderAccepted 被分类为 event、command 还是普通 message。
  4. 确认 endpoint mapping 是否解析到 BusStop.Backend。
  5. 检查发布者队列、错误队列和订阅存储。
  6. 实际发布一个 OrderAccepted,确认 subscriber 能否收到。

日志配置方式取决于 NServiceBus 的大版本和使用的日志框架,应采用与当前项目版本对应的配置方法。

还需要确认发布方式

发布者必须发布事件,而不是发送命令。例如:

bus.Publish(new OrderAccepted());

不能用下面的方式验证发布/订阅:

bus.Send(new OrderAccepted());

Send 是点对点发送,目标由路由决定。Publish 才会把事件交给已经订阅的 endpoint。

建议的排查顺序

可以按以下顺序排查:

  1. 将 OrderAccepted 明确改为实现 IEvent。
  2. 确认 Messages 填写的是契约程序集名称。
  3. 确认 Host 加载的正是当前编辑的 .config。
  4. 确认 handler 和契约程序集都在运行目录中,并且已被扫描。
  5. 开启 Debug 日志,检查 handler discovery 和 message classification。
  6. 使用 Publish 发布一条事件,进行端到端验证。
  7. 根据所用 transport 检查订阅存储或 broker topology,不要只观察发布者输入队列。

如果项目使用较老的 NServiceBus API,IEvent、配置节点和订阅存储方式可能与当前版本不同。排查时应以项目实际引用的 NServiceBus 主版本为准,避免混用不同版本的文档和配置示例。

备注:内容仅供参考。