有功能讓使用者等到火大嗎?
可以使用 Hangfire 來處理非同步的工作哦!
問題
在前一篇「好用的 HostingEnvironment.QueueBackgroundWorkItem」,
有使用 QBWI 來處理非同步,但是如果發生錯誤要自已去處理。
也想要 使用 Polly 來讓程式有 Retry 的機制 ,但似乎也有許多的地方要自行處理的。
所以就改用 Hangfire 來試看看,主要是因為他會先將要執行的 Task 存到 DB中,然後再去執行它,而且還提供Dashboard。
下圖是筆者要解的問題,主要是表單系統呼叫Workflow起單時,需要花費太多的時間,所以就將起Workflow Task放到 Hangfire 裡面去。
實作
1.建立 Hangfire 所需的Table。
從「Hangfire.SqlServer/Install.sql」拿 Script Run 到 DB中(我是在WebAP中安裝 SQL Express 來放 Hangfire的資料),或是給有建立Table的帳號,執行 Hangfire會幫你建立這些Table。
2.在現有的 Workflow Services Application 使用 Hangfire 。
2.1.將版本調整成 .NET 4.5 (Hangfire最新版是使用.NET 4.5,也有 .NET 4.0版本的)
2.2.安裝 Hangfire.Core 及 Hangfire.SqlServer
因為只是要將 Task 寫進 Hangfire 所以只要安裝這2個 package 就可以了。
2.3.在 Global.asax.cs 的 Application_Start 設定 Hangfire 使用的DB連線字串 (因為我們要將Task寫到Hangfire去),如下,
protected void Application_Start(object sender, EventArgs e) { GlobalConfiguration.Configuration .UseSqlServerStorage("連到hangfire的DB連線名稱 or 字串"); }
2.4.在需要執行 Task 的地方,呼叫 BackgroundJob.Enqueue (如果呼叫端需要回傳值的話,可以做一個假的回給它),如下,
BackgroundJob.Enqueue(() => StartMyTask(apid, startInfo, extendInfo)); //使用 Hangfire 去執行的話, nested obj 會 parse 不出來,所以用字串傳遞 public void StartMyTask(string arg1, string arg2, string arg3) { // 要執行的事項 }
這裡有幾個部份要注意,
2.4.1.要執行的 Task,用一個 Method 封裝起來比較方便。
2.4.2.執行的 Method 參數最好是字串,如果是物件的話,不要是 nested 物件,這樣會Parse不出來,然後報錯哦!
3.把資料放到 Hangfire 後,就要建立Web API 專案來讓 Hangfire 去執行 Task 及提供 Dashboard。
3.1.新增一個名叫 HangfireDashboard 的 Web API 專案(.NET 4.5 以上)。
3.2.安裝 Hangfire 套件
3.3.再來依「Making ASP.NET application always running」方式來讓 HangfireDashboard 可執行 Task 及提供 Dashboard。
3.3.1.新增 HangfireBootstrapper Class
public class HangfireBootstrapper : IRegisteredObject { public static readonly HangfireBootstrapper Instance = new HangfireBootstrapper(); private readonly object _lockObject = new object(); private bool _started; private BackgroundJobServer _backgroundJobServer; private HangfireBootstrapper() { } public void Start() { lock (_lockObject) { if (_started) return; _started = true; HostingEnvironment.RegisterObject(this); GlobalConfiguration.Configuration .UseSqlServerStorage("連到hangfire的DB連線名稱 or 字串") .UseNLogLogProvider(); // 也可以使用 NLog, 請記得安裝 NLog 及 NLog config 哦 // 建立Background JobSserver 來處理 Job _backgroundJobServer = new BackgroundJobServer(); } } public void Stop() { lock (_lockObject) { if (_backgroundJobServer != null) { _backgroundJobServer.Dispose(); } HostingEnvironment.UnregisterObject(this); } } void IRegisteredObject.Stop(bool immediate) { Stop(); } }
3.3.2.新增 ApplicationPreload Class (詳細可以參考:Speeding up your application with the IIS Auto-Start feature )
public class ApplicationPreload : System.Web.Hosting.IProcessHostPreloadClient { public void Preload(string[] parameters) { HangfireBootstrapper.Instance.Start(); } }
3.3.3.在 global.asax.cs 中,去啟動或停止 BackgroundJob
public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); System.Web.Http.GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); //啟動 BackgroundJob HangfireBootstrapper.Instance.Start(); } protected void Application_End(object sender, EventArgs e) { //停止 BackgroundJob HangfireBootstrapper.Instance.Stop(); } }
3.3.4.新增 Startup 來設定啟用 Dashboard。
public class Startup { public void Configuration(IAppBuilder app) { //使用 Dashboard,可以設定顯示 dashboard 的 path app.UseHangfireDashboard("/ds"); } }
3.3.5.在 namespace 上面加入 OwinStartup 的設定
[assembly: OwinStartup(typeof(HangfireDashboard.Startup))]
註:如果您要設定 Access Dashboard 的權限,可以實作 Hangfire.Dashboard.IAuthorizationFilter ,如下,
public class AllowAllAuthorizationFilter : Hangfire.Dashboard.IAuthorizationFilter { public bool Authorize(IDictionary<string, object> owinEnvironment) { //比較使用都的資訊,驗證OK就回傳true return true; } } //然後,在 Startup Class 裡使用它,如下, public class Startup { public void Configuration(IAppBuilder app) { //使用 Dashboard,並任何人 Access 它 app.UseHangfireDashboard("/ds", new DashboardOptions { AuthorizationFilters = new[] { new AllowAllAuthorizationFilter() } }); } }
HangfireDashboard 建立好了之後,就可以開起來看一下狀況。
使用狀況
Hangfire如果Job有錯誤的話,會自動 Retry 10 次,而每次 Retry 的時間間隔會逾來逾久。超過10次後,就會留在 Failed 等待手動將它移進 Queue 之中,或是將它刪除,如下,
Job執行成功之後,會自動清 Detail 的資料。如下圖,雖然有94筆成功,但詳細資料的Job目前只留 8 筆而已,
而預設詢問Job是 15 秒,也可以依狀況來調整,如下我使用1分鐘,
var options = new BackgroundJobServerOptions { SchedulePollingInterval = TimeSpan.FromMinutes(1) }; var server = new BackgroundJobServer(options);
其他例如平行處理的數量(預設是 處理器數目 * 5)、Retry試次數也都是可以調整的哦!
在實作的過程中,主要遇到要如何切出 Task 放到 Hangfire 之中,以及放進 Hangfire 之前需要先做一些卡關的作業。
例如,請假需要先將假扣掉後,才把 Task 放進去。而批示時,也要先將資訊記到別的Table去,讓批示著認為已經批好了。
再來就是要考慮到如果Job真的無法成功時,對應的補償的機制。
參考資料
Processing jobs in a web application
Hangfire.SqlServer/Install.sql
[ASP.NET]好用的 HostingEnvironment.QueueBackgroundWorkItem
Hi,
亂馬客Blog已移到了 「亂馬客 : Re:從零開始的軟體開發生活」
請大家繼續支持 ^_^