まれに、トランザクション MSMQ キューからのメッセージが 2 回受信されるという問題が発生しています。
これが私のセットアップです(3つのWindows 2008 R2 x64サーバー):
- プライベート キューをホストする MSMQ サーバー。それらはトランザクション対応になるように構成されています。
- ワークフロー (WF4) をホストする 2 つのサーバーが、WCF を介して MSMQ サーバー上のリモート プライベート キューからメッセージを受信します。
- 各キューには 2 つのレシーバー (サーバーごとに 1 つのレシーバー) があります。1 つのサーバーがキューからメッセージをプルすると、このメッセージを再度処理することはできないと思います。
- メッセージはトランザクション的に受信されます (TransactionScopeRequired = true)
- 受信者の netMsmqBinding には
exactlyOnce="true"
- メッセージを受信するたびに、ワークフロー インスタンスの作成がトリガーされます。カスタム WorkflowHostingEndpoint を使用しているので、メッセージの内容に基づいてワークフロー ID を設定できます。
1 つのサーバーでのみ重複メッセージが発生する場合もあれば、2 つのサーバーのそれぞれで重複メッセージが発生する場合もあります。これは、処理するメッセージが大量にある場合にのみ発生し、重複メッセージは常に短い時間枠 (通常は 200 ミリ秒未満) 内に受信されます。
各メッセージには一意の GUID が含まれており、データベースへの挿入の主キーとして使用されるため、メッセージが 2 回受信されたことを検出できます。メッセージを 2 回受信すると、ログ ファイルに主キー違反エラーが記録されます。
WCF サービス モデルとメッセージ トレースを有効にしたところ、メッセージが 2 つの異なる分散トランザクションで 2 回受信されたことがわかりました。この場合、これは同じサーバーで発生しました。
2012-08-16T08:31:18.4874394Z: Thread 210: Message Log Trace (message contains unique GUID 43df02cc-79c7-48ca-995f-175c22fb9e59)
2012-08-16T08:31:18.4874394Z: Thread 210: The transaction '69acc04f-4853-4680-bc13-3855f5b844d4:3782' was received for operation 'CreateWorkflow' from a transacted transport, such as MSMQ.
2012-08-16T08:31:18.6434444Z: Thread 207: Message Log Trace (message contains unique GUID 43df02cc-79c7-48ca-995f-175c22fb9e59)
2012-08-16T08:31:18.6434444Z: Thread 207: The transaction '69acc04f-4853-4680-bc13-3855f5b844d4:3789' was received for operation 'CreateWorkflow' from a transacted transport, such as MSMQ.
これが発生する原因は何ですか? また、これを防ぐにはどうすればよいですか?
更新:メッセージの送信に使用している関連コードは次のとおりです。メッセージの送信方法は、メッセージの受信方法に影響するべきではありませんよね? 送信者側で TransactionScope を開くと、送信者と受信者の間で分散トランザクションが作成される可能性がありますか? これは本当にこれの意図ではありません。
private static readonly ChannelFactory<IWorkflowCreation> _channelFactory =
new ChannelFactory<IWorkflowCreation>(new NetMsmqBinding(NetMsmqSecurityMode.None));
private void StartWorkflow(EndpointAddress address, WorkflowData data)
{
using (var scope = new TransactionScope())
{
var client = _channelFactory.CreateChannel(address);
using ((IClientChannel)client)
{
client.CreateWorkflow(data);
}
scope.Complete();
}
}