基於Quartz.NET構建自己的動態作業調度器

来源:http://www.cnblogs.com/zhuzhiyuan/archive/2016/12/15/6180870.html
-Advertisement-
Play Games

在日常的開發中,運行定時任務基本上已經是很普遍的需求了,可以通過windows服務+timer組件來實現,也可以使用第三方框架來集成,Quartz.NET就是一款從JAVA的Quartz移植過來的一個不錯的作業調度組件,但是當我們把作業都寫好,並部署完成的時候,管理成為了很麻煩的事情,因此我基於Qu ...


   

 

  在日常的開發中,運行定時任務基本上已經是很普遍的需求了,可以通過windows服務+timer組件來實現,也可以使用第三方框架來集成,Quartz.NET就是一款從JAVA的Quartz移植過來的一個不錯的作業調度組件,但是當我們把作業都寫好,並部署完成的時候,管理成為了很麻煩的事情,因此我基於Quartz.NET,又簡單做了一下封裝,來實現作業動態管理。

  首先作業動態管理包含以下幾個核心點

  1. 應用程式動態載入器
  2. 作業管理(運行)池
  3. 動態啟動/停止/卸載作業

 

  Quzrtz.NET怎麼用我這裡就不再講解了,百度上很多。

 

  主要有三個核心模塊,Job,Trigger和Schedule,

      Job就是每一個作業,Trigger就是作業執行策略(多長時間執行一次等),Schedule則把Job和Tigger裝載起來

  Job和Tigger可以隨意搭配裝載到Schedule裡面運行

      

  接下來講解實現的思路

  

  先定義一個類庫,類庫只包含一個類,BaseJob ,裡面只有一個Run()方法

  之後我們實現的每一個作業都是繼承自這個類,實現Run()方法即可(每個作業都作為一個獨立的類庫,引用這個只有一個類的類庫)

  

public abstract class BaseJob:MarshalByRefObject,IDisposable
{
        public abstract void Run();
}

  接下來建立我們的作業管理核心類庫Job.Service nuget安裝Quartz.NET

  然後新建類JobImplement.cs實現Quartz.NET的IJob介面

  這樣我們就可以在裡面通過我們自己寫的作業調度容器獲取到動態載入的Job信息,並運行Job的run方法,來實現動態調度了(作業調度容器里的作業如何裝載進去的在文章後面講解)

   jobRuntimeInfo是我們自己定義的實體類,裡面包含了BaseJob,AppDomain,JobInfo 三個信息

  JobInfo是作業在上傳到作業動態調度框架時所需要填寫的作業基本信息

    

  

public class JobImplement : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            try
            {
                long jobId = context.JobDetail.JobDataMap.GetLong("JobId");
                //從作業調度容器里查找,如果找到,則運行
                var jobRuntimeInfo =  JobPoolManager.Instance.Get(jobId);
try
                    {
                        jobRuntimeInfo.Job.TryRun();
                    }
                    catch (Exception ex)
                    {
                        //寫日誌,任務調用失敗
                        ConnectionFactory.GetInstance<Provider.JobStateRepository>()
                            .Update(new Provider.Tables.JobState()
                            {
                                JobId = jobId,
                                RunState = (int) Provider.DirectiveType.Stop,
                                UpdateTime = DateTime.Now
                            });
                        Common.Logging.LogManager.GetLogger(this.GetType()).Error(ex.Message, ex);
                    }

            }
            catch (Exception ex)
            {
                Common.Logging.LogManager.GetLogger(this.GetType()).Error(ex.Message, ex);
                //調用的時候失敗,寫日誌,這裡錯誤,屬於系統級錯誤,嚴重錯誤
            }
        }
    }

 

  JobRuntimeInfo

  

public class JobRuntimeInfo
    {
        public AppDomain AppDomain;
        public BaseJob Job { get; set; }

        public JobInfo JobModel { get; set; }
    }

  JobInfo

public class JobInfo
    {
        public long JobId { get; set; }
        public string JobName { get; set; }public string TaskCron { get; set; }
        public string Namespace { get; set; }
        public string MainDllName { get; set; }
        public string Remark { get; set; }
        public string ZipFileName { get; set; }

        public string Version { get; set; }

        public DateTime? CreateTime { get; set; }
    }

 

   接下來我們來講解這個作業是如何執行的

  1.通過一個上傳頁面把作業類庫打包為zip或者rar上傳到伺服器,並填寫Job運行的相關信息,添加到資料庫里

  2.上傳完成之後發佈一條廣播消息給所有的作業調度框架

  3.作業調度框架接收到廣播消息,從資料庫獲取JobInfo,自動根據上傳的時候填寫的信息(見上面的JobInfo類的屬性),自動解壓,裝載到AppDomain里

public class AppDomainLoader
    {
        /// <summary>
        /// 載入應用程式,獲取相應實例
        /// </summary>
        /// <param name="dllPath"></param>
        /// <param name="classPath"></param>
        /// <param name="appDomain"></param>
        /// <returns></returns>
        public static BaseJob Load(string dllPath, string classPath, out AppDomain appDomain) where T : class
        {
            AppDomainSetup setup = new AppDomainSetup();
            if (System.IO.File.Exists($"{dllPath}.config"))
                setup.ConfigurationFile = $"{dllPath}.config";
            setup.ShadowCopyFiles = "true";
            setup.ApplicationBase = System.IO.Path.GetDirectoryName(dllPath);
            appDomain = AppDomain.CreateDomain(System.IO.Path.GetFileName(dllPath), null, setup);
            AppDomain.MonitoringIsEnabled = true;
            BaseJob obj = (BaseJob) appDomain.CreateInstanceFromAndUnwrap(dllPath, classPath);
            return obj;
        }

        /// <summary>
        /// 卸載應用程式
        /// </summary>
        /// <param name="appDomain"></param>
        public static void UnLoad(AppDomain appDomain)
        {
            AppDomain.Unload(appDomain);
            appDomain = null;
        }
    }

 

  4.因為作業都繼承了BaseJob類,所以AppDomain里的入口程式就是JobInfo.Namespace,反射實例化之後強制轉換為BaseJob,然後創建一個JobRuntime對象,添加到JobPoolManager里,JobPoolManager里維護所有的正在運行的Job

  5.根據JobInfo.TaskCron(時間表達式)創建Trigger,創建一個JobImplement,併在Context裡加一個JobId,保證在JobImplement的Run運行的時候能夠從JobPoolManager里獲取到Job的基本信息,以及BaseJob的事例,並調用JobRuntime=>BaseJob=>Run()方法來運行實際的作業

  

 public class JobPoolManager:IDisposable
    {
        private static ConcurrentDictionary<long, JobRuntimeInfo> JobRuntimePool =
            new ConcurrentDictionary<long, JobRuntimeInfo>();

        private static IScheduler _scheduler;
        private static JobPoolManager _jobPollManager;

        private JobPoolManager(){}

        static JobPoolManager()
        {
            _jobPollManager = new JobPoolManager();
            _scheduler = StdSchedulerFactory.GetDefaultScheduler();
            _scheduler.Start();
        }

        public static JobPoolManager Instance
        {
            get { return _jobPollManager; }

        }


        static object _lock=new object();
        public bool Add(long jobId, JobRuntimeInfo jobRuntimeInfo)
        {
            lock (_lock)
            {
                if (!JobRuntimePool.ContainsKey(jobId))
                {
                    if (JobRuntimePool.TryAdd(jobId, jobRuntimeInfo))
                    {
                        IDictionary<string, object> data = new Dictionary<string, object>()
                        {
                            ["JobId"]=jobId
                        };
                        IJobDetail jobDetail = JobBuilder.Create<JobImplement>()
                            .WithIdentity(jobRuntimeInfo.JobModel.JobName, jobRuntimeInfo.JobModel.Group)
                            .SetJobData(new JobDataMap(data))
                            .Build();
                        var tiggerBuilder = TriggerBuilder.Create()
                            .WithIdentity(jobRuntimeInfo.JobModel.JobName, jobRuntimeInfo.JobModel.Group);
                        if (string.IsNullOrWhiteSpace(jobRuntimeInfo.JobModel.TaskCron))
                        {
                            tiggerBuilder = tiggerBuilder.WithSimpleSchedule((simple) =>
                            {
                                simple.WithInterval(TimeSpan.FromSeconds(1));
                            });
                        }
                        else
                        {
                            tiggerBuilder = tiggerBuilder
                                .StartNow()
                                .WithCronSchedule(jobRuntimeInfo.JobModel.TaskCron);
                        }
                        var trigger = tiggerBuilder.Build();
                        _scheduler.ScheduleJob(jobDetail, trigger);
                        return true;
                    }
                }
                return false;
            }
        }

        public JobRuntimeInfo Get(long jobId)
        {
            if (!JobRuntimePool.ContainsKey(jobId))
            {
                return null;
            }
            lock (_lock)
            {
                if (JobRuntimePool.ContainsKey(jobId))
                {
                    JobRuntimeInfo jobRuntimeInfo = null;
                    JobRuntimePool.TryGetValue(jobId, out jobRuntimeInfo);
                    return jobRuntimeInfo;
                }
                return null;
            }
        }

        public bool Remove(long jobId)
        {
            lock (_lock)
            {
                if (JobRuntimePool.ContainsKey(jobId))
                {
                    JobRuntimeInfo jobRuntimeInfo = null;
                    JobRuntimePool.TryGetValue(jobId, out jobRuntimeInfo);
                    if (jobRuntimeInfo != null)
                    {
                        var tiggerKey = new TriggerKey(jobRuntimeInfo.JobModel.JobName,
                            jobRuntimeInfo.JobModel.Group);
                        _scheduler.PauseTrigger(tiggerKey);

                        _scheduler.UnscheduleJob(tiggerKey);

                        _scheduler.DeleteJob(new JobKey(jobRuntimeInfo.JobModel.JobName,
                            jobRuntimeInfo.JobModel.Group));

                        JobRuntimePool.TryRemove(jobId, out jobRuntimeInfo);

                        return true;
                    }
                }
                return false;
            }
        }

        public virtual void Dispose()
        {
            if (_scheduler != null && !_scheduler.IsShutdown)
            {
                foreach (var jobId in JobRuntimePool.Keys)
                {
                    var jobState = ConnectionFactory.GetInstance<Job.Provider.JobStateRepository>().Get(jobId);
                    if (jobState != null)
                    {
                        jobState.RunState = (int) DirectiveType.Stop;
                        jobState.UpdateTime = DateTime.Now;
                        ConnectionFactory.GetInstance<Job.Provider.JobStateRepository>().Update(jobState);
                    }
                }
                _scheduler.Shutdown();
            }
        }
    }

 

  

  然後我們除了做了一個web版的上傳界面之外,還可以做所有的job列表,用來做Start|Stop|Restart等,思路就是發佈一條廣播給所有的作業調度框架,作業調度框架根據廣播消息來進行作業的裝載,啟動,停止,卸載等操作。

  至此,一個基本的動態作業調度框架就結束了。

 

 

 

  

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 說起來慚愧,學了大半年的C#,其實最開始就接觸到了封裝的部分,但是一直模模糊糊的弄不清楚,也覺得沒什麼影響就沒怎麼在意,現在才開始認真的看這部分內容,看懂了過後好多東西清晰了不少,才發現封裝這個基礎那麼那麼重要。 現在反過來一想,封裝和類這些其實就是當初最開始學習面向對象編程的時候老師教的定義 ...
  • 需求是這樣,有個程式界面我們需要通過自己的程式持續輸入數據,界面如圖。 可以獲得控制項的句柄而用鉤子寫入值。這裡需要用到spy++工具。在VS的工具下有個spy++工具,打開如下圖 通過這個工具可以獲得窗體的句柄,當然這裡獲得的句柄只能用於測試,因為.net開發的程式窗體每次打開句柄都會變,都需要重新 ...
  • 開放中經常會要做單元測試,新的項目又沒有單元測試項目,怎麼才能搭建一個單元測試項目呢? 下麵跟我四步走,如有錯誤之處,還請指正! 1、添加項目 2、添加配置文件 新建app.config文件,註意不是web.config,添加connectionstring 3、設置文件屬性 Bulid Actio ...
  • 通過程式集掃描, 能夠自動註冊符合規則的類型. 這種方式, 很方便. 這一篇就介紹下程式集掃描吧. 一、掃描 其實前面已經介紹過, 這種方式. 不過並不全. 先看一個之前的方式: 二、過濾 如果我並不想註冊那麼多的類型, 但是又想通過程式集的註冊方式去註冊, 那怎麼辦呢? 1. Where過濾 只需 ...
  • 在上一篇Log4net入門(ASP.NET MVC 5篇)中,我們講述瞭如何在ASP.NET MVC 5項目中使用log4net。在這一篇中,我們將講述如何在WCF應用中使用log4net,為了講述這個過程,我們將創建三個項目:WCF服務庫項目、WCF服務應用程式和客戶端應用程式。WCF服務庫項目主 ...
  • 最近深圳突然降溫,身體不適感冒發燒,各種不舒服,故請假休息一天,我是個閑不下來的人,這麼好的時光豈能浪費,抄起筆記本,年終總結走起來。 今年是我的幸福年,主要體現在兩個方面:愛情、工作。 割 1.愛情 作為一個對家庭對愛情專一的北方男人(河北邢台人士),2014年畢業後,為了維持美好的校園愛情放棄了 ...
  • 由於第一次寫博客,寫的不好的地方,還請各位大神多多指點, 講解一下:xml動態插入數據並保存,寫這個時候費了我不少勁,最後終於皇天不負有心人讓我搞出來了,特意分享給大家,寫的不完美的地方還請大家多多指點 資料庫表結構 Categoryid GUid自動生成 CategoryName 分類名稱Cate ...
  • ASP.NET Core 中間件(Middleware)Diagnostics使用。對於中間件的介紹可以查看之前的文章ASP.NET Core 開發-中間件(Middleware)。 Diagnostics中間件,主要功能是用於報告和處理ASP.NET Core中的異常和錯誤信息,以及診斷Entit ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...