5

シンプルなテーブル (SQL Server 2005 または 2008) から行を読み取り、Web サービスの呼び出しなどの作業を行い、結果のステータス (成功) で行を更新するサーバー アプリケーションを (C# で) 開発する必要があります。 、 エラー)。

非常に単純に見えますが、次のアプリケーション要件を追加すると、さらに難しくなります。

  • ロード バランシングとフォールト トレランスのために、複数のアプリケーション インスタンスを同時に実行する必要があります。通常、アプリケーションは 2 つ以上のサーバーにデプロイされ、同じデータベース テーブルに同時にアクセスします。テーブルの各行は 1 回だけ処理する必要があるため、複数のアプリケーション インスタンス間で共通の同期/ロック メカニズムを使用する必要があります。

  • アプリケーション インスタンスが行セットを処理している場合、他のアプリケーション インスタンスは、処理待ちの別の行セットを読み取るために、その処理が終了するまで待機する必要はありません。

  • アプリケーション インスタンスがクラッシュした場合、処理中のテーブル行に対して手動で介入する必要はありません (クラッシュしたインスタンスが処理していた行のアプリケーション ロックに使用された一時的なステータスを削除するなど)。

  • 行はキューのように処理する必要があります。つまり、最も古い行を最初に処理する必要があります。

これらの要件はそれほど複雑ではないように見えますが、解決策を見つけるのに苦労しています。

XLOCKUPDLOCKROWLOCK、などのロック ヒントの提案を見READPASTたことがありますが、これらの必要条件を実装できるロック ヒントの組み合わせは見当たりません。

助けてくれてありがとう。

よろしく、

ヌーノ・ゲレイロ

4

3 に答える 3

5

これは、「テーブルをキューとして使用する」で説明されているように、典型的なテーブル アズ キュー パターンです。保留キューを使用し、デキュー トランザクションも妥当なタイムアウトで再試行をスケジュールする必要があります。Web コールの間、ロックを保持することは現実的に不可能です。成功すると、保留中のアイテムが削除されます。

また、バッチでデキューできる必要があります。深刻な負荷 (1 秒あたり 100 および数千の操作) になると、1 つずつデキューするのは遅すぎます。したがって、リンクされた記事から保留中のキューの例を取り上げます。

create table PendingQueue (
  id int not null,
  DueTime datetime not null,
  Payload varbinary(max),
  cnstraint pk_pending_id nonclustered primary key(id));

create clustered index cdxPendingQueue on PendingQueue (DueTime);
go

create procedure usp_enqueuePending
  @dueTime datetime,
  @payload varbinary(max)
as
  set nocount on;
  insert into PendingQueue (DueTime, Payload)
    values (@dueTime, @payload);
go

create procedure usp_dequeuePending
  @batchsize int = 100,
  @retryseconds int = 600
as
  set nocount on;
  declare @now datetime;
  set @now = getutcdate();
  with cte as (
    select top(@batchsize) 
      id,
      DueTime,
      Payload
    from PendingQueue with (rowlock, readpast)
    where DueTime < @now
    order by DueTime)
  update cte
    set DueTime = dateadd(seconds, @retryseconds, DueTime)
    output deleted.Payload, deleted.id;
go

処理が成功したら、ID を使用してアイテムをキューから削除します。失敗またはクラッシュした場合、10 分後に自動的に再試行されます。HTTP がトランザクションのセマンティクスを提供しない限り、100% 一貫したセマンティクスでこれを行うことは決してできないということを内部化する必要があると考えています(たとえば、アイテムが 2 回処理されないことを保証します)。エラーのマージンを非常に高くすることができますが、HTTP 呼び出しが成功した後、データベースが更新される前にシステムがクラッシュする瞬間が常にあり、同じ項目が再試行される原因になります。HTTP 呼び出しのにシステムがクラッシュした場合。

于 2012-07-04T20:12:02.407 に答える
4

私は当初、これにSQL Server Service Brokerを提案しました。ただし、いくつかの調査の結果、これはおそらく問題を処理する最善の方法ではないことが判明しました。

残っているのは、要求したテーブル アーキテクチャです。ただし、お気づきのように、ロック、トランザクション、およびそのようなスキームに対する高い圧力が非常に複雑であるため、指定された基準をすべて満たすソリューションを考え出すことができる可能性は低いです。同時実行性と 1 秒あたりの高いトランザクション。

注: 現在、この問題を調査しており、後ほどご連絡いたします。次のスクリプトは、指定された要件を満たすための私の試みです。ただし、頻繁にデッドロックが発生し、アイテムが順不同で処理されます。しばらくお待ちください。それまでの間、破壊的な読み取り方法 (OUTPUT または OUTPUT INTO を使用した DELETE) を検討してください。

SET XACT_ABORT ON; -- blow up the whole tran on any errors
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRAN

UPDATE X
SET X.StatusID = 2 -- in process
OUTPUT Inserted.*
FROM (
   SELECT TOP 1 * FROM dbo.QueueTable WITH (READPAST, ROWLOCK)
   WHERE StatusID = 1 -- ready
   ORDER BY QueuedDate, QueueID -- in case of items with the same date
) X;

-- Do work in application, holding open the tran.

DELETE dbo.QueueTable WHERE QueueID = @QueueID; -- value taken from recordset that was output earlier

COMMIT TRAN;

複数または多数の行が 1 つのクライアントによって一度にロックされる場合、行ロックがエクステント、ページ、またはテーブル ロックにエスカレートする可能性があるため、注意してください。また、通常、ロックを維持する長時間実行されるトランザクションを保持することは、絶対に避けてください。この特別な使用例ではうまくいくかもしれませんが、複数のクライアントによる高い tps によってシステムが故障するのではないかと心配しています。通常、キュー テーブルにクエリを実行するプロセスは、キュー作業を行っているプロセスのみであることに注意してください。レポートを実行するすべてのプロセスは、READ UNCOMMITTED または WITH NOLOCK を使用して、キューとの干渉を回避する必要があります。

行が順不同で処理されることの意味は何ですか? 別のインスタンスが行を正常に完了している間にアプリケーション インスタンスがクラッシュした場合、この遅延により少なくとも 1 行の完了が遅れ、処理順序が正しくなくなる可能性があります。

上記のトランザクション/ロック方法が満足のいくものでない場合、アプリケーションのクラッシュを処理する別の方法として、インスタンスに名前を付けてから、それらの名前付きインスタンスが実行されているかどうかを定期的にチェックする機能を持つ監視プロセスを設定します。名前付きインスタンスが起動すると、そのインスタンス識別子を持つ未処理の行が常にリセットされます (「インスタンス A」と「インスタンス B」のような単純なものが機能します)。さらに、監視プロセスはインスタンスが実行されているかどうかを確認し、実行されていないインスタンスがある場合は、欠落しているインスタンスの行をリセットして、他のインスタンスを実行できるようにします。クラッシュと回復の間にはわずかな時間差がありますが、適切なアーキテクチャがあれば、それはかなり合理的です。

注: 次のリンクは啓発的である必要があります。

于 2012-07-04T18:08:19.767 に答える
2

これは、SQL トランザクションでは実行できません (または、トランザクションをメイン コンポーネントとして使用することはできません)。実際、これを行うことはできますが、すべきではありません。長いロックのために、トランザクションはこのように使用されることを意図していません。

トランザクションを長時間開いたままにしておく (行を取得し、Web サービスを呼び出し、戻って更新を行う) ことは、まったく良くありません。そしてoptimistic locking、あなたが望むことを可能にする分離レベルはありません。

ROWLOCK を使用することも良い考えではありません。ヒント。これはロック エスカレーションの対象であり、テーブル ロックに変換できます。

データベースへの単一のエントリ ポイントを提案してもよろしいですか? pub/sub のデザインに合っていると思います。したがって、これらのレコードを読み取り/更新するコンポーネントは1 つだけです。

  1. メッセージのバッチを読み取ります (他のすべてのインスタンスが消費するのに十分です) - 1000、10000、適切と思われるものは何でも。これらのバッチは、キューに入れられた方法で他の (同時) コンポーネントで利用できるようになります。私は MSMQ と言うつもりはありません :) (今日私がお勧めするのは 2 回目ですが、あなたの場合にも本当に適しています)。
  2. メッセージを次のようにマークしますin progress
  3. コンシューマーはすべて、トランザクション的にインバウンド キューにバインドされ、処理を行います。
  4. 準備ができたら、Web サービス呼び出しの後、メッセージを送信キューに入れます。
  5. 中央コンポーネントはそれらを取得し、分散トランザクション内でデータベースの更新を行います (失敗した場合、メッセージはキューに残ります)。その操作を実行できるのはこれだけなので、同時実行性の問題は発生しません。少なくともデータベースにはありません。
  6. その間、次の保留中のバッチなどを読み取ることができます。
于 2012-07-04T18:00:21.160 に答える