はじめに
Webアプリをつくるときにどうしても問題になるのがレスポンスだと思います。
時間がかかる処理をWeb画面でポチッとした時に実行されるシステムを作る時、あなたならどうしますか?
30秒とかならまだユーザに待ってもらえるかも知れませんが、
5時間かかる処理だったらどうしましょう??
そこで有効な考え方がメッセージキューを使用した仕組みです。
APサーバーからはキューに「これやっとくように言っておいて」と伝えると、
キューの内容を購読しているワーカーが順次やっていくような仕組みです。
そんな時に使いやすいHangfireというフレームワーク?ライブラリ?を紹介します。
早く結論をくれ
今回の記事に載せた手順を経ると下記リポジトリのようなものができます。
環境
この記事の内容を試すのに使った環境は
- OS: Windows 10
- RAM: 8GB
です。
Hangfireとは?
ジョブのキューイング/実行ができるフレームワークです。
rabbitMQ
やActiveMQ
といったメッセージキュー系のソフトウェアを使用すれば
同じことは実現できますが、Hangfire
には以下の利点があります。
- .NET(NuGet)の巨大な恩恵を授かることができる
- ダッシュボードまで準備されているいたれりつくせりぶり
しかし、だいぶマイナーなので調べても日本語で書いてある解説が少ないのが難点でしょうか。
.NET Core(OSS版.NET Framework)公開で敷居が下がった
Microsoftがオープンソース版.NET Frameworkであるところの.NET Core
を公開しました。
Hangfireは.NET Coreにも対応しており、これによってだいぶ敷居が下がっています。が、依然マイナーですね。
まだ更新されているのか?
私は4年ぐらい前からHangfireを使用しているんですが、まだ開発は続いています。
早速入門する
dotnet sdk のインストール
下記リンクからdotnet sdk 3.1をダウンロード/インストールしてください。
https://dotnet.microsoft.com/download
インストールが完了すると以下のようにdotnetコマンドを使用可能になります。
$ dotnet --version
3.1.100
ソリューションディレクトリの作成
今回はclient
プロジェクトとserver
プロジェクトを作ろうと思うので、
それぞれを束ねるソリューションを作成します。
$ dotnet new sln -o hangfire-dotnet3
The template "Solution File" was created successfully.
$ cd hangfire-dotnet3 # ソリューションのディレクトリに移動
$ ls
hangfire-dotnet3.sln
各プロジェクトの追加
先程作成したソリューションにclient
プロジェクトを追加します
$ dotnet new webapp -o client
The template "ASP.NET Core Web App" was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/aspnetcore/3.1-third-party-notices for details.
f
Processing post-creation actions...
Running 'dotnet restore' on client\client.csproj...
C:\dev\hangfire-dotnet3\client\client.csproj の復元が 165.82 ms で完了しました。
Restore succeeded.
$ dotnet sln add client # プロジェクトの追加
プロジェクト `client\client.csproj` をソリューションに追加しました。
同様にserver
プロジェクトも追加します
$ dotnet new console -o server
$ dotnet sln add server
上記工程で client, server フォルダにそれぞれテンプレートが展開されたと思います。
※注意
HangfireではジョブをキューするUIを提供するWebサーバ/APサーバを client
と呼びます。
一方で、キューされたジョブを待ち受け、順次実行するワーカを server
と呼びます。
よく混同するので注意!
JobStorageの準備
キューとして使えるものは以下のページの Storage
セクションにあります。
今回はmongoDB使っていきたいと思います。
docker run -d -p 27017:27017 mongo:4.0-xenial
終わり。
(docker使えない場合は https://www.mongodb.com/download-center/community?jmp=docs からインストールすると良いと思います。)
Serverの実装
必須パッケージをインストールする
$ cd server
$ dotnet add package Hangfire.Core -v 1.7.8
$ dotnet add package Hangfire.Mongo -v 0.6.5
すると server/server.csproj
ファイルに依存パッケージが追記されます。
server/Program.cs
には以下のようにコードを書いて、先程Dockerで立てたmongoを参照するように設定します。
using System;
using Hangfire;
using Hangfire.Mongo;
namespace server {
class Program {
static void Main(string[] args) {
GlobalConfiguration.Configuration
.UseMongoStorage("mongodb://localhost", "ApplicationDatabase");
using(var server = new BackgroundJobServer()) {
Console.WriteLine("Started BackgroundJobServer. Press Enter to exit.");
Console.ReadLine();
}
}
}
}
サーバーはこれだけで完成です。
$ dotnet run .
コマンドで以下のような表示が出ればオッケーです。
Clientの実装
Clientにも同様に必須パッケージをインストールします。
$ cd client
$ dotnet add package Hangfire -v 1.7.8
$ dotnet add package Hangfire.AspNetCore -v 1.7.8
$ dotnet add package Hangfire.Mongo -v 0.6.5
次に、client/StartUp.cs
を編集します。
public void ConfigureServices(IServiceCollection services) {
services.AddRazorPages();
services.AddHangfire(config => {
string connectionString = "mongodb://localhost"; # 本来は環境変数で指定する
string databaseName = "ApplicationDatabase"; # 本来は環境変数で指定する
var storageOptions = new MongoStorageOptions {
MigrationOptions = new MongoMigrationOptions {
Strategy = MongoMigrationStrategy.Drop,
},
};
config.UseMongoStorage(connectionString, databaseName, storageOptions);
});
}
本来は接続文字列とかは環境変数で指定できるように変更しておくべきかと思ったんですが、
とりあえずサンプルということでざざっと直書き。
ここまで書いて以下のコマンドでClient
を起動します。
$ cd client
$ dotnet run .
そしてブラウザで http://localhost:5000/hangfire にアクセスすると以下のようなダッシュボードが開きます。
Producerの実装
上記までの手順で、タスク管理して実行する機構ができました。
次はタスクを実際にキューするアプリを作成します。
$ dotnet new console -o app
$ cd app
$ dotnet add package Hangfire.Core -v 1.7.8
$ dotnet add package Hangfire.Mongo -v 0.6.5
app/Program.cs ファイルを編集します
using System;
using System.Threading;
using Hangfire;
using Hangfire.Mongo;
namespace app {
class Program {
static void Main(string[] args) {
GlobalConfiguration.Configuration
.UseMongoStorage("mongodb://localhost", "ApplicationDatabase");
int Cnt = 10;
for (int i = 0; i < Cnt; i++) {
BackgroundJob.Enqueue(() => Console.WriteLine($"task #{i}"));
Console.WriteLine($"Enqueued task#{i}");
}
}
}
}
単純にfor文で10個タスクをキューに入れていきます。
$ dotnet run .
Enqueued task#0
Enqueued task#1
Enqueued task#2
Enqueued task#3
Enqueued task#4
Enqueued task#5
Enqueued task#6
Enqueued task#7
Enqueued task#8
Enqueued task#9
実行
client
と server
を起動した状態で app
を実行してみましょう。
server
を複数実行させてみると分散されて実行されている様子が確認できます。
ダッシュボードはこんな感じ
ジョブ履歴もこのように参照することができます。
終わりに
Hangfire
というジョブをキューしてワーカーに実行させるフレームワークについて紹介しました。
これだけで分散実行してダッシュボードで管理できるのはすごく便利です。
今回はConsole.WriteLineするだけという単純なジョブ実行でしたが、
共通ライブラリでクラスを定義することでもっと複雑な処理をさせることが可能です。
また、今回は「ダッシュボード」「ワーカー」「タスクのキューイング実行」と3構成で実装しましたが、
ダッシュボードアプリにコントローラを実装して、APIを通してキューイングさせるような実装も可能です。