分散式任務排程ScheduleMaster

2022-05-29 06:06:32
1.什麼是ScheduleMaster

ScheduleMaster分散式任務排程系統。簡稱:集中任務排程系統,最簡單的理解ScheduleMaster,就是對不同的系統裡面的排程任務做統一管理的框架

例如我們現在有多個系統,每個系統針對自己處理不同的業務場景。衍生出自己的排程任務,想象一下,如果每個系統人為去維護,那隨著排程任務越來越多,人是崩潰的吧,可見維護技術成本是巨大的,這時我們需要選擇分散式任務系統框架做統一的管理

當然有目前有很多相對優秀分散式任務系統框架,我們主要學習 ScheduleMaster

2.使用ScheduleMaster

1.首先我們需要使用NET Core web Api建立幾個模擬的微服務,分別為 考勤算薪郵件簡訊

2.下載開源的ScheduleMaster,並且使用ScheduleMaster排程我們的微服務介面

- sqlserver:"Persist Security Info = False; User ID =sa; Password =123456; Initial Catalog =schedule_master; Server =."
- postgresql:"Server=localhost;Port=5432;Database=schedule_master;User Id=postgres;Password=123456;Pooling=true;MaxPoolSize=20;"
- mysql:"Data Source=localhost;Database=schedule_master;User ID=root;Password=123456;pooling=true;CharSet=utf8mb4;port=3306;sslmode=none;TreatTinyAsBoolean=true"

修改Host的組態檔和支援的資料庫,框架預設使用Mysql

修改Web的組態檔和支援的資料庫,框架預設使用Mysql

3.進入Hos.ScheduleMaster.Web專案的釋出目錄,dotnet Hos.ScheduleMaster.Web.dll啟動專案 ,此時會生成資料庫

登入賬號:admin

密碼:111111

4.進入Hos.ScheduleMaster.QuartzHost專案的釋出目錄,執行命令,啟動專案

dotnet Hos.ScheduleMaster.QuartzHost.dll --urls http://*:30003
1.設定Http排程任務

5.準備就緒後,使用後臺檢視節點管理,可以看到web主節點30000任務排程的介面30002已經在執行

6.使用任務列表選單,建立定時排程任務,設定基礎資訊後設資料設定,然後點選儲存就開始執行任務

2.設定程式集排程任務

1.建立一個類庫,安裝ScheduleMaster庫, 建立一個類繼承TaskBase,實現抽象方法,然後編譯程式集

namespace TaskExcuteService
{
    class AssemblyTask : TaskBase
    {
        public override void Run(TaskContext context)
        {
            context.WriteLog("程式集任務");
        }
    }
}

2.找到debug目錄,去掉Base程式集,然後新增為壓縮檔案

3.在web介面找到任務設定,選擇程式集進行基本設定,設定後設資料,輸入程式集名稱,然後輸入類,並將程式集上傳,儲存就可以執行了

3.使用Api接入任務

為了方便業務系統更好的接入排程系統,ScheduleMaster建立任務不僅可以在控制檯中實現,系統也提供了WebAPI供業務系統使用程式碼接入,這種方式對延時任務來說尤其重要。

1.API Server 對接流程
  • 在控制檯中建立好專用的API對接使用者賬號。

  • 使用對接賬號的使用者名稱設定為http header中的ms_auth_user值。

  • 使用經過雜湊運算過的祕鑰設定為http header中的ms_auth_secret值,計算規則:按{使用者名稱}{hash(密碼)}{使用者名稱}的格式拼接得到字串str,然後再對str做一次hash運算即得到最終祕鑰,hash函數是小寫的32位元MD5演演算法。

  • 使用form格式發起http呼叫,如果非法使用者會返回401-Unauthorized。

HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("ms_auth_user", "admin");
client.DefaultRequestHeaders.Add("ms_auth_secret", SecurityHelper.MD5($"admin{SecurityHelper.MD5("111111")}}admin"));

所有介面採用統一的返回格式,欄位如下:

引數名稱 引數型別 說明
Success bool 是否成功
Status int 結果狀態,0-請求失敗 1-請求成功 2-登入失敗 3-引數異常 4-資料異常
Message string 返回的訊息
Data object 返回的資料
2.建立程式集任務

使用API建立任務的方式不支援上傳程式包,所以在任務需要啟動時要確保程式包已通過其他方式上傳,否則會啟動失敗。

  • 介面地址:http://yourip:30000/api/task/create

  • 請求型別:POST

  • 引數格式:application/x-www-form-urlencoded

  • 返回結果:建立成功返回任務id

  • 參數列:

引數名稱 引數型別 是否必填 說明
MetaType int 任務型別,這裡固定是1
Title string 任務名稱
RunLoop bool 是否按週期執行
CronExpression string cron表示式,如果RunLoop為true則必填
AssemblyName string 程式集名稱
ClassName string 執行類名稱,包含完整名稱空間
StartDate DateTime 任務開始時間
EndDate DateTime 任務停止時間,為空表示不限停止時間
Remark string 任務描述說明
Keepers List<int> 監護人id
Nexts List<guid> 子級任務id
Executors List<string> 執行節點名稱
RunNow bool 建立成功是否立即啟動
Params List<ScheduleParam> 自定義參數列,也可以通過CustomParamsJson欄位直接傳json格式字串

ScheduleParam:

引數名稱 引數型別 是否必填 說明
ParamKey string 引數名稱
ParamValue string 引數值
ParamRemark string 引數說明
HttpClient client = new HttpClient();
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>();
args.Add(new KeyValuePair<string, string>("MetaType", "1"));
args.Add(new KeyValuePair<string, string>("RunLoop", "true"));
args.Add(new KeyValuePair<string, string>("CronExpression", "33 0/8 * * * ?"));
args.Add(new KeyValuePair<string, string>("Remark", "By Xunit Tester Created"));
args.Add(new KeyValuePair<string, string>("StartDate", DateTime.Today.ToString("yyyy-MM-dd HH:mm:ss")));
args.Add(new KeyValuePair<string, string>("Title", "程式集介面測試任務"));
args.Add(new KeyValuePair<string, string>("AssemblyName", "Hos.ScheduleMaster.Demo"));
args.Add(new KeyValuePair<string, string>("ClassName", "Hos.ScheduleMaster.Demo.Simple"));
args.Add(new KeyValuePair<string, string>("CustomParamsJson", "[{\"ParamKey\":\"k1\",\"ParamValue\":\"1111\",\"ParamRemark\":\"r1\"},{\"ParamKey\":\"k2\",\"ParamValue\":\"2222\",\"ParamRemark\":\"r2\"}]"));
args.Add(new KeyValuePair<string, string>("Keepers", "1"));
args.Add(new KeyValuePair<string, string>("Keepers", "2"));
//args.Add(new KeyValuePair<string, string>("Nexts", ""));
//args.Add(new KeyValuePair<string, string>("Executors", ""));
HttpContent reqContent = new FormUrlEncodedContent(args);
var response = await client.PostAsync("http://localhost:30000/api/Task/Create", reqContent);
var content = await response.Content.ReadAsStringAsync();
Debug.WriteLine(content);
3.建立HTTP任務
  • 介面地址:http://yourip:30000/api/task/create

  • 請求型別:POST

  • 引數格式:application/x-www-form-urlencoded

  • 返回結果:建立成功返回任務id

  • 參數列:

引數名稱 引數型別 是否必填 說明
MetaType int 任務型別,這裡固定是2
Title string 任務名稱
RunLoop bool 是否按週期執行
CronExpression string cron表示式,如果RunLoop為true則必填
StartDate DateTime 任務開始時間
EndDate DateTime 任務停止時間,為空表示不限停止時間
Remark string 任務描述說明
HttpRequestUrl string 請求地址
HttpMethod string 請求方式,僅支援GET\POST\PUT\DELETE
HttpContentType string 引數格式,僅支援application/json和application/x-www-form-urlencoded
HttpHeaders string 自定義請求頭,ScheduleParam列表的json字串
HttpBody string 如果是json格式引數,則是對應引數的json字串;如果是form格式引數,則是對應ScheduleParam列表的json字串。
Keepers List<int> 監護人id
Nexts List<guid> 子級任務id
Executors List<string> 執行節點名稱
RunNow bool 建立成功是否立即啟動
HttpClient client = new HttpClient();
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>();
args.Add(new KeyValuePair<string, string>("MetaType", "2"));
args.Add(new KeyValuePair<string, string>("RunLoop", "true"));
args.Add(new KeyValuePair<string, string>("CronExpression", "22 0/8 * * * ?"));
args.Add(new KeyValuePair<string, string>("Remark", "By Xunit Tester Created"));
args.Add(new KeyValuePair<string, string>("StartDate", DateTime.Today.ToString("yyyy-MM-dd HH:mm:ss")));
args.Add(new KeyValuePair<string, string>("Title", "Http介面測試任務"));
args.Add(new KeyValuePair<string, string>("HttpRequestUrl", "http://localhost:56655/api/1.0/value/jsonpost"));
args.Add(new KeyValuePair<string, string>("HttpMethod", "POST"));
args.Add(new KeyValuePair<string, string>("HttpContentType", "application/json"));
args.Add(new KeyValuePair<string, string>("HttpHeaders", "[]"));
args.Add(new KeyValuePair<string, string>("HttpBody", "{ \"Posts\": [{ \"PostId\": 666, \"Title\": \"tester\", \"Content\":\"testtesttest\" }], \"BlogId\": 111, \"Url\":\"qweqrrttryrtyrtrtrt\" }"));
HttpContent reqContent = new FormUrlEncodedContent(args);
var response = await client.PostAsync("http://localhost:30000/api/Task/Create", reqContent);
var content = await response.Content.ReadAsStringAsync();
Debug.WriteLine(content);
4.建立延時任務
  • 介面地址:http://yourip:30000/api/delaytask/create

  • 請求型別:POST

  • 引數格式:application/x-www-form-urlencoded

  • 返回結果:建立成功返回任務id

  • 參數列:

引數名稱 引數型別 是否必填 說明
SourceApp string 來源
Topic string 主題
ContentKey string 業務關鍵字
DelayTimeSpan int 延遲相對時間
DelayAbsoluteTime DateTime 延遲絕對時間
NotifyUrl string 回撥地址
NotifyDataType string 回撥引數格式,僅支援application/json和application/x-www-form-urlencoded
NotifyBody string 回撥引數,json格式字串
for (int i = 0; i < 5; i++)
{
    int rndNum = new Random().Next(20, 500);
    List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>();
    args.Add(new KeyValuePair<string, string>("SourceApp", "TestApp"));
    args.Add(new KeyValuePair<string, string>("Topic", "TestApp.Trade.TimeoutCancel"));
    args.Add(new KeyValuePair<string, string>("ContentKey", i.ToString()));
    args.Add(new KeyValuePair<string, string>("DelayTimeSpan", rndNum.ToString()));
    args.Add(new KeyValuePair<string, string>("DelayAbsoluteTime", DateTime.Now.AddSeconds(rndNum).ToString("yyyy-MM-dd HH:mm:ss")));
    args.Add(new KeyValuePair<string, string>("NotifyUrl", "http://localhost:56655/api/1.0/value/delaypost"));
    args.Add(new KeyValuePair<string, string>("NotifyDataType", "application/json"));
    args.Add(new KeyValuePair<string, string>("NotifyBody", "{ \"Posts\": [{ \"PostId\": 666, \"Title\": \"tester\", \"Content\":\"testtesttest\" }], \"BlogId\": 111, \"Url\":\"qweqrrttryrtyrtrtrt\" }"));
    HttpContent reqContent = new FormUrlEncodedContent(args);
    var response = await client.PostAsync("http://localhost:30000/api/DelayTask/Create", reqContent);
    var content = await response.Content.ReadAsStringAsync();
    Debug.WriteLine(content);
}
4.框架簡單分析
1.全域性設計

根據官網的設計圖,以及操作的流程,簡單總結一下任務全域性流程為使用者端—–>master——>work—–>執行任務。從大的架構方向的思想就是,將原有單個服務業務和任務排程混合的方式做一些改變,使業務排程分離,專門把任務排程部分剝離出來,作為一個獨立的程序,來統一呼叫管理任務,而原有服務只做業務處理

####### 2.Master和Work分析

我們主要從主節點從節點資料表這幾個方面來簡單分析,整個業務的時序流程,從本質來看並不複雜masterWork之間的關係是相互配合的,可能乍一看下面整個圖有點混亂,下面來解釋一下,其實Master節點將任務新增資料中,然後work節點,去從對應的資料表中取出任務資料,然後根據任務對應的設定生成設定資訊,呼叫Quartz執行任務,這就是我們整個框架中最核心的業務。

當然master和work除了主要承載了整個管理系統的UI視覺化、後臺業務操作任務執行之外,如果從細節以及實現方式來說主要做了以下事情:

Master

  • 1.分配任務執行選擇節點
  • 2.對work節點進行健康檢查,對任務進行故障轉移

Work

  • 1.取出任務設定資訊
  • 2.使用Quartz根據設定執行任務
  • 3.使用反射呼叫程式集
  • 4.使用httpclient呼叫http 介面