Hangfire-.NET用タスクスケジューラ

Hangfireデザイン

hangfire.ioからの画像



Hangfireは、.NETテクノロジースタック(主にTask Parallel LibraryとReflection)を使用して、クライアントサーバーアーキテクチャ上に構築されたマルチスレッドでスケーラブルなタスクスケジューラであり、データベースにタスクの中間ストレージがあります。 無料(LGPL v3)オープンソースバージョンで完全に機能します。 この記事では、Hangfireの使用方法について説明します。



記事の概要:





動作原理



ポイントは何ですか? 公式ドキュメントから正直にコピーしたKDPVを見るとわかるように、クライアントプロセスはデータベースにタスクを追加し、サーバープロセスは定期的にデータベースをポーリングしてタスクを実行します。 重要なポイント:



クライアントの観点から見ると、タスクの処理は「ファイアアンドフォーゲット」の原則に基づいて行われます。より正確には、「キューに追加されて忘れられました」-クライアントにタスクをデータベースに保存する以外は何も起こりません。 たとえば、別のプロセスで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についてのもので、まったく反映されていません。

ダッシュボード-2kk



結論 調査。



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



All Articles