3

分散トランザクションに問題があります。

SQL Server 2008 R2、Windows 7、.Net 4.0 を使用しています。

これが私が実際にやりたいことです:

  • バッファデータベース(Aという名前)があります
  • 別のデータベース(Bという名前)があります
  • WCF Webサービスを介してデータベースAからデータベースBにデータを送信したい(SOAP over HTTP、AとBは同じマシン上にない)
  • データがデータベース B に正常に送信されると、データはデータベース A から削除されます

これは、私が現在抱えている問題の非常に単純化された説明であることに注意してください。私の実際のアプリケーションでは、複数の SQL Server インスタンス間でデータをレプリケートしていません (記事の最後にある更新を参照してください)。

すべてがトランザクションの一部であるため、操作全体がアトミックです。

これが私が現在シーケンシャルにやろうとしていることです(トランザクション内のすべて):

  1. データベースAからチャンクを読み取りました(たとえば50行)
  2. ステップ1で読み取った行を更新します(実際には、行のブールSent列に値を設定trueしたため、将来的にはこれらの行が送信されることがわかります)
  3. wsHttpBindingトランザクション フローの一部である WCF Web サービス (SOAP 1.2、) を (クライアント側で) 消費します (同期呼び出しをブロックします)。Web サービス リクエストは、手順 1 で読み取ったデータを送信します。
  4. Web サービスの実装 (サーバー側) は、データベース B にデータを挿入します (Web サービス要求に含まれるデータ)。
    • サーバーから例外が受信されない場合、Sent値が asのデータtrueはデータベース A から削除されます
    • 特定の値FaultExceptionがサーバーから受信された場合、Sent値が true のデータがデータベース A から削除されます
    • その他FaultExceptionsおよびその他の例外 (エンドポイントが見つからないなど) の場合、Sent値が asのデータtrueはデータベース A から削除されません

注: 実際には複数のバッファ データベース (A1、A2、A3...、AN) と複数のターゲット データベース (B1、.... BN) があります。N 個のデータベースを処理するための N 個のスレッドがあります。

サーバーとクライアントを実行すると、すべて正常に動作します。データは、チャンクごとにデータベース A からデータベース B にアトミックに「転送」されます。トランザクションの途中でクライアントを残酷に停止すると (プロセスが強制終了されます)、ほとんどの場合、すべて問題ありません (つまり、データがデータベース A から削除されたり、追加されたりすることはありません)。データベース B) に。

しかし、ときどき (これが私の実際の問題です)、データベース B がロックされることがあります。ロックを解除する唯一のオプションは、サーバー プロセスを終了することです。

問題が発生すると、アクティブなトランザクションがあることを MSDTC で確認できます (現在、3 つのバッファー データベースと 3 つのターゲット データベースを扱っているため、スクリーンショットには 3 つのトランザクションがあることに注意してください。 3DBあります)。

ここに画像の説明を入力

データベース B を再度実行しようとすると、データベース B がロックされていることがSELECTわかります。下の図でわかるように、私の要求は、サーバー プロセスによってデータベース B にSELECT対応するセッション ID 67 によってブロックされます。INSERT

ここに画像の説明を入力

サーバー プロセスが終了するまで、ロックは永久に残ります (1 時間以上経ってもトランザクション タイムアウトは発生しません)。MSDTC でトランザクションを検証またはキャンセルできません。「it cannot be aborted because it is not "In Doubt"」という警告が表示されます。

データベース B がロックされたままになっているのはなぜですか? クライアントが終了した場合、トランザクションが失敗し、タイムアウト後にデータベース B のロックが解放されるべきではありませんか?

サーバー側のコードは次のとおりです。

// Service interface
[ServiceContract]
public interface IService
{
    [OperationContract]
    [FaultContract(typeof(MyClass))]
    [TransactionFlow(TransactionFlowOption.Allowed)]
    void SendData(DataClass data);
}

// Service implementation
[ServiceBehavior()]
public partial class Service : IService
{
    [OperationBehavior(TransactionScopeRequired = true)]
    public void SendData(DataClass data)
    {
        if (data == null)
        {
            throw new FaultException<MyClass>(new MyClass());
        }

        try
        {
            // Inserts data in database B
            using (DBContextManagement ctx = new DBContextManagement())
            {
                // Inserts data using Entity Framework
                // This will add some entities to the context
                // then call context.SaveChanges()
                ctx.InsertData(data);
            }
        }
        catch (Exception ex)
        {
            throw new FaultException<MyClass>(new MyClass());
        }
    }
}

これが私のサーバー側の構成です(セルフホステッドWCFサービス):

<system.serviceModel>
    <services>
      <service name="XXXX.MyService">
        <endpoint binding="wsHttpBinding" bindingConfiguration="WsHttpBinding_IMyService" contract="XXXX.IMyService" />
      </service>
    </services>
    <bindings>
      <wsHttpBinding>
        <binding name="WsHttpBinding_IMyService" transactionFlow="true" allowCookies="true" >
          <readerQuotas maxDepth="32" maxArrayLength="2147483647" maxStringContentLength="2147483647" />
          <security mode="None" />
        </binding>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceTimeouts transactionTimeout="00:00:20" />
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <dataContractSerializer maxItemsInObjectGraph="2147483647" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>

これが私のクライアントコードです:

try
{
    using (DBContextManagement ctx = new DBContextManagement())
    {
        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted, Timeout = new TimeSpan(0, 0, 30) }, EnterpriseServicesInteropOption.None))
        {
            // First of all, retrieves data from database A
            // Internally queries the database A through Entity Framework
            var data = ctx.GetData();

            // Mark data as sent to the server
            // Internally updates the database A through Entity Framework
            // This actually set the Sent property as true, then call
            // context.SaveChanges()
            ctx.SetDataAsSent(data);

            try
            {
                // Send data to the server
                MyServiceClient proxy = new MyServiceClient();
                MyServiceClient.SendData(data);

                // If we're here, then data has successfully been sent
                // This internally removes sent data (i.e. data with
                // property Sent as true) from database A through entity framework
                // (entities removed then context.SaveChanges)
                ctx.RemoveSentData();
            }
            catch (FaultException<MyClass> soapError)
            {
                // SOAP exception received
                // We internally remove sent data (i.e. data with
                // property Sent as true) from database A through entity framework
                // (entities removed then context.SaveChanges)
                ctx.RemoveSentData();
            }
            catch (Exception ex)
            {
                // Some logging here
                return;
            }

            ts.Complete();
        }
    }
}
catch (Exception ex)
{
    // Some logging here (removed from code)
    throw;
}

これが私のクライアント構成です:

<system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="WsHttpBinding_IMyService"
                 closeTimeout="00:01:00"
                 openTimeout="00:01:00"
                 receiveTimeout="00:10:00"
                 sendTimeout="00:01:00"
                 allowCookies="false"
                 bypassProxyOnLocal="false"
                 hostNameComparisonMode="StrongWildcard"
                 transactionFlow="true"
                 maxBufferPoolSize="2147483647"
                 maxReceivedMessageSize="2147483647"
                 messageEncoding="Text"
                 textEncoding="utf-8"
                 useDefaultWebProxy="true">

          <readerQuotas maxDepth="32"
                        maxStringContentLength="2147483647"
                        maxArrayLength="16384"
                        maxBytesPerRead="4096"
                        maxNameTableCharCount="16384" />
          <security mode="None">
            <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>

    <client>
      <endpoint address="http://localhost:8080/MyService.svc"
                binding="wsHttpBinding"
                bindingConfiguration="WsHttpBinding_IMyService"
                contract="XXX.IMyService"
                name="WsHttpBinding_IMyService" />
    </client>

  </system.serviceModel>

だから私の質問は:

  • 分散トランザクションを使用したパターン/設計は有効で堅牢な設計ですか?
  • クライアントのトランザクションが強制終了された場合、データベース B が無期限にロックされる原因は何ですか?
  • 期待どおりに動作させるには、何を変更 (構成および/またはコードおよび/または設計) する必要がありますか?

ありがとう。


編集

実際のユースケースの説明を単純化しすぎたと思います。複数の SQL Server インスタンス間で単純なデータ レプリケーションを実際に行っているようです。それはよく説明されていません (私の悪い点です)、それは私が達成しようとしていることではありません。

私は単にデータを複製しているわけではありません。マシン M1 で A から読み取り、マシン M2 で B に書き込みますが、書き込まれるのは読み取ったものではなく、読み取ったものから計算された値です。SQL Server レプリケーション サービスがビジネス計算を処理できると確信していますが、いくつかの理由でこの方法を実行することはできません。

  • 私は実際にはサーバー側 (データベース B に書き込む) を担当していないため、SQL Server レプリケーションを使用できません。反対側に SQL Server があるかどうかさえわかりません (MySQL、PostgreSQL などを使用した Java バックオフィスである可能性があります)。
  • 同じ理由で、SQL Server サービス ブローカーまたはメッセージ指向ミドルウェア (IMO に適合) を使用できません (異機種データベースおよび環境の可能性があります)。
  • インターフェイスが定義および修正されたため、Web サービスの内容を変更することはできません。

私は WCF で立ち往生しており、相互運用性の要件のために、MSMQ などを使用するようにバインド構成を変更することさえできません。MSMQ は確かに優れています (プロジェクトの別の部分で既に使用しています) が、これは Windows のみです。SOAP 1.2 は標準プロトコルであり、SOAP 1.2 トランザクションも標準です (WS-Atomic実装)。

実際、WCF トランザクションは良い考えではないかもしれません。

実際にどのように機能するかを正しく理解していれば(間違っている場合は修正してください)、サーバー側でトランザクションスコープを「継続」するだけで済みます。これには、サーバー側でトランザクションコーディネーターを構成する必要があり、おそらく壊れます私の相互運用性のニーズ (繰り返しますが、トランザクション コーディネーターとうまく統合されていないサーバー側のデータベースが存在する可能性があります)。

4

1 に答える 1

2

おそらくこれはあなたが探している答えではないかもしれませんが、車輪を再発明しているだけです. A から B に変更をレプリケートする信頼できる方法が必要です。トランザクション レプリケーションという組み込みのソリューションが既に存在します。削除を複製しないように設定するだけで、まさに求めていたセマンティクスが得られます。

トランザクション レプリケーションがうまくいかない場合 (多くのパブリッシャーと多くのサブスクライバーを含む複雑なトポロジがそのようなケースになる可能性があります)、私はまだ WCF を使用しません。SQL Server には、超高速でトランザクション対応の信頼性の高いメッセージングが既にあり、分散トランザクションと 2 フェーズ コミットを回避するように明示的に設計されています: Service Broker .

WCF にこだわる場合でも、調整されたトランザクションを使用することは正しいアプローチではありません。MSMQ キュー チャネルを使用し、RPC 呼び出しスタイルではなく、メッセージ指向の方法でこれを設計する必要があります (また、http WCF バインディングを介した SOAP/HTTP 呼び出しは、美化された RPC に他なりません)。The Game of Distributed Systems Programmingを読むことを強くお勧めします 。レベル 1 に到達したので、レベル 2 に移行する必要があります。WCF HTTP では、レベル 2 に移行できません。MSMQ バインディングを使用した WCF はそうかもしれませんが、キューに入れられたチャネルのプログラミングは RPC チャネルのプログラミングとはまったく異なり、とにかくほとんどすべてをゼロから書き直す必要があることにすぐに気付くでしょう。クールエイドをあきらめるつもりなら、SSB はより信頼性が高く、より速い乗り物でそこに連れて行くことができます.

于 2012-04-19T17:07:56.433 に答える