.Net Core實現基於Quart.Net的任務管理

最美的不是下雨天發表於2020-10-23

前段時間給公司專案升級.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
專案截圖:

相關文章