zoukankan      html  css  js  c++  java
  • ABP框架系列之十四:(Background-Jobs-And-Workers-背景工作和工人)

    Introduction

    ASP.NET Boilerplate provides background jobs and workers those are used to execute some tasks in background threads in an application.

    ASP.NET样板提供背景工作和工人是用来执行一些任务在后台线程中的一个应用。

    Background Jobs

    Background jobs are used to queue some tasks to be executed in background in a queued and persistent manner. You may need background jobs for several reasons. Some examples:

    • To perform long-running tasks to not wait users. For example; A user presses a 'report' button to start a long running reporting job. You add this job to the queue and send report result to your user via email when it's completed.
    • To create re-trying and persistent tasks to guarantee a code will be successfully executed. For example; You can send emails in a background job to overcome temporary failures and guarantie that it's eventually will be sent. Also, thus, users do not wait while sending emails.
    • 后台作业用于在队列中以队列和持久的方式在后台执行某些任务。您可能需要后台工作有以下几个原因。一些例子:

      执行长时间任务而不是等待用户。例如,用户按下“报告”按钮开始一个长时间运行的报表作业。将此作业添加到队列中,并在完成时将报告结果通过电子邮件发送给用户。
      要创建重新尝试和持久的任务,确保代码将成功执行。例如;
      你可以发送电子邮件,在后台工作克服暂时的失败和保证它最终将被发送。因此,用户在发送电子邮件时不会等待。

    About Job Persistence

    See Background Job Store section for more information on job persistence.

    有关工作持久性的更多信息,请参见后台作业存储部分。

    Create a Background Job

    We can create a background job class by either inheriting from BackgroundJob<TArgs> class or directly implementing IBackgroundJob<TArgs> interface.

    我们可以通过继承backgroundjob <tags>类或直接实现ibackgroundjob <tags>接口创建一个后台作业类。

    Here is the most simple background job:

    public class TestJob : BackgroundJob<int>, ITransientDependency
    {
        public override void Execute(int number)
        {
            Logger.Debug(number.ToString());
        }
    }

    A background job defines an Execute method gets an input argument. Argument type is defined as generic class parameter as shown in the example.

    A background job must be registered to dependency injection. Implementing ITransientDependency is the simplest way.

    Lest's define a more realistic job which sends emails in a background queue:

    后台作业定义了执行方法获取输入参数。参数类型被定义为泛型类参数,如示例中所示。

    后台作业必须注册到依赖注入。实施itransientdependency是最简单的方法。

    避免定义一个更现实的工作,在后台队列发送电子邮件:

    public class SimpleSendEmailJob : BackgroundJob<SimpleSendEmailJobArgs>, ITransientDependency
    {
        private readonly IRepository<User, long> _userRepository;
        private readonly IEmailSender _emailSender;
    
        public SimpleSendEmailJob(IRepository<User, long> userRepository, IEmailSender emailSender)
        {
            _userRepository = userRepository;
            _emailSender = emailSender;
        }
        
        [UnitOfWork]
        public override void Execute(SimpleSendEmailJobArgs args)
        {
            var senderUser = _userRepository.Get(args.SenderUserId);
            var targetUser = _userRepository.Get(args.TargetUserId);
    
            _emailSender.Send(senderUser.EmailAddress, targetUser.EmailAddress, args.Subject, args.Body);
        }
    }

    We injected user repository (to get user emails) and email sender (a service to send emails) and simply sent the email. SimpleSendEmailJobArgs is the job argument here and defined as shown below:

    我们给用户注入了存储库(获取用户电子邮件)和发送电子邮件(发送电子邮件的服务),然后简单地发送电子邮件。simplesendemailjobargs是工作参数来定义如下图所示:

    [Serializable]
    public class SimpleSendEmailJobArgs
    {
        public long SenderUserId { get; set; }
    
        public long TargetUserId { get; set; }
    
        public string Subject { get; set; }
    
        public string Body { get; set; }
    }

    A job argument should be serializable, because it's serialized and stored in the database. While ASP.NET Boilerplate default background job manager uses JSON serialization (which does not need [Serializable] attribute), it's better to define [Serializable] attribute since we may switch to another job manager in the future, which may use .NET's built-in binary serialization.

    Keep your arguments simple (like DTOs), do not include entities or other non serializable objects. As shown in the SimpleSendEmailJob sample, we can only store Id of an entity and get the entity from repository inside the job.

    工作应该是可序列化的,因为它是序列化并存储在数据库中。而ASP.NET样板默认背景工作经理使用JSON序列化(不需要序列化属性[ ]),它的更好的定义[ ]属性序列化因为我们可以切换到另一个工作的经理在未来,可以使用.NET内置的二进制序列化。

    保持你的论点简单(像DTOs),不包括单位或其他非序列化的对象。在simplesendemailjob示例所示,我们可以只存储ID的实体和从工作中得到的实体库。

    Add a New Job To the Queue

    After defining a background job, we can inject and use IBackgroundJobManager to add a job to the queue. See a sample for TestJob defined above:

    在定义一个背景的工作,我们可以注射和使用ibackgroundjobmanager添加任务到队列。看到一个testjob上面定义的样本:

    public class MyService
    {
        private readonly IBackgroundJobManager _backgroundJobManager;
    
        public MyService(IBackgroundJobManager backgroundJobManager)
        {
            _backgroundJobManager = backgroundJobManager;
        }
    
        public void Test()
        {
            _backgroundJobManager.Enqueue<TestJob, int>(42);
        }
    }

    We sent 42 as argument while enqueuing. IBackgroundJobManager will instantiate and execute the TestJob with 42 as argument.

    Let's see to add a new job for SimpleSendEmailJob defined above:

    [AbpAuthorize]
    public class MyEmailAppService : ApplicationService, IMyEmailAppService
    {
        private readonly IBackgroundJobManager _backgroundJobManager;
    
        public MyEmailAppService(IBackgroundJobManager backgroundJobManager)
        {
            _backgroundJobManager = backgroundJobManager;
        }
    
        public async Task SendEmail(SendEmailInput input)
        {
                await _backgroundJobManager.EnqueueAsync<SimpleSendEmailJob, SimpleSendEmailJobArgs>(
                new SimpleSendEmailJobArgs
                {
                    Subject = input.Subject,
                    Body = input.Body,
                    SenderUserId = AbpSession.GetUserId(),
                    TargetUserId = input.TargetUserId
                });
        }
    }

    Enqueu (or EnqueueAsync) method has other parameters such as priority and delay.

    Enqueu(或enqueueasync)方法具有优先级和时延等参数。

    Default Background Job Manager

    IBackgroundJobManager is implemented by BackgroundJobManager default. It can be replaced by another background job provider (see hangfire integration). Some information on default BackgroundJobManager:

    • It's a simple job queue works as FIFO in a single thread. It uses IBackgroundJobStore to persist jobs (see next section).
    • It retries job execution until job successfully runs (does not throw any exception but logs them) or timeouts. Default timeout is 2 days for a job.
    • It deletes a job from store (database) when it's successfully executed. If it's timed out, sets as abandoned and leaves on the database.
    • It increasingly waits between retries for a job. Waits 1 minute for first retry, 2 minutes for second retry, 4 minutes for third retry and so on..
    • It polls the store for jobs in fixed intervals. Queries jobs ordering by priority (asc) then by try count (asc).
    • ibackgroundjobmanager由backgroundjobmanager默认实现。它可以通过一个后台作业提供商所取代(见迟发性整合)。一些默认的backgroundjobmanager信息:

      这是一个简单的工作队列,就像单线程中的FIFO一样工作。它采用ibackgroundjobstore坚持工作(见下一节)。
      在重试作业执行直到工作成功运行(不抛出任何异常但日志他们)或超时。默认超时为工作2天。
      当它成功执行时,它会从存储(数据库)中删除一个作业。如果超时,则设置为放弃并离开数据库。
      它越来越多地等待重试之间找工作。等待第一次重试1分钟,第二次重试2分钟,4分钟重试第三分钟等等。
      它以固定的间隔轮询商店的工作。查询按优先级排序的作业(ASC),然后通过尝试计数(ASC)。

    Background Job Store

    Default BackgroundJobManager needs a data store to save and get jobs. If you do not implement IBackgroundJobStore then it uses InMemoryBackgroundJobStore which does not save jobs in a persistent database. You can simply implement it to store jobs in a database or you can use module-zero which already implements it.

    If you are using a 3rd party job manager (like Hanfgire), no need to implement IBackgroundJobStore.

    默认backgroundjobmanager需要数据存储和获得工作。如果不实施ibackgroundjobstore然后用inmemorybackgroundjobstore不保存在数据库的工作。您可以简单地实现它来在数据库中存储作业,也可以使用已经实现它的模块零。

    如果您使用的是第三方管理(像Hanfgire),不需要实现ibackgroundjobstore。

    Configuration

    You can use Configuration.BackgroundJobs in PreInitialize method of your module to configure background job system.

    你可以使用configuration.backgroundjobs在分发配置后台作业系统模块的方法。

    Disabling Job Execution(禁止任务执行

    You may want to disable background job execution for your application:

    public class MyProjectWebModule : AbpModule
    {
        public override void PreInitialize()
        {
            Configuration.BackgroundJobs.IsJobExecutionEnabled = false;
        }
    
        //...
    }

    This is rarely needed. But, think that you are running multiple instances of your application working on the same database (in a web farm). In this case, each application will query same database for jobs and executes them. This leads multiple execution of same jobs and some other problems. To prevent it, you have two options:

    • You can enable job execution for only one instance of the application.
    • You can disable job execution for all instances of the web application and create a seperated standalone application (example: a Windows Service) that executes background jobs.
    • 这是很少需要的。但是,请考虑您正在运行多个应用程序在同一数据库上工作的实例(在Web站点中)。在这种情况下,每个应用程序将查询作业的同一数据库并执行它们。这将导致多个相同的作业和一些其他问题的执行。为了防止它,你有两个选择:

      您可以仅为应用程序的一个实例启用作业执行。
      您可以禁用Web应用程序所有实例的作业执行,并创建独立的独立应用程序(例如:Windows服务),执行后台作业。

    Exception Handling(异常处理

    Since default background job manager should re-try failed jobs, it handles (and logs) all exceptions. In case you want to be informed when an exception occurred, you can create an event handler to handleAbpHandledExceptionData. Background manager triggers this event with a BackgroundJobException exception object which wraps the real exception (get InnerException for the actual exception).

    由于默认后台作业管理器应该重新尝试失败的作业,它会处理(并记录)所有异常。如果你想要了解异常发生时,你可以创建一个事件处理程序handleabphandledexceptiondata。触发此事件背景的经理与backgroundjobexception异常对象将真正的例外(得到的实际异常InnerException)。

    Hangfire Integration(迟发性整合

    Background job manager is designed as replaceable by another background job manager. See hangfire integration document to replace it by Hangfire .

    后台作业管理器被另一个后台任务管理器设计为可替换。看到迟发集成文档来取代它的迟发。

    Background Workers

    Background workers are different than background jobs. They are simple independent threads in the application running in the background. Generally, they run periodically to perform some tasks. Examples;

    • A background worker can run periodically to delete old logs.
    • A background worker can periodically to determine inactive users and send emails to return back to your application.
    • 背景工作人员不同于背景工作。它们是运行在后台的应用程序中的简单独立线程。通常,它们周期性地运行以执行某些任务。实例;

      后台工作者可以定期运行以删除旧日志。
      后台工作者可以周期性地决定非活动用户并发送电子邮件返回到应用程序。

    Create a Background Worker

    To create a background worker, we should implement IBackgroundWorker interface. Alternatively, we can inherit from BackgroundWorkerBase or PeriodicBackgroundWorkerBase based on our needs.

    Assume that we want to make a user passive, if he did not login to the application in last 30 days. See the code:

    创建一个背景的工人,我们应该实施ibackgroundworker接口。或者,我们可以从backgroundworkerbase或根据我们的需要periodicbackgroundworkerbase继承。

    假设我们想让用户被动,如果他没有登录到应用程序在过去30天。见代码:

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

    This is a real code and directly works in ASP.NET Boilerplate with module-zero.

    • If you derive from PeriodicBackgroundWorkerBase (as in this sample), you should implement DoWork method to perform your periodic working code.
    • If you derive from BackgroundWorkerBase or directly implement IBackgroundWorker, you will override/implement Start, Stop and WaitToStop methods. Start and Stop methods should be non-blocking, WaitToStop method should wait worker to finish it's current critical job.
    • 这是一个真正的代码,直接在ASP.NET工作的样板模块零。

      如果你从periodicbackgroundworkerbase(就像这个例子),你应该实现DoWork方法执行你的周期运行代码。
      如果你从backgroundworkerbase或直接实施IBackgroundWorker获得,你将覆盖/实现启动、停止和waittostop方法。启动和停止的方法应该是非阻塞的,WaitToStop的方法应该等待工人来完成它的当前重要的工作。

    Register Background Workers

    After creating a background worker, we should add it to IBackgroundWorkerManager. Most common place is the PostInitialize method of your module:

    在创建背景的工人,我们应该将它添加到ibackgroundworkermanager。最常见的地方是你的模块的postinitialize方法:

    public class MyProjectWebModule : AbpModule
    {
        //...
    
        public override void PostInitialize()
        {
            var workManager = IocManager.Resolve<IBackgroundWorkerManager>();
            workManager.Add(IocManager.Resolve<MakeInactiveUsersPassiveWorker>());
        }
    }

    While we generally add workers in PostInitialize, there is no restriction on that. You can inject IBackgroundWorkerManager anywhere and add workers on runtime. IBackgroundWorkerManager will stop and release all registered workers when your applications is being shutdown.

    而我们通常添加在PostInitialize的工人,有没有限制。你可以把ibackgroundworkermanager随时随地在运行时添加的工人。ibackgroundworkermanager将停止并释放所有注册工人当应用程序被关闭。

    Background Worker Lifestyles

    Background workers are generally implemented as singleton. But there is no restriction. If you need multiple instance of same worker class, you can make it transient and add more than one instance to IBackgroundWorkerManager. In this case, your worker probably will be parametric (say that you have a single LogCleaner class but two LogCleaner worker instances they watch and clear different log folders).

    后台工作人员通常是作为单例实现的。但没有限制。如果你需要同工人阶级的多个实例,你可以使它的瞬态和添加多个实例ibackgroundworkermanager。在这种情况下,你的员工可能将参数(说你有一个单一的logcleaner类但两logcleaner工人情况下他们看清楚不同的日志文件夹)。

    Advanced Scheduling(先进的调度

    ASP.NET Boilerplate's background worker system are simple. It has not a schedule system, except periodic running workers as demonstrated above. If you need to more advanced scheduling features, we suggest you to checkQuartz or another library.

    ASP.NET样板的背景工人系统简单。它没有时间表系统,除了定期运行工人,如上面所示。如果你需要更先进的调度功能,我们建议您checkquartz或另一个图书馆。

    Making Your Application Always Running

    Background jobs and workers only works if your application is running. An ASP.NET application shutdowns by default if no request is performed to the web application for a long time. So, if you host background job execution in your web application (this is the default behaviour), you should ensure that your web application is configured to always running. Otherwise, background jobs only works while your applications is in use.

    There are some techniques to accomplish that. Most simple way is to make periodic requests to your web application from an external application. Thus, you can also check if your web application is up and running. Hangfire documentation explains some other ways.

    后台应用程序和工作只有在应用程序运行时才工作。ASP.NET应用程序关闭默认如果没有要求进行长期的Web应用。因此,如果在Web应用程序中托管后台任务执行(这是默认行为),则应确保Web应用程序配置为始终运行。否则,后台作业只在应用程序使用时才有效。

    有一些技术可以做到这一点。最简单的方法是从外部应用程序向Web应用程序发出周期性请求。因此,您还可以检查Web应用程序是否已启动和运行。迟发性文档解释了一些其他的方法。

  • 相关阅读:
    系统设计题:如何设计一个电商平台积分兑换系统!
    服务器上部署多台mysql
    log4j日志输出格式一览
    Intellij IDEA 智能补全
    什么是旅行商问题——算法NP、P、NPC知识
    如何找到两个升序数组归并后的升序数组的中位数
    Java 不同进制的字面值
    Android 进程和线程
    美图秀秀2015年实习生android应用开发方向招聘笔试题
    Android:Layout_weight的深刻理解
  • 原文地址:https://www.cnblogs.com/smileberry/p/7920933.html
Copyright © 2011-2022 走看看