前段時間給公司專案升級.net框架,把原先的任務管理平臺用.net core實現,現做如下整理:
一、實現思路
之前的實現也是參考了部落格園中其他文章實現的思路:
- 一個任務定義一個實現IJob介面的類,通過單獨的dll管理;
- 通過資料庫持久化、維護任務,便於服務重啟時任務的恢復;
- 定義一個管理任務的基礎服務,輪詢資料庫中的任務,根據任務的狀態維護任務的執行;
- 新增任務時,需要在資料庫中新增一條記錄,並且在任務管理的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個專案:
- JobManage.Service:控制檯程式,管理任務的基礎服務,通過Topshelf部署成windows服務,如何部署參考: https://www.cnblogs.com/podolski/p/10054286.html
- JobManage.Web:Web應用程式,管理平臺,新增、暫停、恢復、刪除任務,檢視任務執行日誌;
- 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
專案截圖: