私はあなたが今直面している正確な問題に取り組まなければなりませんでした。
まず、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;
このようにして、データベース サーバーに同時実行性を処理させます。クエリは、キュー内のUPDATE
1 つの電子メール (現在割り当てられていないもの) を現在のインスタンスに割り当てるように 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
グローバルデータ構造にアクセスしない限り、問題ありません。