ServiceInsight 中如何排查大量 Unknown Message Type 消息
结论
出现 Unknown Message Type,通常说明 ServiceControl 从审计队列读到了一条无法识别消息类型的记录。这类消息一般并非由 ServiceInsight 创建。排查时,先确认记录中的 NServiceBus.EnclosedMessageTypes header 是否缺失或无效。
如果消息正文为空、没有错误信息和序列图,而且持续大量出现,建议依次检查:
audit、error、monitoring等 Service Platform 队列是否混用。NServiceBus.ServicePlatform.Connector是否启用了审计,导致它处理的基础设施消息再次进入审计队列。- Connector 的输入和输出地址是否相同,或是否形成回路。
- 是否有非 NServiceBus 程序直接向审计队列发送消息。
- Metrics、Heartbeat 或其他控制消息是否被错误路由到审计队列。
删除并重建 Azure Service Bus queue 或 subscription 通常无效。只要错误的生产者或路由仍然存在,新队列仍会收到这些消息。
检查 Unknown 记录的 headers
在 ServiceInsight 中打开几条 Unknown 记录,对比它们的 headers,主要查看:
NServiceBus.EnclosedMessageTypes
NServiceBus.MessageId
NServiceBus.MessageIntent
NServiceBus.OriginatingEndpoint
NServiceBus.OriginatingMachine
NServiceBus.ProcessingEndpoint
NServiceBus.ProcessingMachine
NServiceBus.ContentType
NServiceBus.TimeSent
NServiceBus.ConversationId
NServiceBus.RelatedTo
可根据以下现象判断来源:
| 现象 | 可能原因 |
|---|---|
缺少 NServiceBus.EnclosedMessageTypes | 非 NServiceBus 消息、基础设施消息、发送方遗漏了 header,或 ServiceControl 把错误的队列当成审计队列读取 |
有 ProcessingEndpoint | 该 endpoint 生成了审计副本,优先检查这个 endpoint |
有 OriginatingEndpoint | 可据此定位最初发送消息的应用 |
ProcessingEndpoint 是 Connector endpoint | Connector 可能被错误地纳入了审计 |
OriginatingEndpoint 和 ProcessingEndpoint 都没有 | 更可能是直接写入审计队列的原生 Azure Service Bus 消息 |
| 消息按固定频率出现,例如每秒或每分钟一次 | 优先检查 Metrics、Heartbeat、健康检查和定时任务 |
| 多条记录只有 MessageId 或时间不同,其他 headers 完全相同 | 通常来自同一个后台生产者或配置回路 |
如果存在 NServiceBus.EnclosedMessageTypes,请记录它的原始值,并确认内容不是空字符串,也没有在发送过程中被截断或改写。正常值通常包含消息的 CLR 类型名,例如:
MyCompany.Contracts.OrderSubmitted, MyCompany.Contracts
ServiceInsight 通常不需要加载业务程序集也能显示这个名称,所以缺少某个业务 DLL 一般不是首先要排查的问题。
检查 Service Platform 队列是否混用
审计、错误和监控数据用途不同,应分别使用不同的实体地址。例如:
var endpointConfiguration = new EndpointConfiguration("Orders");
endpointConfiguration.AuditProcessedMessagesTo("audit");
endpointConfiguration.SendFailedMessagesTo("error");
var metrics = endpointConfiguration.EnableMetrics();
metrics.SendMetricDataToServiceControl(
"monitoring",
TimeSpan.FromSeconds(1));
这里的 "audit"、"error" 和 "monitoring" 只是示例。实际名称应与 ServiceControl、ServiceControl Monitoring 和 Connector 的配置一致。
逐项核对以下配置:
- ServiceControl Audit 实例读取的是否确实是审计队列。
- ServiceControl Error 实例读取的是否为错误队列。
NServiceBus.Metrics.ServiceControl是否把数据发送到 monitoring queue,而非 audit queue。- Heartbeat 或 Custom Check 使用的地址是否误配为 audit queue。
- 开发、测试和生产环境是否意外共用同一个审计队列。
- RabbitMQ 与 Azure Service Bus 之间的 Connector 映射是否误用了同名地址。
Azure Service Bus subscription 只会筛选和转发现有消息,不会自行生成空消息。即使 subscription 规则正确,上游生产者或 Connector 仍可能存在配置错误。
检查 ServicePlatform.Connector
使用 NServiceBus.ServicePlatform.Connector 时,需要把 Connector 作为基础设施组件单独配置。尤其要检查 Connector endpoint 是否因共享配置、统一扩展方法或配置约定而自动启用了审计。
项目中可能存在类似的公共配置:
public static void ApplyCommonConfiguration(
EndpointConfiguration endpointConfiguration)
{
endpointConfiguration.SendFailedMessagesTo("error");
endpointConfiguration.AuditProcessedMessagesTo("audit");
}
如果业务 endpoint 和 Connector endpoint 都调用这段配置,Connector 处理的平台消息也可能再次被审计。可以把业务 endpoint 和基础设施 endpoint 的配置分开:
public static void ApplyBusinessEndpointConfiguration(
EndpointConfiguration endpointConfiguration)
{
endpointConfiguration.SendFailedMessagesTo("error");
endpointConfiguration.AuditProcessedMessagesTo("audit");
}
public static void ApplyConnectorConfiguration(
EndpointConfiguration endpointConfiguration)
{
endpointConfiguration.SendFailedMessagesTo("error");
// 不在这里调用 AuditProcessedMessagesTo。
// Connector 的队列映射应按实际部署配置单独设置。
}
同时检查 Connector 的地址映射,排除以下配置:
connector input -> audit
connector output -> audit
以及:
Namespace A/audit -> Connector -> Namespace B/audit
Namespace B/audit -> 另一个 Connector -> Namespace A/audit
这类回路会不断产生新记录。Connector 的配置 API 会随版本和部署方式变化,应以项目当前使用的 NServiceBus.ServicePlatform.Connector 2.0.1 配置代码为准,核对各地址之间的关系,不要直接照搬其他版本的示例。
从 Azure Service Bus 确认消息来源
如果 ServiceInsight 提供的 headers 不足,可以暂时停止对应的 ServiceControl Audit 实例,再使用 Azure Portal 的 Service Bus Explorer 对审计队列执行 Peek。
请使用 Peek,不要使用 ReceiveAndDelete,因为后者会删除消息,可能破坏调查现场。
检查 Peek 到的消息是否包含:
- Application properties
- Content type
- Correlation ID
- Message ID
- Scheduled enqueue time
- Dead-letter source
- Subject 或 Label
- 消息正文长度
如果审计队列中的原始消息已经缺少 NServiceBus.EnclosedMessageTypes,说明问题出现在 ServiceInsight 之前,可以暂时排除 ServiceInsight UI。
Azure Service Bus 默认不会在每条消息上保存可直接识别的应用程序名称。必要时,可以结合 Azure Monitor 诊断日志调查哪个身份持续向目标实体发送消息。诊断日志能否精确关联到某条消息,取决于当前启用的日志类别和消息发送方式,未必能提供逐条消息对应的生产者信息。
为新消息添加生产者标识
如果多个应用共用 namespace 和审计队列,可以为业务消息添加自定义 header。即使其他元数据不完整,也可以据此确认发送方。
using NServiceBus;
public sealed class AddProducerIdentity :
Behavior<IOutgoingLogicalMessageContext>
{
private readonly string applicationName;
private readonly string environmentName;
public AddProducerIdentity(
string applicationName,
string environmentName)
{
this.applicationName = applicationName;
this.environmentName = environmentName;
}
public override Task Invoke(
IOutgoingLogicalMessageContext context,
Func<Task> next)
{
context.Headers["X-Application-Name"] = applicationName;
context.Headers["X-Environment-Name"] = environmentName;
return next();
}
}
注册 behavior:
endpointConfiguration.Pipeline.Register(
new AddProducerIdentity(
applicationName: "Orders.Api",
environmentName: "Production"),
"Adds the producing application identity to outgoing messages");
上线前,先在测试环境确认自定义 header 会保留在最终的审计记录中。这种方式只能用于追踪新消息,无法补全已经进入 ServiceControl 的历史记录。
用隔离法查找持续发送者
如果仍无法通过 header 确认来源,可以按 endpoint 逐一隔离:
- 记录 Unknown 消息的产生频率和时间间隔。
- 在非生产环境中逐个停止业务 endpoint、Connector、Metrics 和 Heartbeat 发送端。
- 每停止一个组件,观察是否还会产生新的 Unknown 记录。
- 条件允许时,为不同应用临时配置不同的审计队列。
- 找到来源后,检查该组件的审计设置、公共配置和 Connector 映射。
不要通过反复删除 queue 或 subscription 进行隔离。这会丢失现场信息,也无法阻止仍在运行的发送方继续发送消息。
header 完整但仍显示 Unknown
如果记录符合以下情况:
NServiceBus.EnclosedMessageTypes存在且内容合理;- 消息正文可以正常读取;
ProcessingEndpoint等审计 headers 完整;- ServiceControl 中的数据没有明显缺失;
再考虑 ServiceInsight 与 ServiceControl 的兼容性、ServiceControl 索引状态或产品缺陷。
建议按以下顺序处理:
- 查看 ServiceControl 日志中是否有导入、反序列化或索引错误。
- 确认 ServiceInsight 2.13.0 与 ServiceControl 4.32.2 是否属于官方支持的组合。
- 备份配置和数据库后,按照官方升级路径升级,不要只升级 ServiceInsight。
- 准备一条脱敏后的原始消息,附上完整 headers、正文长度、ServiceControl 日志和稳定复现步骤,再提交给 Particular Software 支持。
目前的信息还不足以认定这是特定版本的缺陷。大量正文为空且缺少消息类型的记录,更可能源于队列混用、基础设施消息被审计、Connector 重复审计,或非 NServiceBus 生产者直接向审计队列写入消息。
修复后仍能看到旧记录
停止错误生产者后,ServiceControl 已经导入的历史记录不会自动消失。这些记录会一直保留到超过配置的保留期限,或通过当前 ServiceControl 版本支持的维护方式清理。
修复后需要分别确认:
- ServiceControl 不再新增
Unknown Message Type记录。 - 旧记录按保留策略逐步清除。
除非当前版本的官方维护文档明确支持,否则不要直接修改或删除 ServiceControl 数据库中的记录。
备注:内容仅供参考。
