ABP入门系列(20)——使用后台作业和工作者
1.引言
說(shuō)到后臺(tái)作業(yè),你可能條件反射的想到BackgroundWorker,但后臺(tái)作業(yè)并非是后臺(tái)任務(wù),后臺(tái)作業(yè)用一種隊(duì)列且持久穩(wěn)固的方式安排一些待執(zhí)行后臺(tái)任務(wù)。
- 為執(zhí)行長(zhǎng)時(shí)間運(yùn)行的任務(wù)而用戶無(wú)需等待,以提高用戶體驗(yàn)。
- 為創(chuàng)建可重試且持久穩(wěn)固的任務(wù)來(lái)保證一個(gè)代碼將會(huì)被成功運(yùn)行,以提高系統(tǒng)的穩(wěn)定性。
那什么又是后臺(tái)工作者呢?
后臺(tái)工作者則是簡(jiǎn)單運(yùn)行在應(yīng)用程序后臺(tái)的獨(dú)立線程,它用于定期執(zhí)行一些任務(wù)。
- 一個(gè)后臺(tái)工作者可以定期清除臨時(shí)表、重建索引。
- 一個(gè)后臺(tái)工作者可以定期清除日志。
2. 實(shí)現(xiàn)機(jī)制
后臺(tái)作業(yè)
后臺(tái)作業(yè)的實(shí)現(xiàn)機(jī)制
后臺(tái)作業(yè)的核心接口為IBackgroundJobManager。Abp對(duì)其提供了默認(rèn)實(shí)現(xiàn)BackgroundJobManager,當(dāng)然我們也可以選擇已經(jīng)集成的其它后臺(tái)作業(yè)提供器替代(比如HangFire、Quartz)。以下是它的實(shí)現(xiàn)機(jī)制:
- 它是一個(gè)簡(jiǎn)單的作業(yè)隊(duì)列,以FIFO(先進(jìn)先出)方式單線程作業(yè),它使用IBackgroundJobStore來(lái)持久化作業(yè),Abp默認(rèn)使用InMemoryBackgroundJobStore在內(nèi)存中持久化后臺(tái)作業(yè),我們也可使用Module-Zero實(shí)現(xiàn)的BackgroundJobStore將后臺(tái)作業(yè)持久化到數(shù)據(jù)庫(kù)。
- 它一直重試作業(yè)執(zhí)行直到作業(yè)成功運(yùn)行(只記錄日志不拋出異常)或超時(shí)(默認(rèn)超時(shí)期限為2天)。
- 在作業(yè)成功運(yùn)行后,它從存儲(chǔ)(數(shù)據(jù)庫(kù))里刪除這個(gè)作業(yè),如果超時(shí)了,就把這個(gè)作業(yè)設(shè)置為“被拋棄的”,后續(xù)將不再處理。
- 重試時(shí)間逐漸遞增,第一次重試,等待1分鐘,第二次重試,等待2分鐘,第三次重試,等待4分鐘,如此類推。
- 后臺(tái)作業(yè)是在固定的間隔按優(yōu)先級(jí)(升序)排序,然后再按重試次數(shù)排序(升序)。
后臺(tái)工作者
后臺(tái)工作者的實(shí)現(xiàn)機(jī)制
后臺(tái)工作者是運(yùn)行在應(yīng)用程序后臺(tái)定期執(zhí)行任務(wù)的。 Abp提供了IBackgroundWorkerManager接口,默認(rèn)使用的是定時(shí)器Timer來(lái)實(shí)現(xiàn)定期執(zhí)行任務(wù)的。當(dāng)應(yīng)用關(guān)閉時(shí),IBackgroundWorkerManager將停止并釋放所有已注冊(cè)的工作者。
3.使用后臺(tái)作業(yè)
管理員負(fù)責(zé)任務(wù)的進(jìn)度跟蹤,當(dāng)打開任務(wù)列表時(shí),可以發(fā)送通知提醒未完成任務(wù)的用戶。
3.1. 定義后臺(tái)作業(yè)參數(shù)
后臺(tái)作業(yè)的參數(shù)主要用于參數(shù)傳遞,因?yàn)楹笈_(tái)作業(yè)需要提供重試機(jī)制,所以我們應(yīng)該保存參數(shù)信息,而最好的辦法就是直接序列化和反序列化來(lái)使用。另外我們應(yīng)該保持參數(shù)的簡(jiǎn)單,避免直接使用實(shí)體或其他非序列化對(duì)象。
?
[Serializable] public class SendNotificationJobArgs {public long TargetUserId { get; set; }public string NotificationTitle { get; set; }public MessageNotificationData NotificationData { get; set; }public NotificationSeverity NotificationSeverity { get; set; } }3.2. 定義作業(yè)
繼承BackgroundJob<TArgs>類或直接實(shí)現(xiàn)IBackgroundJob<TArgs>接口來(lái)創(chuàng)建一個(gè)后臺(tái)作業(yè)。
?
public class SendNotificationJob : BackgroundJob<SendNotificationJobArgs>, ITransientDependency {private readonly IRepository<User,long> _userRepository;private readonly INotificationPublisher _notificationPublisher;public SendNotificationJob(IRepository<User, long> userRepository, INotificationPublisher notificationPublisher){_userRepository = userRepository;_notificationPublisher = notificationPublisher;}public override void Execute(SendNotificationJobArgs args){var targetUser = _userRepository.Get(args.TargetUserId);_notificationPublisher.Publish(args.NotificationTitle, args.NotificationData, null,args.NotificationSeverity, new[] {targetUser.ToUserIdentifier()});} }可以看到我們使用依賴注入注入了IRepository<User,long>和INotificationPublisher。其中僅需實(shí)現(xiàn)Execute方法來(lái)定義后臺(tái)作業(yè)的邏輯。
3.3.定義應(yīng)用服務(wù)
為了方便調(diào)用,定義一個(gè)發(fā)送通知的應(yīng)用服務(wù):
?
public interface INotificationAppService : IApplicationService {void NotificationUsersWhoHaveOpenTask(); } public class NotificationAppService : LearningMpaAbpAppServiceBase, INotificationAppService {private readonly IRepository<Task> _taskRepository;private readonly IBackgroundJobManager _backgroundJobManager;public NotificationAppService(IRepository<Task> taskRepository, IBackgroundJobManager backgroundJobManager){_taskRepository = taskRepository;_backgroundJobManager = backgroundJobManager;}public void NotificationUsersWhoHaveOpenTask(){var openTasks = _taskRepository.GetAll().Where(t => t.State == TaskState.Open);foreach (var openTask in openTasks){var sendNotificationArgs = new SendNotificationJobArgs(){NotificationTitle = "You have an open task",NotificationSeverity = NotificationSeverity.Info,NotificationData = new MessageNotificationData(openTask.Title),TargetUserId = openTask.AssignedPersonId.Value};_backgroundJobManager.Enqueue<SendNotificationJob, SendNotificationJobArgs>(sendNotificationArgs);}} }我們通過(guò)獲取所有未完成的任務(wù),然后循環(huán)遍歷構(gòu)造發(fā)送通知參數(shù)SendNotificationJobArgs,再調(diào)用依賴注入的IBackgroundJobManager的Enqueue方法給隊(duì)列添加作業(yè)。
3.4. 測(cè)試效果
我們?cè)谌蝿?wù)清單列表上添加一個(gè)按鈕來(lái)觸發(fā)后臺(tái)作業(yè),實(shí)現(xiàn)效果如下圖:
后臺(tái)作業(yè)通知效果
3.5. 持久化后臺(tái)作業(yè)到數(shù)據(jù)庫(kù)
上面我們也說(shuō)明了Abp定義了IBackgroundJobStore來(lái)持久化后臺(tái)作業(yè),Abp默認(rèn)使用InMemoryBackgroundJobStore在內(nèi)存中持久化后臺(tái)作業(yè)。
我們看下源碼就明白了:
?
//AbpKernelModule.cs //默認(rèn)注入的是 InMemoryBackgroundJobStoreif (Configuration.BackgroundJobs.IsJobExecutionEnabled){IocManager.RegisterIfNot<IBackgroundJobStore, InMemoryBackgroundJobStore>(DependencyLifeStyle.Singleton);}else{IocManager.RegisterIfNot<IBackgroundJobStore, NullBackgroundJobStore>(DependencyLifeStyle.Singleton);}我們可以使用Module-Zero實(shí)現(xiàn)的BackgroundJobStore將后臺(tái)作業(yè)持久化到數(shù)據(jù)庫(kù)。定位到應(yīng)用服務(wù)層,修改應(yīng)用服務(wù)層的module,將BackgroundJobStore注冊(cè)到依賴注入容器即可:
?
//LearningMpaAbpApplicationModule.cs public override void PreInitialize() {//使用module-zero實(shí)現(xiàn)的后臺(tái)作業(yè)存儲(chǔ)持久化后臺(tái)作業(yè)到數(shù)據(jù)庫(kù)IocManager.Register<IBackgroundJobStore,BackgroundJobStore>(); }再執(zhí)行后臺(tái)作業(yè),就可以從數(shù)據(jù)庫(kù)表AbpBackgroundJobs中查詢到所有未完成的作業(yè)。
保存到數(shù)據(jù)庫(kù)的后臺(tái)作業(yè)
?
其中JobArgs就是存儲(chǔ)的序列化的后臺(tái)作業(yè)參數(shù):
{"TargetUserId":4,"NotificationTitle":"You have an open task","NotificationData":{"Message":"使用Abp框架搭建Mpa網(wǎng)站","Type":"Abp.Notifications.MessageNotificationData","Properties":{"Message":"使用Abp框架搭建Mpa網(wǎng)站"}},"NotificationSeverity":0}
4. 使用后臺(tái)工作者
將超過(guò)30天未登錄的用戶設(shè)置為“消極”的。
4.1. 創(chuàng)建后臺(tái)工作者
為創(chuàng)建一個(gè)后臺(tái)工作者,我們應(yīng)當(dāng)實(shí)現(xiàn)IBackgroundWorker接口,我們還可以選擇直接從BackgroundWorkerBase或PeriodicBackgroundWorkerBase基類上繼承。
- 如果你從PeriodicBackgroundWorkerBase繼承(如這個(gè)例子),需要實(shí)現(xiàn)DoWork方法來(lái)執(zhí)行你的定期工作。
- 如果你從BackgroundWorkerBase繼承或直接實(shí)現(xiàn)IBackgroundWorker,需要重寫/實(shí)現(xiàn)Start、Stop和WaitToStop方法,Start和Stop方法應(yīng)當(dāng)是非阻塞的,WaitToStop方法需要等待工作者完成它當(dāng)前的工作。
?
public class MakeInactiveUsersPassiveWorker : PeriodicBackgroundWorkerBase, ISingletonDependency {private readonly IRepository<User, long> _userRepository;public MakeInactiveUsersPassiveWorker(AbpTimer timer, IRepository<User, long> userRepository): base(timer){_userRepository = userRepository;Timer.Period = 5000; //5 seconds (good for tests, but normally will be more)}[UnitOfWork]protected override void DoWork(){using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant)){var oneMonthAgo = Clock.Now.Subtract(TimeSpan.FromDays(30));var inactiveUsers = _userRepository.GetAllList(u =>u.IsActive &&((u.LastLoginTime < oneMonthAgo && u.LastLoginTime != null) || (u.CreationTime < oneMonthAgo && u.LastLoginTime == null)));foreach (var inactiveUser in inactiveUsers){inactiveUser.IsActive = false;Logger.Info(inactiveUser + " made passive since he/she did not login in last 30 days.");}CurrentUnitOfWork.SaveChanges();}} }4.2.注冊(cè)后臺(tái)工作者
在完成創(chuàng)建后臺(tái)工作者后,需要把它添加到IBackgroundWorkerManager,通常在模塊的PostInitialize方法里注冊(cè)即可,但不是一定要這樣,你可以在任何地方注入IBackgroundWorkerManager,然后在運(yùn)行時(shí)添加工作者。
?
//LearningMpaAbpApplicationModule.cspublic override void PostInitialize(){//注冊(cè)后臺(tái)工作者標(biāo)記消極用戶var workManager = IocManager.Resolve<IBackgroundWorkerManager>();workManager.Add(IocManager.Resolve<MakeInactiveUsersPassiveWorker>());}5.最后
后臺(tái)作業(yè)和工作者正常工作的前提是你的應(yīng)用保持運(yùn)行。但一個(gè)Web應(yīng)用長(zhǎng)時(shí)間沒(méi)有收到訪問(wèn)請(qǐng)求,它默認(rèn)地會(huì)被關(guān)閉,所以,如果你的宿主后臺(tái)作業(yè)運(yùn)行在你的web應(yīng)用里(這是默認(rèn)行為),你應(yīng)當(dāng)確保你的web應(yīng)用被配置成一直運(yùn)行。
而如何做到這點(diǎn)呢,一個(gè)非常簡(jiǎn)單的辦法是:從一個(gè)外部應(yīng)用里定期訪問(wèn)你的Web應(yīng)用,從而你可以一直檢查你的web應(yīng)用是否一直運(yùn)行著。
參考資料:
Background Jobs and Workers
作者:圣杰
鏈接:https://www.jianshu.com/p/d20027bd76d5
來(lái)源:簡(jiǎn)書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
總結(jié)
以上是生活随笔為你收集整理的ABP入门系列(20)——使用后台作业和工作者的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 大额信用卡有什么好处
- 下一篇: 项目架构开发:数据访问层之Cache