パイプライン パフォーマンスの最適化
このトピックでは、BizTalk Server ソリューションでのパイプラインのパフォーマンスを最適化するためのガイドラインについて説明します。
BizTalk Server パイプラインのパフォーマンスを最適化するためのベスト プラクティス
パイプライン コンポーネントはパフォーマンスに大きな影響を与える (たとえば、パススルー パイプライン コンポーネントのパフォーマンスが XML アセンブラー/逆アセンブラー パイプライン コンポーネントよりも最大 30% 優れている) ため、デプロイに実装する前に、カスタム パイプライン コンポーネントが最適に実行されるようにしてください。 BizTalk アプリケーションの全体的なパフォーマンスを最大化する場合は、カスタム パイプライン内のパイプライン コンポーネントの数を最小限に抑えます。
また、パイプライン コンポーネントのメッセージ永続化頻度を減らし、冗長性を最小限に抑えるためにコンポーネントをコーディングすることで、全体的なパフォーマンスを向上させることもできます。 カスタム追跡コンポーネントなど、パフォーマンスが低下する可能性があるすべてのカスタム アセンブリと特定の成果物は、システムが完全な容量で動作しているときの動作を観察し、考えられるボトルネックを見つけるために、負荷の高い状態で個別にテストする必要があります。
パイプライン コンポーネント内の受信メッセージを読み取る必要がある場合は、 XmlDocument オブジェクトを使用してドキュメント全体をメモリに読み込まないようにします。 XML ドキュメントのメモリ内表現を読み込んで作成するために XmlDocument クラスのインスタンスに必要な領域の量は、実際のメッセージ サイズの最大 10 倍です。 メッセージを読み取るには、 XmlTextReader オブジェクトと、次のクラスのインスタンスを使用する必要があります。
VirtualStream (Microsoft.BizTalk.Streaming.dll) - このクラスのソース コードは、Pipelines SDK の 2 つの場所にあります。SDK\Samples\Pipelines\ArbitraryXPathPropertyHandler と SDK\Samples\Pipelines\SchemaResolverComponent\SchemaResolverFlatFileDasm。
ReadOnlySeekableStream (Microsoft.BizTalk.Streaming.dll)。
SeekAbleReadOnlyStream - このクラスのソース コードは、次のように Pipelines SDK の 2 つの場所にあります。SDK\Samples\Pipelines\ArbitraryXPathPropertyHandler と SDK\Samples\Pipelines\SchemaResolverComponent\SchemaResolverFlatFileDasm。
可能な限り、PassThruReceive と PassThruTransmit 標準パイプラインを使用します。 パイプライン コンポーネントが含まれていないため、メッセージの処理は実行されません。 このため、メッセージの受信または送信のパフォーマンスを最大限に高めます。 バイナリ メッセージを BizTalk MessageBox に発行する必要がある場合は、受信場所で PassThruReceive パイプラインを使用し、バイナリ メッセージを送信する必要がある場合は送信ポートで PassThruTransmit パイプラインを使用できます。 メッセージが書式設定され、送信する準備ができている場合は、オーケストレーションにバインドされた物理送信ポートで PassThruTransmit パイプラインを使用することもできます。 次のいずれかのアクションを実行する必要がある場合は、別のアプローチを使用する必要があります。
受信 XML またはフラット ファイル メッセージのコンテキストでプロパティを昇格します。
受信場所内にマップを適用します。
メッセージをサブスクライブするオーケストレーションにマップを適用します。
メッセージをサブスクライブする送信ポートにマップを適用します。
これらのアクションのいずれかを実行するには、受信パイプライン内のドキュメントの種類をプローブして検出し、(namespace#root-name) 値を MessageType コンテキスト プロパティに割り当てる必要があります。 この操作は通常、Xml 逆アセンブラー コンポーネント (XmlDasmComp) やフラット ファイル逆アセンブラー コンポーネント (FFDasmComp) などの逆アセンブラー コンポーネントによって実行されます。 この場合は、標準 (XmlReceive パイプラインなど) または標準コンポーネントまたはカスタム逆アセンブラー コンポーネントを含むカスタム パイプラインを使用する必要があります。
可能な限り遅れてリソースを取得し、できるだけ早く解放します。 たとえば、データベース上のデータにアクセスする必要がある場合は、できるだけ遅く接続を開き、できるだけ早く閉じます。 C# using ステートメントを使用して、破棄可能なオブジェクトを暗黙的に解放するか、try-catch-finally ステートメントの finally ブロックを使用してオブジェクトを明示的に破棄します。 ソース コードをインストルメント化して、コンポーネントを簡単にデバッグできるようにします。
メッセージ処理を高速化するために厳密に必要ではないコンポーネントをパイプラインから排除します。
受信パイプライン内では、メッセージ ルーティング (オーケストレーション、送信ポート) またはメッセージ コンテキスト プロパティ (送信ポート) の降格に必要な場合にのみ、アイテムをメッセージ コンテキストに昇格させる必要があります。
メッセージにメタデータを含める必要があり、ルーティングまたは降格の目的でメタデータを使用しない場合は、 IBaseMessageContext.Promote メソッドの代わりに IBaseMessageContext.Write メソッドを使用します。
XPath 式を使用してメッセージから情報を抽出する必要がある場合は、SelectNodes メソッドまたは SelectSingleNode メソッドを使用するためだけに、XmlDocument オブジェクトを使用してドキュメント全体をメモリに読み込まないようにします。 または、「ストリーミングを使用したメモリ使用量の最適化」で説明されている手法 を使用します。
ストリーミングを使用して、パイプラインにメッセージを読み込むときに必要なメモリ占有領域を最小限に抑える
次の手法では、メッセージをパイプラインに読み込むときにメッセージのメモリ占有領域を最小限に抑える方法について説明します。
ReadOnlySeekableStream と VirtualStream を使用してパイプライン コンポーネントからのメッセージを処理する
パイプライン コンポーネント内のメモリにメッセージ全体を読み込まないようにすることをお勧めします。 望ましい方法は、カスタム ストリーム実装で受信ストリームをラップし、読み取り要求が行われると、カスタム ストリームの実装によって基になるラップされたストリームが読み取られ、読み取り時にデータが処理されます (純粋なストリーミング方式)。 これは実装が非常に難しい場合があり、ストリームで何を行う必要があるかによっては不可能な場合があります。 この場合は、Microsoft.BizTalk.Streaming.dll によって公開される ReadOnlySeekableStream クラスと VirtualStream クラスを使用します。 これらの実装は、BizTalk SDK の任意の XPath プロパティ ハンドラー (BizTalk Server サンプル) (https://go.microsoft.com/fwlink/?LinkId=160069) でも提供されます。ReadOnlySeekableStream を使用すると、カーソルをストリームの先頭に位置変更できます。 VirtualStream は、サイズが指定されたしきい値を超えない限り、MemoryStream を内部的に使用します。この場合、ストリームはファイル システムに書き込まれます。 これら 2 つのストリームを組み合わせて使用すると (ReadOnlySeekableStream の永続ストレージとして VirtualStream を使用)、"シーク可能性" と "ファイル システムへのオーバーフロー" の両方の機能が提供されます。 これにより、メッセージ全体をメモリに読み込むことなく、大きなメッセージの処理に対応できます。 この機能を実装するために、パイプライン コンポーネントで次のコードを使用できます。
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 オブジェクトを抽出します。
XmlDocument クラスによって公開される SelectNodes メソッドと SelectSingleNode メソッドではなく、XML ドキュメントから特定の値を取得する必要がある場合は、次のコード例に示すように、Microsoft.BizTalk.XPathReader.dll アセンブリによって提供される XPathReader クラスのインスタンスを使用します。
Note
特に小さいメッセージの場合、SelectNodes または SelectSingleNode で XmlDocument を使用すると、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 を使用してパイプライン コンポーネントからのメッセージを処理する
ストリーミング アプローチを使用するカスタム パイプライン コンポーネントを実装するもう 1 つの方法は、.NET XmlReader クラスと XmlWriter クラスを、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 の使用
パイプライン コンポーネントは、作成するオブジェクトの有効期間を管理し、これらのオブジェクトが不要になったらすぐにガベージ コレクションを実行する必要があります。 パイプライン コンポーネントが、パイプラインの実行が終了するまでオブジェクトの有効期間を持続させたい場合は、パイプラインがパイプライン コンテキストからフェッチできるそのようなオブジェクトをリソース トラッカーに追加する必要があります。
リソース トラッカーは、次の種類のオブジェクトに使用されます。
Stream オブジェクト
COM オブジェクト
IDisposable オブジェクト
メッセージ エンジンにより、リソース トラッカーに追加されたすべてのネイティブ リソースが適切なタイミングで解放されます。これは、成功したか失敗したかに関係なく、パイプラインが完全に実行された後です。 Resource Tracker インスタンスと追跡対象のオブジェクトの有効期間は、パイプライン コンテキスト オブジェクトによって管理されます。 パイプライン コンテキストは、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;
}
インメモリ アプローチとストリーミング アプローチを使用したパイプラインへのメッセージの読み込みの比較
次の情報は、Nic Barden のブログ 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 クラスと文書化されていない BizTalk クラスの多く | 非常に良い - .NET Frameworkクラス |
"処理ロジック" コードの場所 | - Execute メソッドを使用してリーダーとストリームを "ワイヤアップ" します。 - 実際の実行は、データの読み取り時にリーダーとストリームで実行されます。 |
パイプライン コンポーネントの Execute メソッドから直接。 |
データ | データの読み取り時に、各折り返しレイヤーで再作成されます。 | 次のコンポーネントが呼び出される前に、各コンポーネントで読み取り、変更、書き出しを行います。 |