
hangfire.ioからの画像
Hangfireは、.NETテクノロジースタック(主にTask Parallel LibraryとReflection)を使用して、クライアントサーバーアーキテクチャ上に構築されたマルチスレッドでスケーラブルなタスクスケジューラであり、データベースにタスクの中間ストレージがあります。 無料(LGPL v3)オープンソースバージョンで完全に機能します。 この記事では、Hangfireの使用方法について説明します。
記事の概要:
動作原理
ポイントは何ですか? 公式ドキュメントから正直にコピーしたKDPVを見るとわかるように、クライアントプロセスはデータベースにタスクを追加し、サーバープロセスは定期的にデータベースをポーリングしてタスクを実行します。 重要なポイント:
- クライアントとサーバーを接続するのは、タスククラスが宣言されている共通データベースと共通アセンブリへのアクセスだけです。
- 負荷のスケーリング(サーバーの数の増加)-はい!
- データベース(タスクストレージ)がないと、Hangfireは機能せず、機能しません。 デフォルトでは、SQL Serverがサポートされており、多くの一般的なDBMSの拡張機能があります。 有料版は、Redisのサポートを追加します。
- ASP.NETアプリケーション、Windowsサービス、コンソールアプリケーションなど、何でもHangfireのホストとして機能できます。 Azure Worker Roleに至るまで。
クライアントの観点から見ると、タスクの処理は「ファイアアンドフォーゲット」の原則に基づいて行われます。より正確には、「キューに追加されて忘れられました」-クライアントにタスクをデータベースに保存する以外は何も起こりません。 たとえば、別のプロセスでMethodToRunメソッドを実行します。
BackgroundJob.Enqueue(() => MethodToRun(42, "foo"));
このタスクは、入力パラメーターの値とともにシリアル化され、データベースに保存されます。
{ "Type": "HangClient.BackgroundJobClient_Tests, HangClient, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Method": "MethodToRun", "ParameterTypes": "(\"System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\",\"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\")", "Arguments": "(\"42\",\"\\\"foo\\\"\")" }
この情報は、それが宣言されているHangClientアセンブリにアクセスする場合、Reflectionを介して別のプロセスでMethodToRunメソッドを呼び出すのに十分です。 当然、バックグラウンド実行のコードをクライアントと同じアセンブリに保持することは完全にオプションです。一般的な場合、依存関係スキームは次のとおりです。

クライアントとサーバーは一般的なアセンブリにアクセスできる必要がありますが、埋め込みWebインターフェイス(少し後で)へのアクセスはオプションです。 必要に応じて、サーバーアプリケーションが参照するアセンブリを置き換えることにより、データベースに既に保存されているタスクの実装を置き換えることができます。 これはスケジュールされたタスクには便利ですが、もちろん、古いアセンブリと新しいアセンブリのMethodToRunコントラクトが完全に同一であれば機能します。 メソッドの唯一の制限は、パブリック修飾子の存在です。
オブジェクトを作成してそのメソッドを呼び出す必要がありますか? Hangfireは私たちのためにそれをします:
BackgroundJob.Enqueue<EmailSender>(x => x.Send(13, "Hello!"));
また、必要に応じて、DIコンテナーを介して EmailSenderのインスタンスを受け取ります。
サーバーを展開する場所はありません(たとえば、別のWindowsサービス)。
public partial class Service1 : ServiceBase { private BackgroundJobServer _server; public Service1() { InitializeComponent(); GlobalConfiguration.Configuration.UseSqlServerStorage("connection_string"); } protected override void OnStart(string() args) { _server = new BackgroundJobServer(); } protected override void OnStop() { _server.Dispose(); } }
サービスの開始後、Hangfireサーバーはデータベースからタスクを取り出して実行し始めます。
オプションで使用できますが、タスクの処理を制御できる組み込みのWebダッシュボードが便利で非常に快適です。

Hangfireサーバーの内部と機能
まず、サーバーには、タスクパラレルライブラリを介して実装された独自のスレッドプールが含まれています。 そして、基礎はよく知られているTask.WaitAllです( BackgroundProcessingServerクラスを参照)。
水平スケーリング? Webファーム? ウェブガーデン? サポートされています:
バックグラウンド処理で追加のスレッドプールスレッドを使用したくない場合-Hangfire Serverは、カスタムの個別の制限されたスレッドプールを使用します。
Web FarmまたはWeb Gardenを使用しており、同期の問題に直面したくない-Hangfire ServerはデフォルトでWeb Garden / Web Farmに対応しています。
任意の数のHangfireサーバーを作成でき、それらの同期については考えません。Hangfireは、1つのタスクが1つのサーバーのみで実行されるようにします。 実装例は、sp_getapplockの使用です( SqlServerDistributedLockクラスを参照)。
既に述べたように、Hangfireサーバーはホストプロセスを必要とせず、コンソールアプリからAzure Webサイトまでどこにでも展開できます。 ただし、全能ではないため、ASP.NETでホストする場合、 プロセスリサイクル 、 自動開始 (startMode =“ AlwaysRunning”)など、IISの多くの一般的な機能を考慮する必要があります。 ただし、 プランナーのドキュメントには、この場合の包括的な情報も記載されています。
ちなみに! 私は文書の品質に注目せざるを得ません-それは賞賛を超えており、理想的な地域のどこかにあります。 Hangfireのソースコードはオープンで高品質であり、ローカルサーバーを持ち上げたり、デバッガーでコードを歩き回ったりするのに障害はありません。
繰り返し可能な遅延タスク
Hangfireでは、1分あたりの最小間隔で繰り返し可能なタスクを作成できます。
RecurringJob.AddOrUpdate(() => MethodToRun(42, "foo"), Cron.Minutely);
タスクを手動で実行するか、削除します。
RecurringJob.Trigger("task-id"); RecurringJob.RemoveIfExists("task-id");
タスクを延期します。
BackgroundJob.Schedule(() => MethodToRun(42, "foo"), TimeSpan.FromDays(7));
CRON式を使用して、繰り返しAND遅延タスクを作成できます (サポートはNCrontabプロジェクトを通じて実装されます)。 たとえば、次のタスクは毎日午前2時15分に実行されます。
RecurringJob.AddOrUpdate("task-id", () => MethodToRun(42, "foo"), "15 2 * * *");
Quartz.NET Microview
特定のタスクスケジューラに関するストーリーは、価値のある代替案に言及せずに不完全になります。 .NETプラットフォームでは、Javaの世界からのQuartzスケジューラーの移植版であるQuartz.NETはそのような代替手段です。 Quartz.NETは、Hangfireなどの同様の問題を解決します。共通データベースを使用して、任意の数の「クライアント」(タスクの追加)と「サーバー」(タスクの実行)をサポートします。 しかし、実行は異なります。
Quartz.NETを初めて知った人は成功とは言えませんでした-正式にGitHubリポジトリから取得したソースコードは、いくつかの不足しているファイルとアセンブリへのリンクを手動で調整するまでコンパイルされませんでした(免責事項:方法を説明します) プロジェクトではクライアント部分とサーバー部分の分離はありません-Quartz.NETは単一のDLLとして配布されます。 特定のアプリケーションインスタンスでタスクの追加のみを許可し、タスクの実行は許可しないようにするには、 設定する必要があります 。
Quartz.NETは完全に無料で、インメモリと多くの一般的なDBMS(SQL Server、Oracle、MySQL、SQLiteなど)を使用したタスクのストレージを提供します。 インメモリストレージは、基本的に、タスクを実行する単一のサーバープロセスのメモリ内にある通常の辞書です。 データベースにタスクを保存する場合、複数のサーバープロセスのみを実装できます。 同期のために、Quartz.NETは特定のDBMS(SQL Serverの同じアプリケーションロック)の実装の特定の機能に依存せず、1つの一般化されたアルゴリズムを使用します。 たとえば、QRTZ_LOCKSテーブルに登録することにより、特定の一意のIDを持つ1つ以上のスケジューラプロセスの1回限りの操作が保証され、QRTZ_TRIGGERSテーブルのステータスを変更するだけでタスクが発行されます。
Quartz.NETのタスククラスは、IJobインターフェイスを実装する必要があります。
public interface IJob { void Execute(IJobExecutionContext context); }
このような制限があるため、タスクをシリアル化するのは非常に簡単です。完全なクラス名はデータベースに保存されます。これは、Type.GetType(名前)を介してタスククラスのタイプを後で受け取るのに十分です。 タスクにパラメーターを渡すには、JobDataMapクラスが使用され、既に保存されているタスクのパラメーターを変更できます。
マルチスレッドに関しては、Quartz.NETはSystem.Threading名前空間のクラスを使用します:new Thread()( QuartzThreadクラスを参照)、そのスレッドプール、Monitor.Wait / Monitor.PulseAllによる同期。
タールのかなりのスプーンは、公式文書の品質です。 たとえば、ここにクラスタリングに関する資料があります: レッスン11:高度な(エンタープライズ)機能 。 はい、はい、それはこのトピックに関する公式ウェブサイトにあります。 広大なSOのどこかで、 オリジナルのQuartzのガイドも参照する魅惑的なアドバイスがありました。そこで、トピックはより詳細に開示されています。 Javaと.NETの両方の世界で同様のAPIをサポートしたいという開発者の欲求は、開発の速度に影響を及ぼします。 Quartz.NETのリリースと更新はまれです。
クライアントAPIの例:繰り返されるHelloJobタスクを登録します。
IScheduler scheduler = GetSqlServerScheduler(); scheduler.Start(); IJobDetail job = JobBuilder.Create<HelloJob>() .Build(); ITrigger trigger = TriggerBuilder.Create() .StartNow() .WithSimpleSchedule(x => x .WithIntervalInSeconds(10) .RepeatForever()) .Build(); scheduler.ScheduleJob(job, trigger);
考慮される2人のプランナーの主な特徴は、表にまとめられています。
特徴 | ハングファイア | Quartz.NET |
---|---|---|
無制限の数のクライアントとサーバー | はい | はい |
ソースコード | github.com/HangfireIO | github.com/quartznet/quartznet |
Nugetパッケージ | ハングファイア | クォーツ |
免許 | LGPL v3 | Apacheライセンス2.0 |
ホストはどこですか | Web、Windows、Azure | Web、Windows、Azure |
タスクストレージ | SQL Server(デフォルト)、 拡張機能を介した多数のDBMS 、Redis(有料版) | インメモリ、多数のデータベース(SQL Server、MySQL、Oracleなど) |
マルチスレッド実装 | Tpl | スレッド、モニター |
Webインターフェース | はい | いや 将来のバージョンで計画されています。 |
遅延タスク | はい | はい |
繰り返し可能なタスク | はい(最小間隔1分) | はい(最小間隔1ミリ秒) |
cron式 | はい | はい |
更新: ShurikEvがコメントで正しく指摘したように、Quartz.NETのWebインターフェイスが存在します: github.com/guryanovev/CrystalQuartz
プロ(非)ストレステスト
Hangfireが多数のタスクにどのように対処するかを確認する必要がありました。 言うよりすぐに、私は0.2秒の間隔でタスクを追加する簡単なクライアントを書きました。 各タスクは、デバッグ情報を含む行をデータベースに書き込みます。 クライアントに100Kのタスク制限を設定した後、2つのクライアントインスタンスと1つのサーバー、およびプロファイラー(dotMemory)を備えたサーバーを起動しました。 6時間後、Hangfireで正常に完了した200Kのタスクと、データベースに200Kの追加された行が待っていました。 スクリーンショットは、プロファイリングの結果を示しています-実行前と実行後のメモリステータスの2つのスナップショット:

次の段階では、20のクライアントプロセスと20のサーバープロセスが既に実行されていたため、タスクの実行時間が増加し、ランダム変数になりました。 それはHangfireについてのもので、まったく反映されていません。

結論 調査。
個人的に、私はHangfireが好きでした。 分散システムの開発と保守のコストを削減する無料のオープンソース製品。 そのようなものを使用しますか? アンケートに参加し、コメントであなたの意見を述べてください。