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。
可以采用以下方法检查:
- 暂时将 NServiceBus 日志级别调到
Debug。 - 查看启动日志中是否发现了
OrderAcceptedHandler。 - 确认
OrderAccepted被分类为 event、command 还是普通 message。 - 确认 endpoint mapping 是否解析到
BusStop.Backend。 - 检查发布者队列、错误队列和订阅存储。
- 实际发布一个
OrderAccepted,确认 subscriber 能否收到。
日志配置方式取决于 NServiceBus 的大版本和使用的日志框架,应采用与当前项目版本对应的配置方法。
还需要确认发布方式
发布者必须发布事件,而不是发送命令。例如:
bus.Publish(new OrderAccepted());
不能用下面的方式验证发布/订阅:
bus.Send(new OrderAccepted());
Send 是点对点发送,目标由路由决定。Publish 才会把事件交给已经订阅的 endpoint。
建议的排查顺序
可以按以下顺序排查:
- 将
OrderAccepted明确改为实现IEvent。 - 确认
Messages填写的是契约程序集名称。 - 确认 Host 加载的正是当前编辑的
.config。 - 确认 handler 和契约程序集都在运行目录中,并且已被扫描。
- 开启 Debug 日志,检查 handler discovery 和 message classification。
- 使用
Publish发布一条事件,进行端到端验证。 - 根据所用 transport 检查订阅存储或 broker topology,不要只观察发布者输入队列。
如果项目使用较老的 NServiceBus API,IEvent、配置节点和订阅存储方式可能与当前版本不同。排查时应以项目实际引用的 NServiceBus 主版本为准,避免混用不同版本的文档和配置示例。
备注:内容仅供参考。
