5

毎分実行され、メールをキューに送信する自動タスクをセットアップする必要があります。ASP.NET 4.5 と C# を使用しています。現在、global.asax で開始し、キャッシュとキャッシュ コールバックを利用するスケジューラ クラスを使用しています。これがいくつかの問題につながると読んだことがあります。

私がそのようにした理由は、このアプリが複数の負荷分散されたサーバーで実行されるためです。これにより、1 か所で実行でき、1 つ以上のサーバーがオフラインの場合でもコードが実行されます。

これをより良くするための方向性を探しています。Quartz.NET について読んだことがありますが、使用したことはありません。Quartz.NET はアプリケーションからメソッドを呼び出しますか? またはWindowsサービスから?またはWebサービスから?

Windowsサービスの使用についても読んだことがありますが、私が知る限り、それらはサーバーに直接インストールされています。問題は、オンラインのサーバーの数に関係なくタスクを実行する必要があり、それを複製したくないということです。たとえば、サーバー 1 とサーバー 2 でスケジュールされたタスクのセットアップがある場合、それらは両方とも一緒に実行されるため、要求が複製されます。ただし、サーバー 1 がオフラインの場合、タスクを実行するにはサーバー 2 が必要です。

ここで先に進む方法についてのアドバイスはありますか、それとも global.asax メソッドがマルチサーバー環境に最適な方法ですか? ところで、Web サーバーは IIS 8 で Win Server 2012 を実行しています。

編集

詳細情報の要求では、キューはデータベースに格納されます。また、データベース サーバーが Web サーバーとは別であることにも言及しておく必要があります。2 つのデータベース サーバーがありますが、一度に実行されるのは 1 つだけです。両方が読み取る中央ストレージがあるため、データベースのインスタンスは 1 つだけです。1 つのデータベース サーバーがダウンすると、もう 1 つのデータベース サーバーがオンラインになります。

そうは言っても、Windows サービスを両方のデータベース サーバーに配置する方が理にかなっているでしょうか? これにより、一度に 1 つだけが実行されるようになります。

また、アプリケーションから Quartz.NET を実行することについてどう思いますか? millimoose が言及しているように、必ずしも Web フロント エンドで実行する必要はありませんが、そうすることで、Windows サービスを複数のマシンに展開する必要がなくなり、どちらの方法でもパフォーマンスに違いはないと思います。考え?

これまでにご意見をお寄せいただきありがとうございました。追加情報が必要な場合は、お知らせください。

4

2 に答える 2

6

私はあなたが今直面している正確な問題に取り組まなければなりませんでした。

まず、ASP.NET 内で実行時間の長いプロセスを確実に実行することは絶対にできないことを認識する必要があります。global.asax からスケジューラ クラスをインスタンス化すると、そのクラスの有効期間を制御できなくなります。

つまり、IIS は、クラスをホストするワーカー プロセスをいつでもリサイクルすることを決定できます。せいぜい、これはクラスが破棄されることを意味します (そして、それに対してできることは何もありません)。最悪の場合、作業の途中でクラスが殺されます。おっとっと。

有効期間の長いプロセスを実行する適切な方法は、マシンに Windows サービスをインストールすることです。データベースではなく、各 Web ボックスにサービスをインストールします。

Service は Quartz スケジューラーをインスタンス化します。このようにして、マシンが稼働している限り、スケジューラーが実行を継続することが保証されていることがわかります。IJobジョブを実行するとき、Quartzは指定したクラスのメソッドを呼び出すだけです。

class EmailSender : Quartz.IJob
{
    public void Execute(JobExecutionContext context)
    {
        // send your emails here
    }
}

Quartz はExecute別のスレッドでメソッドを呼び出すため、スレッドセーフになるように注意する必要があることに注意してください。

もちろん、複数のマシンで同じサービスを実行することになります。これを気にしているように聞こえますが、実際にはこれをプラスに活用できます。

私がしたことは、データベースに「ロック」列を追加することでした。送信ジョブが実行されると、ロック列を設定することにより、キュー内の特定の電子メールのロックを取得します。たとえば、ジョブの実行時に GUID を生成してから、次のようにします。

UPDATE EmailQueue SET Lock=someGuid WHERE Lock IS NULL LIMIT 1;
SELECT * FROM EmailQueue WHERE Lock=someGuid;

このようにして、データベース サーバーに同時実行性を処理させます。クエリは、キュー内のUPDATE1 つの電子メール (現在割り当てられていないもの) を現在のインスタンスに割り当てるように DB に指示します。次にSELECT、ロックされた電子メールを送信します。送信したら、キューから電子メールを削除し (または送信済み電子メールを処理します)、キューが空になるまでこのプロセスを繰り返します。

これで、次の 2 つの方向にスケーリングできます。

  • 複数のスレッドで同じジョブを同時に実行する。
  • これが複数のマシンで実行されているという事実のおかげで、すべてのサーバー間で送信作業の負荷を効率的に分散できます。

ロック メカニズムにより、複数のマシン上の複数のスレッドがすべて同じコードを実行している場合でも、キュー内の各電子メールが 1 回だけ送信されることを保証できます。


コメントへの回答:最終的に実装された実装にはいくつかの違いがあります。

まず、ASP アプリケーションは、キューに新しい電子メールがあることをサービスに通知できます。つまり、スケジュール通りに実行する必要さえなく、いつ作業を開始するかをサービスに指示するだけで済みます。ただし、この種の通知メカニズムを分散環境で正しく取得するのは非常に難しいため、1 分ごとにキューをチェックするだけで問題ありません。

使用する間隔は、メール配信の時間感度によって異なります。メールをできるだけ早く配信する必要がある場合は、30 秒またはそれ以下の間隔でトリガーする必要がある場合があります。緊急性がなければ、5 分ごとに確認できます。Quartz は一度に実行するジョブの数を制限し (構成可能)、トリガーが失敗した場合にどうするかを構成できるため、何百ものジョブがバックアップされることを心配する必要はありません。

次に、DB サーバーのクエリ負荷を軽減するために、実際に一度に 5 通の電子メールをロックします。私は大量に扱うので、これは効率に役立ちました (サービスと DB 間のネットワーク ラウンドトリップが少なくなりました)。ここで注意すべきことは、電子メールのグループを送信している最中にノードがダウンした場合 (何らかの理由で、例外からマシン自体のクラッシュまで) に何が起こるかです。DB に「ロックされた」行が残り、それらにサービスを提供するものは何もありません。グループのサイズが大きいほど、このリスクは大きくなります。また、残りのすべての電子メールがロックされている場合、アイドル状態のノードは明らかに何も機能しません。

スレッド セーフに関しては、一般的な意味での意味です。Quartz はスレッド プールを維持するため、スレッド自体の実際の管理について心配する必要はありません。

ジョブ内のコードが何にアクセスするかに注意する必要があります。経験則として、ローカル変数は問題ないはずです。ただし、関数の範囲外にアクセスする場合は、スレッド セーフが問題になります。例えば:

class EmailSender : IJob {
    static int counter = 0;

    public void Execute(JobExecutionContext context) {
        counter++; // BAD!
    }
}

counter複数のスレッドが同時にアクセスしようとする可能性があるため、このコードはスレッドセーフではありません。

Thread A           Thread B
Execute()
                   Execute()
Get counter (0)
                   Get counter (0)
Increment (1)
                   Increment (1)
Store value
                   Store value

            counter = 1 

counter 2 である必要がありますが、代わりに競合状態をデバッグするのが非常に困難です。次にこのコードを実行すると、次のようになる可能性があります。

Thread A           Thread B
Execute()
                   Execute()
Get counter (0)
Increment (1)
Store value
                   Get counter (1)
                   Increment (2)
                   Store value

            counter = 2

...そして、今回はなぜうまくいったのか頭を悩ませています。

あなたの特定のケースでは、呼び出しのたびに新しいデータベース接続を作成し、Executeグローバルデータ構造にアクセスしない限り、問題ありません。

于 2013-02-06T20:17:22.333 に答える
1

アーキテクチャについてより具体的にする必要があります。電子メール キューはどこにありますか。メモリまたはデータベースで?それらがデータベースに存在する場合、「処理中」という名前のフラグ列を持つことができます。タスクがキューからメールを取得すると、現在処理されていないメールのみが取得され、取得したメールの処理フラグが true に設定されます。次に、同時実行の問題をデータベースに任せます。

于 2013-02-06T18:44:15.423 に答える