C#-初识Hangfire
Hangfire 分布式后端作業調度框架服務
- 概述
- 特點
- 版本
- 基本結構
- 基本功能及使用
- 基本功能的部分對象解析
- 項目引入
- 拓展
- MySqlStorageOptions 數據庫配置項
- BackgroundJobServerOptions 服務端配置項
- DashBoard 頁面配置權限認證(登陸)
- Hangfire的數據庫分析(MySql)
- 使ASP.NET應用程序始終運行
- 開發過程需要注意的點
- 體驗得到的一些缺點/注意點
- 留疑/思索
概述
分布式后端作業調度框架服務,我們只需要關心業務邏輯代碼,而不用關心調度機制持。
官方原文:在.NET和.NET Core應用程序中執行后臺處理的簡單方法。無需Windows服務或單獨的進程。免費開源且可用于商業應用。Easy to set up, easy to use。
Hangfire支持久存儲支,存儲方式可支持sqlserver、redis,mongodb等等。
Hangfire支持所有類型的后臺任務 - 短時間運行和長時間運行,CPU密集型和I/O密集型,一次性和周期性。
官網:www.hangfire.io
特點
簡單 Simple
易于設置易于使用。沒有windows服務,沒有windows計劃策劃稿內需,不需要單獨的應用程序。
后臺作業是常規靜態或帶有常規參數的實例.NET方法-不需要基類或者接口實現。
PS:啟動和結束依托于宿主項目。可以正常的調用項目內部的方法,無需特意實現某個類或接口。實施上,它會拿取到你的方法的引用來源并和參數一起保存下來,空間名方法名項目名等。
可靠 Reliable
一旦創建了后臺作業而沒有任何異常,Hangfire將負責至少處理它一次。
您可以自由的拋出未處理的異常或終止您的應用程序 - 后臺作業將會自動重新嘗試。
PS:只要可以正常放入計劃中,就一定能執行。當然,它沒提到能否精確執行。事實上很多情況下會導致重復執行(某一任務執行時間過長)或不執行(多任務時間要求過于緊湊)或未按時執行(如網站被回收后又重新激活,此時若錯過了執行時間,但是依舊會在激活第一時間執行)。
高效 Efficient
雖然默認安裝使用SQL Server和輪詢技術來獲取作業,但您可以利用MSMQ或Redis擴展將處理延遲降低至最低。
PS:很顯然是相對而言。
持久化 Persistent
后臺作業時在永久存儲創建-SQL服務器,Redis,PostgreSQL,MongoDB或其它。
您可以安全的重新啟動應用程序并在ASP.NET中使用Hangfire,而無需擔心應用程序池的循環使用。
PS:與RbaitMQ的消息持久化存儲類似,實際上只要想達到永久存儲肯定要有硬盤參與。
分布式 Distributed
后臺方法調用以及其參數被序列化可以克服過程邊界。
您可以在不同的計算機上使用hangfire以獲得更多處理能力而無需配置 - 自動執行同步。
PS:參數全部被序列化的。分布式可以用是可以用但是并不智能,也無法自己開發服務端平衡機制。簡單易用但也死板。
自我維護 Self-maintainable
您無需執行手動存儲清理 - hangfire會盡可能保持清潔并自動刪除舊紀錄。
PS:就是盡量少占用數據庫空間。而且對于異常信息的記錄也并不詳細。
透明 Transparent
內置的web睫毛允許您查看后臺處理的整個畫面,以及觀察每個后臺作業的狀態。
對流行的日志框架的開箱即用支持允許您在零配置的早期捕獲異常。
PS:有個簡單的可視窗口頁面。
擴展 Extensible
作業篩選器允許您以類似于ASP.NET MVC操作篩選器的方式向后臺處理添加自定義功能。
PS:應該很有用,但是目前尚未使用過。
開源 Open source
Hangfire是開源軟件,完全免費用于商業用途,它是根據LGPLv3許可證授權的。
GitHub。
版本
截止目前(2019-02-20)最新穩定版為1.6.22。
當前項目使用版本為1.6.17。
自從1.6+版本后開始支持NetCore。
1.6.17-1.6.22之間基本為對Hangfire.Core的升級。
官網:各版本列表
基本結構
核心組件:客戶端,服務端,持久化存儲。這些組件既可以分開部署在不同項目中也可以都部署在一個項目中。
客戶端: 創建任務–>1、配置HangFire數據庫連接 2、創建任務(在創建任務的時候HangFire會自動將任務序列化并存儲到數據)。
服務端: 執行任務–>1、配置HangFire數據庫連接 2、從HangFire數據庫系統表讀取客戶端創建的任務然后開線程并行執行,任務之間不沖突。(服務端可宿主在Windows服務、控制臺程序、IIS中…)。
數據庫(持久化存儲): HangFire程序框架表–>創建任務的時候HangFire會自動生成無需關心,但要注意如果采用現有的數據庫,必須保證數據庫中沒有重名的表(aggregatedcounter、counter、distributedlock、hash、job、jobparameter、jobqueue、jobstate、list、server、set、state),數據庫可采用 MySQL ,MSSQL,Redis視需要而定。
儀表盤(管理面板): 展示作業列表執行狀態和結果等相關信息–>在.Net WebForm或.Net MVC 或.NetCore MVC網站程序對接HangFire數據庫
組件之間關系圖:
組件特點:
組件功能:
部署上述組件的方案(來自參考1):
- 方案一(不推薦):客戶端A、服務端B、儀表盤C 分別運行在3個獨立的項目中。
- 優缺點:沒什么優點,而且AB分開會導致服務端B從數據庫提取任務列表,準備反向加載程序集執行任務的時候,在自己的程序代碼中找不到對應的業務邏輯代碼或者引用類,因為業務邏輯代碼是在客戶端A項目中創建,自然業務邏輯類及功能代碼都在A項目的程序集中,自然找不到。
- 讓服務端B也引用YourOwnJobLibrary.dll即可,但是這并不是我們想要的結果,以后更新服務什么的還得兩頭更新很麻煩。
- 方案二(推薦):客戶端A、服務端B在同一個項目中,儀表盤C獨立網站項目中。
- 優缺點:這樣比較合理,更新服務方便,即便沒有儀表盤C,作業正常調度執行;適合后臺相對固定的作業。
- 可以將AB放在Windows服務項目中(VS中可以創建),這樣系統重啟什么的毫無顧慮,更新服務程序的時候只需停止服務->替換程序->開啟服務即可。
- 方案三(中等):客戶端A、服務端B、儀表盤C在同一網站項目只能是網站項目,因為儀表盤只能在Web項目(API、WebForm、MVC )中。
- 優缺點:視情況也,如果宿主在IIS中,IIS默認20分鐘沒有人訪問會停止,HangFire作業服務也會定制,可將此時間在IIS配置中延長,比較適合任務經常需要靈活變動處理的場景。
備注(來自參考1):
基本功能及使用
使用:
引用命名空間。
- Fire-and-forget jobs(基于隊列的任務處理)
- Delayed jobs(延遲任務執行)
- Recurring jobs(定時任務執行)
- Continuations(延續性任務執行)
- Batches(批處理) | Pro 版
- Batch Continuations(延續性批處理) | Pro 版
基本功能的部分對象解析
- RecurringJob.AddOrUpdate()
該方法用于定期作業在指定的CRON計劃上觸發多次。該方法具有16個重載。
Job.FromExpression(methodCall) 用于獲取基于Job類的新實例給定的方法調用的表達式樹。
GetRecurringJobId(job)方法根據Job對象獲取對應的JobID。
我們常用這個方法來增加定時執行的任務,實際使用實例:
// 使用實例 RecurringJob.AddOrUpdate(() => remindService.PushNoSubmittedDailyReportMsg(), Cron.Daily(8), TimeZoneInfo.Local, bgOption.Queues[0]); // 每天早8點 RecurringJob.AddOrUpdate(() => remindService.PushNoSubmittedWeeklyReportMsg(), Cron.Weekly(DayOfWeek.Monday, 8), TimeZoneInfo.Local, bgOption.Queues[0]); // 每周周一早八點 RecurringJob.AddOrUpdate(() => remindService.PushNoSubmittedMonthlyReportMsg(), Cron.Monthly(1, 8), TimeZoneInfo.Local, bgOption.Queues[0]); // 每月1日早8點- BackgroundJob.Enqueue()
請注意,該方法的名稱為’Enqueue’,而不是’Call’,'Invoke’等。選擇該方法的名稱是為了強調給定方法的調用僅在當前執行上下文中排隊,并且在排隊之后立即將控制返回給調用線程。
它會在不同的執行上下文中調用。What does this mean?有些事情會脫離你對方法調用過程的通常期望。你應該知道他們。
(上面兩段文字來自官網文檔中的一個文章連接,也許是來著開發者的善意提醒。這篇文章也是一篇有助于理解hangfire的文章。See More)
該方法基于給定的方法調用表達式創建一個新的fire-and-forget作業。該方法接受一個參數,表示將被編組到服務器的方法調用表達式。接下來我們看一下var client = ClientFactory();方法的具體實現
內部源碼:
該屬性定義了一個Func的泛型委托。該屬性是一個可讀可寫的操作,對ClientFactoryLock加鎖,確保不發生死鎖情況。
我們常用這個方法來在后端服務中新增執行的任務,實際使用實例:
// 使用實例 var jobId = BackgroundJob.Enqueue(() => throwExTest(throwEx));- BackgroundJob.ContinueWith()
該方法用于新增一個延續性作業,即當傳入的作業id的作業執行完畢后,才會執行該作業。
使用該方法要注意的是,其等待執行完畢的上一個作業,如果未成功執行完畢,因為拋異常等原因導致仍在計劃中、等待狀態的話,該作業就會一直處于等待狀態。只有當上一個作業成功執行完畢后,才會將該作業由等待狀態轉為計劃中,等待被執行。
項目引入
我新建了一個空白的,ASP.NET Web + MVC空白項目,在該項目上進行的步驟。
演示結果如下:
備注:
<add key="owin:AutomaticAppStartup" value="true" />。
拓展
MySqlStorageOptions 數據庫配置項
// 配置 MySqlStorageOptions var dbHangfire = WebConfigurationManager.ConnectionStrings["dbHangfire"].ToString(); GlobalConfiguration.Configuration.UseStorage(new MySqlStorage(dbHangfire, new MySqlStorageOptions {TransactionIsolationLevel = IsolationLevel.ReadCommitted, // 事務隔離級別。默認值為讀提交。QueuePollInterval = TimeSpan.FromSeconds(15), // 作業隊列輪詢間隔。默認值為15秒JobExpirationCheckInterval = TimeSpan.FromHours(1), // 作業過期檢查間隔(管理過期記錄)。默認為1小時CountersAggregateInterval = TimeSpan.FromMinutes(5), // 間隔到聚合計數器。默認為5分鐘PrepareSchemaIfNecessary = true, // 如果設置為true,則創建數據庫表。默認值為trueDashboardJobListLimit = 50000, // 儀表板作業列表上限。默認值為50000 TransactionTimeout = TimeSpan.FromMinutes(1), // 事務超時。默認為1分鐘 })); app.UseHangfireDashboard(); app.UseHangfireServer();BackgroundJobServerOptions 服務端配置項
// 配置 服務端配置項 var dbHangfire = WebConfigurationManager.ConnectionStrings["dbHangfire"].ToString(); GlobalConfiguration.Configuration.UseStorage(new MySqlStorage(dbHangfire)); app.UseHangfireDashboard(); var bgOption = new BackgroundJobServerOptions {ServerName = String.Format("{0}.{1}", Environment.MachineName, Guid.NewGuid().ToString()), //服務器唯一的標識符Queues = new string[] { "defult" }, // 自定義隊列WorkerCount = 5, //最大并行數SchedulePollingInterval = TimeSpan.FromMinutes(1), }; app.UseHangfireServer(bgOption);備注:
DashBoard 頁面配置權限認證(登陸)
概述:
Hangfire Dashboard為我們提供了可視化的對后臺任務進行管理的界面,我們可以直接在這個頁面上對定時任務進行刪除、立即執行等操作。
默認情況下,這個頁面只能在部署Hangfire的機器上進行訪問,想要在其他地方進行訪問,需要配置權限認證模塊:Hangfire.Dashboard.Authorization。
引用安裝:
通過Nuget程序包管理控制臺安裝的命令:通過Nuget程序包管理控制臺安裝的命令:
Install-Package Hangfire.Dashboard.Authorization
應用:
在Startup.cs中的Configuration方法中添加以下代碼:
在代碼中的Login和Password后面寫登錄的用戶名和密碼,這樣在下次打開Hangfire的Dashboard時,就會彈出需要輸入用戶名和密碼的窗口了,輸入之后就可了打開Dashboard了。
SHA1 hash密碼生成方式(來自參考4):
string password = "<your password here>"; using (var cryptoProvider = System.Security.Cryptography.SHA1.Create()) {byte[] passwordHash = cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(password));string result = "new byte[] { " + String.Join(",", passwordHash.Select(x => "0x" + x.ToString("x2")).ToArray())+ " } "; }實際應用結果:
備注:
Hangfire的數據庫分析(MySql)
hangfire總共會在數據庫中創建十二張表,用以做數據永久化存儲。
aggregatedcounter,counter,distributedlock,hash,job,jobparameter,jobqueue,jobstate,list,server,set,state。
使ASP.NET應用程序始終運行
一下內容為官網內容,只是官網為英文版,此處只是將翻譯后的中文放在這里,結尾會附官網連接:
默認情況下,在第一個用戶訪問您的站點之前,不會啟動Web應用程序中的Hangfire Server實例。更重要的是,有些事件會在一段時間后將您的Web應用程序關閉(我說的是Idle Timeout和不同的應用程序池回收事件)。在這些情況下,您的重復任務和延遲的作業將不會排隊,并且不會處理排隊的作業。
- 步驟一:內部部署應用程序
對于在您控制的服務器上運行的Web應用程序(物理或虛擬),您可以使用Windows?≥2002R2附帶的IIS≥7.5的自動啟動功能。完整設置需要執行以下步驟:
- 步驟二:創建類
實現該IProcessHostPreloadClient接口的特殊類。它將在啟動期間和每個應用程序池回收后由Windows進程激活服務自動調用。
public class ApplicationPreload : System.Web.Hosting.IProcessHostPreloadClient {public void Preload(string[] parameters){HangfireBootstrapper.Instance.Start();} }HangfireBootstrapper按如下方式創建類。由于這兩個Application_Start和Preload方法將在啟用了自動啟動的環境中被調用,我們需要確保初始化邏輯將被調用一次。
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("connection string");// Specify other options here_backgroundJobServer = new BackgroundJobServer();}}public void Stop(){lock (_lockObject){if (_backgroundJobServer != null){_backgroundJobServer.Dispose();}HostingEnvironment.UnregisterObject(this);}}void IRegisteredObject.Stop(bool immediate){Stop();} }然后,global.asax.cs按如下所述更新您的文件。調用類實例的方法很重要,同樣重要的是在沒有啟用自動啟動功能的環境中啟動Hangfire服務器(例如,在開發機器上)。
public class Global : HttpApplication {protected void Application_Start(object sender, EventArgs e){HangfireBootstrapper.Instance.Start();}protected void Application_End(object sender, EventArgs e){HangfireBootstrapper.Instance.Stop();} }- 步驟三:啟用服務自動啟動
創建上面的類后,您應該編輯全局applicationHost.config文件(%WINDIR%\System32\inetsrv\config\applicationHost.config)。首先,您需要將應用程序池的啟動模式更改為AlwaysRunning,然后啟用Service AutoStart Providers。
進行這些更改后,將自動重新啟動相應的應用程序池。確保僅在修改所有元素后保存更改。
請注意,對于最后一個條目,WebApplication1.ApplicationPreload是應用程序中實現的類的全名,IProcessHostPreloadClient并且WebApplication1是應用程序庫的名稱。你可以在這里相關信息。無需將IdleTimeout設置為零 - 當應用程序池的啟動模式設置為時AlwaysRunning,空閑超時不再起作用。
=!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!=
官方文檔中的applicationHost文件中配置serviceAutoStartProviders的介紹有坑!
實際應為:
<add name="ApplicationPreload" type="MyNamespace.ApplicationPreload, MyLibrary" />其實此處不過是對于IIS網站自動啟動的一種應用:
其原理為 [9] :當冷啟動IIS 7.5服務器或回收單個應用程序池時,IIS 7.5使用applicationHost.config文件中的信息來確定需要自動啟動哪些Web應用程序。對于標記為自動啟動的每個應用程序,IIS7.5向ASP.NET 4發送請求,以在應用程序暫時不接受HTTP請求的狀態下啟動應用程序。當它處于此狀態時,ASP.NET將實例化serviceAutoStartProvider屬性定義的類型(如上例所示)并調用其公共入口點。
初始化代碼在Preload方法中運行并且方法返回后,ASP.NET應用程序已準備好處理請求。
通過在IIS .5和ASP.NET 4中添加自動啟動功能,您現在可以在處理第一個HTTP請求之前執行昂貴的應用程序初始化。例如,您可以使用新的自動啟動功能初始化應用程序,然后向負載均衡器發出信號,表明應用程序已初始化并準備接受HTTP流量。
再一個問題,就是如果此處配置了這個,同時startup中也配置了實例化服務,那么你會發現這樣一種情況,就是會出現兩個serve同時存在的情況。因為Startup類和ApplicationPreload 類均被調用了一遍,里面各實例化了一個服務端。是這樣的,Hangfire官網有一句話:By default, Hangfire Server instance in a web application will not be started until the first user hits your site. 也就是說,默認情況下,無人訪問時,即便網站正在運行未回收,Hangfire是不會啟動的(Startup)。然而ApplicationPreload會在每次網站準備回收準備實現always running時都會被調用一次。所以碰到這種情況,如果擔心機器配置或數據庫連接池不足以支撐兩個服務端的最大工作數量的話,可以將startup中的服務實例化去掉。其余暫時尚未發現問題。
=!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!=
- 確保自動啟動功能正常工作
最簡單的方法 - 回收您的應用程序池,等待5分鐘,然后轉到Hangfire Dashboard UI并檢查當前的Hangfire Server實例是否在5分鐘前啟動。
- 如果出了什么問題
如果在進行這些更改后您的應用程序無法加載,請打開控制面板→管理工具→事件查看器,檢查Windows事件日志。然后打開Windows日志→應用程序并查找最近的錯誤記錄.
- Azure Web應用程序
為Microsoft Azure中托管的應用程序啟用始終運行功能更簡單:只需打開“配置”頁面上的開關并保存設置即可。Always On
This setting does not work for free sites
官網文檔:Making ASP.NET application always running
開發過程需要注意的點
- 要保證壓入隊列的方法是冪等的。因為方法可以被手動重試,也會在失敗時自動重試。雖然可以通過[Retry(0)]過濾器應用于精確方法或全局來禁用自動重試,但作為一般規則請記住,你的job會至少執行一次。[8]
- 關于隊列之間的執行優先級順序,優先級是基于字母順序的。是的,使用hangfire時,優先級是基于字母順序的,因為在table variable or temporary table中更復雜的查詢會降低吞吐量。所以如果想要隊列之間有優先級順序就按照首字母排序吧。官方文檔對隊列這塊只說了一句順序是很重要的,以至于誤導了一部分人一開始就認為是按照索引順序。該點獲取來源
體驗得到的一些缺點/注意點
- 多個服務器時,服務器與服務器之間是互不關心的。你即無法指定由哪個服務器去執行任務,也無法設置服務器優先級。服務器之間的負載均衡等就由hangfire自己調控。
- 任務執行當出現分鐘級別的時間差時,要多注意了。盡管hangfire能夠精確到分級別,但是根據論壇討論和反饋,在分鐘級別的時間差上,可能因為延遲、性能、隊列優先級的問題等,導致一些問題無法精確執行或不執行作業,如設置兩個任務均每分鐘執行一次,這種情況。
- 當執行壓力過大,如超出數據庫連接池等情況,會出現作業堆積或隨機鎖死作業的情況。這是從論壇中獲取來的情況。
- 當碰到異常需要重試時,“未找到目標方法”這個錯誤的重試時間間隔似乎是不固定的,第一次是5分鐘(3:55創建,4:00重試),第二次是19分鐘(4:19重試),第三次是24分鐘(4:43重試),請注意,此時該作業被放置在計劃中,被放在重試中,而非被打上‘失敗’標簽,也未放入失敗隊列中。
- 根據多次驗證,如果不指定隊列,那么就是會使用default隊列。所以不指定隊列和指定隊列‘default’是一樣的。它會在有default的服務器中隨機一個服務器去執行(下一篇細說,同時一種偏門解決本地調試不要與其它服務端混淆的方法)。
留疑/思索
感謝以下參考資料:
[1] 云棲社區》HangFire分布式后端作業調度框架服務
[2] Hangfire項目實踐分享
[3] 定時任務組件Hangfire解析
[4] 為DashBoard頁面添加權限認證
[5] C#中Byte轉換相關的函數
[6] wall-ee》hangfire使用筆記
[7] 簡書》.NET Core開源組件:后臺任務利器之Hangfire
[8]Are your methods ready to run in background?(官方文檔中的連接)
[9]ASP.NET4和Visual Studio2010 Web開發概述》自動啟動Web應用程序
總結
以上是生活随笔為你收集整理的C#-初识Hangfire的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tinymce上传图片php,图片文件上
- 下一篇: 使用python基于socket的tcp