PAYATHON 2026

ServiceInsight 中如何排查大量 Unknown Message Type 消息

排障赵师傅

结论

出现 Unknown Message Type,通常说明 ServiceControl 从审计队列读到了一条无法识别消息类型的记录。这类消息一般并非由 ServiceInsight 创建。排查时,先确认记录中的 NServiceBus.EnclosedMessageTypes header 是否缺失或无效。

如果消息正文为空、没有错误信息和序列图,而且持续大量出现,建议依次检查:

  1. audit、error、monitoring 等 Service Platform 队列是否混用。
  2. NServiceBus.ServicePlatform.Connector 是否启用了审计,导致它处理的基础设施消息再次进入审计队列。
  3. Connector 的输入和输出地址是否相同,或是否形成回路。
  4. 是否有非 NServiceBus 程序直接向审计队列发送消息。
  5. 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 endpointConnector 可能被错误地纳入了审计
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 逐一隔离:

  1. 记录 Unknown 消息的产生频率和时间间隔。
  2. 在非生产环境中逐个停止业务 endpoint、Connector、Metrics 和 Heartbeat 发送端。
  3. 每停止一个组件,观察是否还会产生新的 Unknown 记录。
  4. 条件允许时,为不同应用临时配置不同的审计队列。
  5. 找到来源后,检查该组件的审计设置、公共配置和 Connector 映射。

不要通过反复删除 queue 或 subscription 进行隔离。这会丢失现场信息,也无法阻止仍在运行的发送方继续发送消息。

header 完整但仍显示 Unknown

如果记录符合以下情况:

  • NServiceBus.EnclosedMessageTypes 存在且内容合理;
  • 消息正文可以正常读取;
  • ProcessingEndpoint 等审计 headers 完整;
  • ServiceControl 中的数据没有明显缺失;

再考虑 ServiceInsight 与 ServiceControl 的兼容性、ServiceControl 索引状态或产品缺陷。

建议按以下顺序处理:

  1. 查看 ServiceControl 日志中是否有导入、反序列化或索引错误。
  2. 确认 ServiceInsight 2.13.0 与 ServiceControl 4.32.2 是否属于官方支持的组合。
  3. 备份配置和数据库后,按照官方升级路径升级,不要只升级 ServiceInsight。
  4. 准备一条脱敏后的原始消息,附上完整 headers、正文长度、ServiceControl 日志和稳定复现步骤,再提交给 Particular Software 支持。

目前的信息还不足以认定这是特定版本的缺陷。大量正文为空且缺少消息类型的记录,更可能源于队列混用、基础设施消息被审计、Connector 重复审计,或非 NServiceBus 生产者直接向审计队列写入消息。

修复后仍能看到旧记录

停止错误生产者后,ServiceControl 已经导入的历史记录不会自动消失。这些记录会一直保留到超过配置的保留期限,或通过当前 ServiceControl 版本支持的维护方式清理。

修复后需要分别确认:

  • ServiceControl 不再新增 Unknown Message Type 记录。
  • 旧记录按保留策略逐步清除。

除非当前版本的官方维护文档明确支持,否则不要直接修改或删除 ServiceControl 数据库中的记录。

备注:内容仅供参考。