zoukankan      html  css  js  c++  java
  • ABP入门系列(20)——使用后台作业和工作者

    ABP入门系列目录——学习Abp框架之实操演练
    源码路径:Github-LearningMpaAbp


    1.引言

    说到后台作业,你可能条件反射的想到BackgroundWorker,但后台作业并非是后台任务,后台作业用一种队列且持久稳固的方式安排一些待执行后台任务。

    • 为执行长时间运行的任务而用户无需等待,以提高用户体验。
    • 为创建可重试且持久稳固的任务来保证一个代码将会被成功运行,以提高系统的稳定性。

    那什么又是后台工作者呢?
    后台工作者则是简单运行在应用程序后台的独立线程,它用于定期执行一些任务。

    • 一个后台工作者可以定期清除临时表、重建索引。
    • 一个后台工作者可以定期清除日志。

    2. 实现机制

    后台作业

    后台作业的实现机制
    后台作业的核心接口为IBackgroundJobManager。Abp对其提供了默认实现BackgroundJobManager,当然我们也可以选择已经集成的其它后台作业提供器替代(比如HangFire、Quartz)。以下是它的实现机制:

    • 它是一个简单的作业队列,以FIFO(先进先出)方式单线程作业,它使用IBackgroundJobStore来持久化作业,Abp默认使用InMemoryBackgroundJobStore在内存中持久化后台作业,我们也可使用Module-Zero实现的BackgroundJobStore将后台作业持久化到数据库。
    • 它一直重试作业执行直到作业成功运行(只记录日志不抛出异常)或超时(默认超时期限为2天)。
    • 在作业成功运行后,它从存储(数据库)里删除这个作业,如果超时了,就把这个作业设置为“被抛弃的”,后续将不再处理。
    • 重试时间逐渐递增,第一次重试,等待1分钟,第二次重试,等待2分钟,第三次重试,等待4分钟,如此类推。
    • 后台作业是在固定的间隔按优先级(升序)排序,然后再按重试次数排序(升序)。

    后台工作者

    后台工作者的实现机制
    后台工作者是运行在应用程序后台定期执行任务的。 Abp提供了IBackgroundWorkerManager接口,默认使用的是定时器Timer来实现定期执行任务的。当应用关闭时,IBackgroundWorkerManager将停止并释放所有已注册的工作者。

    3.使用后台作业

    管理员负责任务的进度跟踪,当打开任务列表时,可以发送通知提醒未完成任务的用户。

    3.1. 定义后台作业参数

    后台作业的参数主要用于参数传递,因为后台作业需要提供重试机制,所以我们应该保存参数信息,而最好的办法就是直接序列化和反序列化来使用。另外我们应该保持参数的简单,避免直接使用实体或其他非序列化对象。

    [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. 定义作业

    继承BackgroundJob<TArgs>类或直接实现IBackgroundJob<TArgs>接口来创建一个后台作业。

    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。其中仅需实现Execute方法来定义后台作业的逻辑。

    3.3.定义应用服务

    为了方便调用,定义一个发送通知的应用服务:

    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);
            }
        }
    }
    

    我们通过获取所有未完成的任务,然后循环遍历构造发送通知参数SendNotificationJobArgs,再调用依赖注入的IBackgroundJobManagerEnqueue方法给队列添加作业。

    3.4. 测试效果

    我们在任务清单列表上添加一个按钮来触发后台作业,实现效果如下图:

    后台作业通知效果

    3.5. 持久化后台作业到数据库

    上面我们也说明了Abp定义了IBackgroundJobStore来持久化后台作业,Abp默认使用InMemoryBackgroundJobStore在内存中持久化后台作业。
    我们看下源码就明白了:

    //AbpKernelModule.cs
    //默认注入的是 InMemoryBackgroundJobStore
     if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
     {
         IocManager.RegisterIfNot<IBackgroundJobStore, InMemoryBackgroundJobStore>(DependencyLifeStyle.Singleton);
     }
     else
     {
         IocManager.RegisterIfNot<IBackgroundJobStore, NullBackgroundJobStore>(DependencyLifeStyle.Singleton);
     }
    

    我们可以使用Module-Zero实现的BackgroundJobStore将后台作业持久化到数据库。定位到应用服务层,修改应用服务层的module,将BackgroundJobStore注册到依赖注入容器即可:

    //LearningMpaAbpApplicationModule.cs
    public override void PreInitialize()
    {
        //使用module-zero实现的后台作业存储持久化后台作业到数据库
        IocManager.Register<IBackgroundJobStore,BackgroundJobStore>();
    }
    

    再执行后台作业,就可以从数据库表AbpBackgroundJobs中查询到所有未完成的作业。
    保存到数据库的后台作业

    其中JobArgs就是存储的序列化的后台作业参数:
    {"TargetUserId":4,"NotificationTitle":"You have an open task","NotificationData":{"Message":"使用Abp框架搭建Mpa网站","Type":"Abp.Notifications.MessageNotificationData","Properties":{"Message":"使用Abp框架搭建Mpa网站"}},"NotificationSeverity":0}

    4. 使用后台工作者

    将超过30天未登录的用户设置为“消极”的。

    4.1. 创建后台工作者

    为创建一个后台工作者,我们应当实现IBackgroundWorker接口,我们还可以选择直接从BackgroundWorkerBasePeriodicBackgroundWorkerBase基类上继承。

    • 如果你从PeriodicBackgroundWorkerBase继承(如这个例子),需要实现DoWork方法来执行你的定期工作。
    • 如果你从BackgroundWorkerBase继承或直接实现IBackgroundWorker,需要重写/实现StartStopWaitToStop方法,StartStop方法应当是非阻塞的,WaitToStop方法需要等待工作者完成它当前的工作。
    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.注册后台工作者

    在完成创建后台工作者后,需要把它添加到IBackgroundWorkerManager,通常在模块的PostInitialize方法里注册即可,但不是一定要这样,你可以在任何地方注入IBackgroundWorkerManager,然后在运行时添加工作者。

    //LearningMpaAbpApplicationModule.cs
     public override void PostInitialize()
     {
         //注册后台工作者标记消极用户
         var workManager = IocManager.Resolve<IBackgroundWorkerManager>();
         workManager.Add(IocManager.Resolve<MakeInactiveUsersPassiveWorker>());
     }
    

    5.最后

    后台作业和工作者正常工作的前提是你的应用保持运行。但一个Web应用长时间没有收到访问请求,它默认地会被关闭,所以,如果你的宿主后台作业运行在你的web应用里(这是默认行为),你应当确保你的web应用被配置成一直运行。

    而如何做到这点呢,一个非常简单的办法是:从一个外部应用里定期访问你的Web应用,从而你可以一直检查你的web应用是否一直运行着。

    参考资料:
    Background Jobs and Workers

  • 相关阅读:
    LeetCode Path Sum II
    LeetCode Longest Palindromic Substring
    LeetCode Populating Next Right Pointers in Each Node II
    LeetCode Best Time to Buy and Sell Stock III
    LeetCode Binary Tree Maximum Path Sum
    LeetCode Find Peak Element
    LeetCode Maximum Product Subarray
    LeetCode Intersection of Two Linked Lists
    一天一个设计模式(1)——工厂模式
    PHP迭代器 Iterator
  • 原文地址:https://www.cnblogs.com/sheng-jie/p/7144105.html
Copyright © 2011-2022 走看看