• .Net Core实现基于Quart.Net的任务管理


    前段时间给公司项目升级.net框架,把原先的任务管理平台用.net core实现,现做如下整理:

    一、实现思路

    之前的实现也是参考了博客园中其他文章实现的思路:

    1. 一个任务定义一个实现IJob接口的类,通过单独的dll管理;
    2. 通过数据库持久化、维护任务,便于服务重启时任务的恢复;
    3. 定义一个管理任务的基础服务,轮询数据库中的任务,根据任务的状态维护任务的执行;
    4. 新增任务时,需要在数据库中添加一条记录,并且在任务管理的dll中添加一个实现IJob的类,基础服务通过反射dll来构建任务的实例添加到调度器中

    由于业务代码会频繁调整,我们业务代码从任务执行中拆分出来,独立部署成http服务,任务的执行就是调用一个http请求,这样不同的任务就是请求的url不一样,查看官方文档( https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/more-about-jobs.html#job-instances )发现,我们可以通过只创建一个基础任务类,创建多个该任务类的实例来实现构建多个任务,IJobDetail中可以用JobDataMap对象来存储Job实例的参数,所以我们通过JobDataMap将请求url传递到任务的Execute()方法中,我们只需要在数据库中补充任务请求的url信息就可以了,不需要单独的dll去定义任务。

    二、项目结构

    根据上面思路,我们只需要一个管理任务的基础服务、一个Web管理平台就可以实现,为了保持项目简单,把任务管理无关的功能合并在一个项目里,并且尽量排除无关的框架和功能点,最终程序包含3个项目:

    1. JobManage.Service:控制台程序,管理任务的基础服务,通过Topshelf部署成windows服务,如何部署参考: https://www.cnblogs.com/podolski/p/10054286.html
    2. JobManage.Web:Web应用程序,管理平台,新增、暂停、恢复、删除任务,查看任务运行日志;
    3. JobManage.Core:类库,提供业务基础服务,如数据库操作等

    动态添加任务:

    IJobDetail jobDetail = JobBuilder.Create<BaseJob>()
         .WithIdentity(jobKey)
         .UsingJobData("RequestUrl", job.RequestUrl)
         .Build();
    
    ITrigger trigger = TriggerBuilder.Create()
        .WithIdentity(group, name)
        .StartNow()
        .WithCronSchedule(job.CronExpression)
        .Build();
    
    await context.Scheduler.ScheduleJob(jobDetail, trigger);
    

    基础任务类BaseJob.cs的Execute()方法:

    public async Task Execute(IJobExecutionContext context)
    {
        var url = context.JobDetail.JobDataMap.GetString("RequestUrl");
        var client = _clientFactory.CreateClient();
        var request = new HttpRequestMessage(HttpMethod.Post, url);
        var response = await client.SendAsync(request);
        if (response.IsSuccessStatusCode)
        {
        	await response.Content.ReadAsStringAsync();
        }
    }
    

    三、任务状态管理

    这里定义7个任务状态:待执行、执行中、待暂停、已暂停、待恢复、待删除、已删除

    web管理平台维护任务(新增、暂停、恢复、删除)时将任务状态更新为待处理状态(待执行、待暂停、待恢复、待删除),任务管理基础服务定时遍历业务任务,根据数据库中任务当前的状态修改任务的执行,并且将数据库中待处理任务状态更新为已处理状态(执行中、已暂停、已删除)

    四、任务依赖注入服务

    在任务类中我们用到了http服务,我们需要在任务类中获取http服务,我们通过.Net Core注入和获取服务的方式来实现,这里主要是要自定义任务类实例的创建和获取,官方文档( https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/miscellaneous-features.html#jobfactory )中说明可以通过实现 IJobFactory 接口,并且修改 IScheduler.JobFactory的属性来实现:

    //自定义任务实例获取
    public class JobFactory : IJobFactory
    {
        private readonly IServiceProvider _serviceProvider;
        public JobFactory(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
    
        public virtual IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            IJobDetail jobDetail = bundle.JobDetail;
            Type jobType = jobDetail.JobType;
            return _serviceProvider.GetService(jobType) as IJob;
        }
    
        public virtual void ReturnJob(IJob job)
        {
            var disposable = job as IDisposable;
            disposable?.Dispose();
        }
    }
        
    //修改IScheduler.JobFactory属性
    _scheduler.JobFactory = serviceProvider.GetService<JobFactory>();
    

    官方文档中也提供了依赖注入的示例: https://www.quartz-scheduler.net/documentation/quartz-3.x/packages/microsoft-di-integration.html#di-aware-job-factories

    五、任务监听

    我们需要记录任务执行的情况,Quartz.Net提供了任务监听功能,我们可以自己实现IJobListener接口,也可以继承Quartz.Net框架中IJobListener的实现类JobListenerSupport来完成任务的监听,继承JobListenerSupport 类时重写对应的方法来实现我们需要的操作,如下实现记录任务上次执行时间、下次执行时间、执行时长、执行异常错误信息

    //监听实现
    public class JobListener : JobListenerSupport
    {
        private readonly JobRepository _jobRepository;
        private readonly JobRunLogRepository _jobRunLogRepository;
    
        public JobListener(JobRepository jobRepository, JobRunLogRepository jobRunLogRepository)
        {
        	_jobRepository = jobRepository;
        	_jobRunLogRepository = jobRunLogRepository;
        }
    
        public override string Name
        {
        	get { return "jobListener"; }
        }
    
        public override async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default)
        {
            string group = context.JobDetail.Key.Group;
            string name = context.JobDetail.Key.Name;
            DateTime fireTimeUtc = TimeZoneInfo.ConvertTimeFromUtc(context.FireTimeUtc.DateTime, TimeZoneInfo.Local);
    
            DateTime? nextFireTimeUtc = null;
            if (context.NextFireTimeUtc != null)
            {
                nextFireTimeUtc = TimeZoneInfo.ConvertTimeFromUtc(context.NextFireTimeUtc.Value.DateTime, TimeZoneInfo.Local);
            }
    
            if (!JobHelper.IsBaseJob(group, name))
            {
                //更新任务执行情况
                await _jobRepository.UpdateExecuteAsync(group, name, fireTimeUtc, nextFireTimeUtc);
                //记录运行日志
                double totalSeconds = context.JobRunTime.TotalSeconds;
                bool succ = true;
                string exception = string.Empty;
                if (jobException != null)
                {
                    succ = false;
                    exception = jobException.ToString();
                }
                JobRunLog log = new JobRunLog(group, name, totalSeconds, fireTimeUtc, succ, exception);
                await _jobRunLogRepository.InsertAsync(log);
            }
        }
    }
    
    //注册监听器
    JobListener listener = serviceProvider.GetService<JobListener>();
    _scheduler.ListenerManager.AddJobListener(listener, GroupMatcher<JobKey>.AnyGroup());
    

    六、总结

    上述内容只是记录了搭建任务管理平台时的思路和几个关键的点,没有对Quartz.Net基础功能、MongoDB操作做说明,官方文档中包含了完整的说明,官方提供的源码中也有完整的示例,建议阅读官方文档源码来实现更高级的功能。

    项目完整代码地址:https://github.com/zhrong92/JobManage
    项目截图:

  • 相关阅读:
    MySQL ——索引原理与慢查询优化(Day45)
    mysql 练习题(Day44)
    MySQL 多表查询(Day43)
    MySQL 单表查询(Day42)
    MySQL -表完整性约束(Day41)
    回调函数
    进程池
    共享数据, 信号量(了解),事件(了解)
    管道
    python并发编程之多进程
  • 原文地址:https://www.cnblogs.com/zhaorong0912/p/13853061.html
Copyright © 2020-2023  润新知