优化管道性能

本主题介绍优化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,在发送端口上发布 PassThruTransmit 管道(如果需要发送二进制消息)可以在接收位置上使用 PassThruReceive 管道。 如果消息已格式化并准备好传输,则还可以在绑定到业务流程的物理发送端口上使用 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 表达式从消息中提取信息,请避免仅使用 SelectNodesSelectSingleNode 方法使用 XmlDocument 对象将整个文档加载到内存中。 或者,使用使用 流式处理优化内存使用情况中所述的技术。

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

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

使用 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 对象、可释放对象和流,并且应始终在自定义管道组件内用于显式关闭 (流) 、释放 (IDisposable 对象) 或释放 (COM 对象) 这些类型的资源时发布到 BizTalk MessageBox。

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.aspx (https://go.microsoft.com/fwlink/?LinkId=160228) 。 此表汇总了使用内存中方法和流式处理方法将消息加载到管道中的比较。

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

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

另请参阅

优化 BizTalk Server 应用程序