优化管道性能

本主题介绍在 BizTalk Server 解决方案中优化管道性能的准则。

优化 BizTalk Server 管道性能的最佳做法

  1. 由于管道组件对性能产生重大影响(例如,传递管道组件的性能比 XML 汇编程序/反汇编程序管道组件的性能要好 30%),因此请确保在部署中实现自定义管道组件之前以最佳方式执行。 如果要最大程度地提高 BizTalk 应用程序的整体性能,请最大程度地减少自定义管道中的管道组件数。

  2. 还可以通过减少管道组件中的消息持久性频率以及对组件进行编码以最大程度地减少冗余来提高整体性能。 每个可能中断性能的自定义程序集和特定项目(如自定义跟踪组件)都应在重负载条件下单独测试,以在系统完全正常运行时观察其行为,并找到任何可能的瓶颈。

  3. 如果需要读取管道组件内的入站消息,请避免使用 XmlDocument 对象将整个文档加载到内存中。 XmlDocument 类实例加载和创建 XML 文档的内存中表示形式所需的空间量是实际消息大小的 10 倍。 若要读取消息,应使用 XmlTextReader 对象以及以下类的实例:

    • VirtualStream (Microsoft.BizTalk.Streaming.dll) - 此类的源代码位于 Pipelines SDK 下的两个位置,如下所示:SDK\Samples\Pipelines\ArbitraryXPathPropertyHandler 和 SDK\Samples\Pipelines\SchemaResolverComponent\SchemaResolverFlatFileDasm。

    • ReadOnlySeekableStream (Microsoft.BizTalk.Streaming.dll)

    • SeekAbleReadOnlyStream - 此类的源代码位于 Pipelines SDK 下的两个位置,如下所示:SDK\Samples\Pipelines\ArbitraryXPathPropertyHandler 和 SDK\Samples\Pipelines\SchemaResolverComponent\SchemaResolverFlatFileDasm。

  4. 尽可能使用 PassThruReceive 和 PassThruTransmit 标准管道。 它们不包含任何管道组件,也不执行消息的任何处理。 因此,它们可确保接收或发送消息时获得最佳性能。 如果需要将二进制文档发布到 BizTalk MessageBox,可以在接收位置使用 PassThruReceive 管道;如果需要发送二进制消息,则可以在发送端口使用 PassThruTransmit 管道。 如果消息已格式化且已准备好传输,还可以在绑定到业务流程的物理发送端口上使用 PassThruTransmit 管道。 如果需要完成以下作之一,则需要使用不同的方法:

    • 提升入站 XML 或平面文件消息上下文的属性。

    • 在接收位置内应用地图。

    • 在订阅消息的业务流程中应用映射。

    • 在订阅消息的发送端口上应用地图。

      若要完成其中一项作,必须探测并发现接收管道中的文档类型,并将 (namespace#root-name) 值分配给 MessageType 上下文属性。 此作通常由反汇编程序组件(如 Xml 反汇编程序组件(XmlDasmComp)或平面文件反汇编程序组件(FFDasmComp)完成。 在这种情况下,需要使用标准(例如 XmlReceive 管道)或包含标准或自定义反汇编程序组件的自定义管道。

  5. 尽可能晚获取资源并尽早释放资源。 例如,如果需要访问数据库上的数据,请尽可能晚打开连接并尽快将其关闭。 使用 C# 的 using 语句来隐式释放可释放对象,或者在 try-catch-finally 语句的 finally 块中显式释放对象。 检测源代码以使组件易于调试。

  6. 从管道中消除任何不严格要求用于加快消息处理速度的组件。

  7. 在接收管道中,只有在需要用于消息路由(业务流程、发送端口)或需要降低消息上下文属性(发送端口)时,才应将项提升到消息上下文。

  8. 如果需要在消息中包含元数据,并且不将元数据用于路由或降级目的,请使用 IBaseMessageContext.Write 方法,而不是 IBaseMessageContext.Promote 方法。

  9. 如果需要使用 XPath 表达式从消息中提取信息,请避免使用 XmlDocument 对象将整个文档加载到内存中,只需使用 SelectNodesSelectSingleNode 方法即可。 或者,使用通过流式处理优化内存使用中所述的技术。

使用流式处理最大程度地减少在管道中加载消息时所需的内存占用

以下技术介绍如何在将消息加载到管道时最大程度地减少消息的内存占用。

使用 ReadOnlySeekableStream 和 VirtualStream 处理来自管道组件的消息

最佳做法是避免将整个消息加载到管道组件内的内存中。 更好的方法是使用自定义流实现包装入站流,然后在发出读取请求时,自定义流实现读取基础流、包装流并处理数据(以纯流式处理方式)。 这可能很难实现,甚至可能无法实现,这取决于需要对流进行的操作。 在这种情况下,请使用 Microsoft.BizTalk.Streaming.dll公开的 ReadOnlySeekableStreamVirtualStream 类。 在 BizTalk SDK 中的任意 XPath 属性处理程序(BizTalk Server 示例)https://go.microsoft.com/fwlink/?LinkId=160069)中还提供了这些实现。ReadOnlySeekableStream 可确保将游标重新定位到流的开头。 VirtualStream 将在内部使用 MemoryStream,除非大小超过指定的阈值,在这种情况下,它会将流写入文件系统。 结合使用这两个流(将 VirtualStream 用作 ReadOnlySeekableStream 的持久存储)可提供“可查找性”和“溢出到文件系统”功能。 这可以容纳大型消息的处理,而无需将整个消息加载到内存中。 可以在管道组件中使用以下代码来实现此功能。

int bufferSize = 0x280;
int thresholdSize = 0x100000;
Stream vStream = new VirtualStream(bufferSize, thresholdSize);
Stream seekStream = new ReadOnlySeekableStream(inboundStream, vStream, bufferSize);

此代码通过在每个接收位置或发送端口配置上公开 bufferSize 和 thresholdSize 变量来提供“溢出阈值”。 然后,开发人员或管理员可以针对不同的消息类型和不同的配置(例如 32 位与 64 位)调整此溢出阈值。

使用 XPathReader 和 XPathCollection 从自定义管道组件中提取给定的 IBaseMessage 对象。

如果需要从 XML 文档拉取特定值,而不是使用 XmlDocument 类公开的 SelectNodesSelectSingleNode 方法,请使用由 Microsoft.BizTalk.XPathReader.dll 程序集提供的 XPathReader 类的实例,如以下代码示例所示。

注释

特别是对于较小的消息,将 XmlDocument 与 SelectNodes 或 SelectSingleNode 配合使用可能会比使用 XPathReader 提供更好的性能,但 XPathReader 允许为应用程序保留平面内存配置文件。

此示例演示如何使用 XPathReader 和 XPathCollection 从自定义管道组件中提取给定 的 IBaseMessage 对象。

public IBaseMessage Execute(IPipelineContext context, IBaseMessage message)
{
    try
    {
        ...
        IBaseMessageContext messageContext = message.Context;
        if (string.IsNullOrEmpty(xPath) && string.IsNullOrEmpty(propertyValue))
        {
            throw new ArgumentException(...);
        }
        IBaseMessagePart bodyPart = message.BodyPart;
        Stream inboundStream = bodyPart.GetOriginalDataStream();
        VirtualStream virtualStream = new VirtualStream(bufferSize, thresholdSize);
        ReadOnlySeekableStream readOnlySeekableStream = new ReadOnlySeekableStream(inboundStream, virtualStream, bufferSize);
        XmlTextReader xmlTextReader = new XmlTextReader(readOnlySeekableStream);
        XPathCollection xPathCollection = new XPathCollection();
        XPathReader xPathReader = new XPathReader(xmlTextReader, xPathCollection);
        xPathCollection.Add(xPath);
        bool ok = false;
        while (xPathReader.ReadUntilMatch())
        {
            if (xPathReader.Match(0) && !ok)
            {
                propertyValue = xPathReader.ReadString();
                messageContext.Promote(propertyName, propertyNamespace, propertyValue);
                ok = true;
            }
        }
        readOnlySeekableStream.Position = 0;
        bodyPart.Data = readOnlySeekableStream;
    }
    catch (Exception ex)
    {
        if (message != null)
        {
            message.SetErrorInfo(ex);
        }
        ...
        throw ex;
    }
    return message;
}

将 XMLReader 和 XMLWriter 与 XMLTranslatorStream 配合使用来处理来自管道组件的消息

实现使用流式处理方法的自定义管道组件的另一种方法是将 .NET XmlReaderXmlWriter 类与 BizTalk Server 提供的 XmlTranslatorStream 类结合使用。 例如,Microsoft.BizTalk.Pipeline.Components 程序集中包含的 NamespaceTranslatorStream 类继承自 XmlTranslatorStream ,可用于将旧命名空间替换为流中包含的 XML 文档中的新命名空间。 若要在自定义管道组件中使用此功能,可以使用 NamespaceTranslatorStream 类的新实例包装消息正文部件的原始数据流并返回后者。 这样,入站消息不会在管道组件内部被读取或处理,而是只有在同一管道中的后续组件读取该流,或最终由消息代理使用该流后才会被处理,然后在将文档发布到 BizTalk Server MessageBox 之前完成处理。

以下示例演示如何使用此功能。

public IBaseMessage Execute(IPipelineContext context, IBaseMessage message)
{
   IBaseMessage outboundMessage = message;
   try
   {
      if (context == null)
      {
         throw new ArgumentException(Resources.ContextIsNullMessage);
      }
      if (message == null)
      {
         throw new ArgumentException(Resources.InboundMessageIsNullMessage);
      }

      IBaseMessagePart bodyPart = message.BodyPart;
      Stream stream = new NamespaceTranslatorStream(context,
                                                    bodyPart.GetOriginalDataStream(),
                                                    oldNamespace,
                                                    newNamespace);
      context.ResourceTracker.AddResource(stream);
      bodyPart.Data = stream;
   }
   catch (Exception ex)
   {
      if (message != null)
      {
         message.SetErrorInfo(ex);
      }

      throw ex;
   }
   return outboundMessage;
}

在自定义管道组件中使用 ResourceTracker

管道组件应管理创建的对象生存期,并在不再需要这些对象后立即执行垃圾回收。 如果管道组件希望对象的生命周期持续到管道执行完毕,则必须将此类对象添加到资源跟踪器中,管道可以从管道上下文中获取。

资源跟踪器用于以下类型的对象:

  • 流对象

  • COM 对象

  • IDisposable 对象

    消息引擎确保在管道完全执行后,适时释放所有添加到资源跟踪器的本地资源,无论管道是成功还是失败。 资源跟踪器实例的生存期及其所跟踪的对象由管道上下文对象管理。 管道上下文通过实现 IPipelineContext 接口的对象提供给所有类型的管道组件。

    例如,以下代码片段是一个示例,演示如何在自定义管道组件中使用 ResourceTracker 属性。 若要使用 ResourceTracker 属性,代码片段使用以下参数 IPipelineContext.ResourceTracker.AddResource。 在此参数中:

  • IPipelineContext 接口定义用于访问所有文档处理特定接口的方法。

  • ResourceTracker 属性引用 IPipelineContext,用于跟踪将在管道处理结束时显式释放的对象。

  • ResourceTracker.AddResource 方法用于跟踪 COM 对象、可处置对象和流,并且应始终在自定义管道组件内使用,以在将消息发布到 BizTalk MessageBox 时显式关闭(流)、处置(IDisposable 对象)或释放(COM 对象)这些类型的资源。

public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)

{
      IBaseMessage outMessage = pContext.GetMessageFactory().CreateMessage();

      IBaseMessagePart outMsgBodyPart = pContext.GetMessageFactory().CreateMessagePart();

      outMsgBodyPart.Charset = Encoding.UTF8.WebName;

      outMsgBodyPart.ContentType = "text/xml";

//Code to load message content in the MemoryStream object//

      MemoryStream messageData = new MemoryStream();

   //The MemoryStream needs to be closed after the whole pipeline has executed, thus adding it into ResourceTracker//

      pContext.ResourceTracker.AddResource(messageData);

//Custom pipeline code to load message data into xmlPayload//

      XmlDocument xmlPayLoad = new XmlDocument();

      xmlPayLoad.Save(messageData);

      messageData.Seek(0, SeekOrigin.Begin);

//The new stream is assigned to the message part’s data//

      outMsgBodyPart.Data = messageData;

// Pipeline component logic here//

      return outMessage;

}

使用内存中方法和流式处理方法将消息加载到管道的比较

以下信息摘自尼克·巴登的博客 http://blogs.objectsharp.com/cs/blogs/nbarden/archive/2008/04/14/developing-streaming-pipeline-components-part-1.aspxhttps://go.microsoft.com/fwlink/?LinkId=160228)。 下表比较了使用内存方法和流方法将消息加载到管道中的情况。

比较... 流媒体 内存中
每条消息的内存使用量 低,不考虑消息大小 高(因消息大小而异)
用于处理 XML 数据的常见类 内置派生和自定义派生:

XmlTranslatorStream、XmlReader 和 XmlWriter
XmlDocument、XPathDocument、MemoryStream 和 VirtualStream
文档 差 - 许多不受支持和未记录的 BizTalk 类 非常好 - .NET Framework 类
“处理逻辑”代码的位置 - 通过 Execute 方法“连接”数据流和读写器。
- 读取数据时,实际执行发生在读取器和流中。
直接从管道组件的 Execute 方法执行。
数据 在每个封装层中重新创建,因为数据被逐层读取。 在调用下一个组件之前,在每个组件中进行读取、修改并写出。

另请参阅

优化 BizTalk Server 应用程序