螞蟻調度AntJob-分佈式任務調度系統

分佈式任務調度系統,純NET打造的重量級大數據實時計算平臺,萬億級調度經驗積累!面向中小企業大數據分析場景。

開源地址:https://github.com/NewLifeX/AntJob

使用教程:https://www.yuque.com/smartstone/blood/antjob

體驗地址:http://ant.newlifex.com

功能特點

AntJob的核心是螞蟻算法把任意大數據拆分成爲小塊,採用螞蟻搬家策略計算每一塊!

(螞蟻搬家,一個饅頭掉在地上,衆多小螞蟻會把饅頭掰成小塊小塊往家裏般!)

該算法設計於2008年,最開始用於處理基金公司的短信/郵件/傳真羣發(每批兩百萬)和電話話費分析(上百種國際長途計費規則),數據量不算大,但是有一定複雜度,並且要求支持持續處理(實時計算)以及出錯重試。

2016年在中通快遞某產品項目中使用該算法進行大數據實時計算,成功挑戰每日1200萬的訂單。並進一步發展衍生成爲重量級實時計算平臺,集分佈式計算、集羣調度、配置中心、負載均衡、故障轉移、跨機房冗餘、作業監控告警、百億級數據清洗、超大Redis緩存(>2T)於一身,於2019年達到每年萬億級計算量(2019年雙十一日訂單量破億)。

AntJob是開源簡化版,僅提供分佈式計算和集中調度能力,支持百億級調度(需要改造)。

AntJob主要功能點:

  1. 作業處理器。每一個最小業務模塊實現一個處理器類,用於處理這一類作業。例如同步數據表時,每張表寫一個處理器類,並在調度中心註冊一個作業,調度中心按照作業時間切片得到任務,然後把任務(主要包含時間區間)分派給各個計算節點上的處理器類執行。又如,每天彙總計算是一個作業,而每月彙總計算又是另一個作業;

  2. 任務上下文。作業處理器類實例化以後,將反覆向調度中心申請任務來執行,每個任務的上下文核心數據是時間區間(數據調度)、時間點(定時調度)、消息體(消息調度)。調度中心記錄任務處理結果;

  3. 數據切片。支持按照時間區間(如5秒)把大數據切分爲小片,也即是數據調度,處理過最大單表60億行;

  4. 定時調度。支持定時執行(秒級)指定業務邏輯,每個執行時間點得到一個任務;

  5. 任務重試。每個任務完整記錄處理結果,失敗任務在延遲一段時間後將會自動重新分派(可能由原節點或其它節點執行);

  6. 任務重置。支持批量重置已執行完成的任務,讓其再次執行處理;

  7. 作業面板。在Web控制檯上可查看每個應用所有作業的運行狀態,或修改參數;

  8. 作業重置。調整作業參數,讓其再次處理某段時間的任務數據,例如重算過去一個月的數據;

其它細節功能將穿插在以下各主要功能點中進行講解。

定時調度

以下源碼位於 https://github.com/NewLifeX/AntJob/tree/master/Samples/HisAgent 

新建項目

新建.net core 3.1項目,從nuget引用 AntJob。實例化一個調度器Scheduler,配置網絡提供者。

using System;
using AntJob;
using AntJob.Providers;
using NewLife.Log;
namespace HisAgent
{
    class Program
    {
        static void Main(string[] args)
        {
            XTrace.UseConsole();
            var set = AntSetting.Current;
            // 實例化調度器
            var sc = new Scheduler();
            // 使用分佈式調度引擎替換默認的本地文件調度
            sc.Provider = new NetworkJobProvider
            {
                Server = set.Server,
                AppID = set.AppID,
                Secret = set.Secret,
            };
            // 添加作業處理器
            sc.Handlers.Add(new HelloJob());
            // 啓動調度引擎,調度器內部多線程處理
            sc.Start();
            Console.WriteLine("OK!");
            Console.ReadKey();
        }
    }
}

然後添加第一個定時調度的作業處理器

using System;
using AntJob;
namespace HisAgent
{
    internal class HelloJob : Handler
    {
        public HelloJob()
        {
            // 今天零點開始,每10秒一次
            var job = Job;
            job.Start = DateTime.Today;
            job.Step = 10;
        }
        protected override Int32 Execute(JobContext ctx)
        {
            // 當前任務時間
            var time = ctx.Task.Start;
            WriteLog("新生命螞蟻調度系統!當前任務時間:{0}", time);
            // 成功處理數據量
            return 1;
        }
    }
}

作業處理器必須繼承自Handler,並且重寫Execute實現業務邏輯。

我們這裏的業務邏輯就是輸出一行日誌,其中的ctx.Task就是切分得到的任務上下文,Start是時間點。

構造函數中設定的開始時間和步進Step,僅用於首次註冊作業到調度中心,後面就沒有用處了。

爲了編譯觀察,修改項目輸出目錄,在項目文件上點右鍵選“編輯項目文件”

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>netcoreapp3.1</TargetFramework>
  <AssemblyVersion>1.0.*</AssemblyVersion>
  <Deterministic>false</Deterministic>
  <OutputPath>..\..\Bin\HisAgent</OutputPath>
  <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>

編譯執行

代碼能編譯通過,先跑起來看看

可以看到,調度器首先連接 tcp://127.0.0.1:9999,其次 tcp://ant.newlifex.com:9999 ,而上面代碼中並沒有提及這兩個地址。其實這就是調度中心地址,默認本地用於調試,如果鏈接失敗再連接公開版調度中心,位於配置文件中:

/// <summary>螞蟻配置。主要用於網絡型調度系統</summary>
[Config("Ant")]
public class AntSetting : Config<AntSetting>
{
    #region 屬性
    /// <summary>調試開關。默認false</summary>
    [Description("調試開關。默認false")]
    public Boolean Debug { get; set; }
    /// <summary>調度中心。逗號分隔多地址,主備架構</summary>
    [Description("調度中心。逗號分隔多地址,主備架構")]
    public String Server { get; set; } = "tcp://127.0.0.1:9999,tcp://ant.newlifex.com:9999";
    /// <summary>應用標識。調度中心以此隔離應用,默認當前應用</summary>
    [Description("應用標識。調度中心以此隔離應用,默認當前應用")]
    public String AppID { get; set; }
    /// <summary>應用密鑰。</summary>
    [Description("應用密鑰。")]
    public String Secret { get; set; }
    #endregion
    #region 方法
    /// <summary>重載</summary>
    protected override void OnLoaded()
    {
        if (AppID.IsNullOrEmpty())
        {
            var asm = Assembly.GetEntryAssembly();
            if (asm != null) AppID = asm.GetName().Name;
        }
        base.OnLoaded();
    }
    #endregion
}

其實上面Main函數中已經看到從配置文件裏面讀取Server+AppID+Secret,該配置類讀取的配置文件在這:

AppID默認取本應用名,Secret由調度中心生成並下發。

調度中心默認打開自動註冊AutoRegistry,任意應用登錄時自動註冊,省去人工配置應用賬號的麻煩。

企業內部正式場景使用時,爲安全期間,建議關閉自動註冊。

再來看看前面跑起來的日誌

21:33:08.470  1 N - 啓動任務調度引擎[AntJob.Providers.NetworkJobProvider],作業[1]項,定時5秒
21:33:08.471  1 N - HelloJob 開始工作 False 區間(2020-04-09 00:00:00, 0001-01-01 00:00:00) Offset=15 Step=10 MaxTask=8
21:33:08.587  5 Y Job HelloJob 停止工作
21:33:09.467  7 Y T [180.174.185.180:53926]上線!X3

啓動了調度引擎,帶有一個作業;

作業HelloJob,就是我們通過 sc.Handlers.Add(new HelloJob()) 添加進去的作業處理器實例;

HelloJob狀態False,處於停止工作狀態,那是因爲作業註冊後,默認都是停止狀態,需要去web控制檯配置參數後手工開啓;

最後一個xxx上線,這是螞蟻調度的Peers功能,可以探測得到當前應用下所有已連接節點的狀態。當HisAgent部署於多個服務器時,每個進程都可以通過Peers得知其它節點的存在;

作業管理

不用關閉HistAgent客戶端窗口,我們去線上web控制檯看看 http://ant.newlifex.com/

可以看到應用節點在線,點擊應用名進去作業面板

這就是我們的HelloJob作業,對應HisAgent中的HelloJob作業處理器。

它處於停用狀態,下一次執行時間是 00:00:00 ,也就是今天零點,加上10秒步進,也遠小於當前時間,因此,只要啓用該作業,調度中心將會馬上開始切分任務,並分派給客戶去執行。

我們來點擊紅色叉叉,讓它改變爲啓用狀態

幾秒後,客戶端HisAgent歡快地跑起來!它正在以10秒間隔不斷切分並執行任務。

刷新作業面板,可以看到,開始時間已經變爲當前附近的時間,右邊也有了執行次數。

點擊作業名HelloJob,進去查看任務明細

任務切分後,插入作業任務表,此時狀態爲“就緒”,等待分發給客戶端執行。

客戶端執行後,向調度中心報告執行結果,可能“完成”,可能“錯誤”。

錯誤的任務,會在1分鐘後,重新執行,最多連續錯誤10次。

隨系統自動啓動

至今我們仍然使用控制檯來跑調度程序,怎麼樣實現穩定可靠的自動化處理呢?

那就必須解決隨系統自動啓動,以及進程守護(包括Windows和Linux)的問題。

這裏推薦 NewLife.Agent,可以把調度程序包裝成爲一個 Windows服務,或者Linux守護進程,支持看門狗守護。

此處爲語雀文檔,點擊鏈接查看:https://www.yuque.com/smartstone/nx/agent

多節點部署時,推薦 星塵Stardust 中的星塵代理 StarAgent,調度程序無需修改繼續使用控制檯,由StarAgent負責拉起進程並守護,同時Stardust支持遠程多節點部署以及集中監控。

此處爲語雀文檔,點擊鏈接查看:https://www.yuque.com/smartstone/blood/stardust

雙跑,沸騰吧,分佈式計算

再開兩個HisAgent進程,查看應用在線表,可以看到有三個節點在線。

HisAgent控制檯中,可以看到各自都有機會分配了任務,每個任務有且僅有一個節點執行。

刷新作業HelloJob的任務列表,可以看到不同客戶端執行了不同的任務。

調度中心

公開版調度中心 http://ant.newlifex.com 僅用於開發測試,不建議用於生產場景。各企業內部應該自己部署調度中心。

獲取AntJob源碼 https://github.com/NewLifeX/AntJob ,編譯 AntJob.Server,然後跑起來 AntServer.exe

這是一個標準的NewLife.Agent應用,可以選擇2安裝爲Windows服務(需要管理員權限),或者Linux守護進程(需要root權限)。這裏僅爲了測試,選擇5循環調試,直接跑起來核心業務:

可以看到AntServer在tcp/udp/ipv6上監聽了9999接口,下方是它所使用的RPC接口。除了前三個內置接口意外,AntJob的接口也就7個,非常簡單!

ApiServer的具體內容可參考

此處爲語雀文檔,點擊鏈接查看:https://www.yuque.com/smartstone/nx/api_server

再啓動一個HisAgent

可以看到,它自動連接了本機這個調度中心,因爲配置文件Server裏面,127寫在第一位!

AntJob客戶端支持調度中心的故障轉移,配置多個服務端,其中一個斷開後,自動選擇下一個。

配置文件 Config\AntJob.config 很簡單,只有端口和自動註冊開關。

<?xml version="1.0" encoding="utf-8"?>
<Setting>
  <!--調試開關。默認true-->
  <Debug>true</Debug>
  <!--端口-->
  <Port>9999</Port>
  <!--自動註冊。任意應用登錄時自動註冊,省去人工配置應用賬號的麻煩,默認true-->
  <AutoRegistry>true</AutoRegistry>
</Setting>

多年使用經驗來看,還沒遇到過需要關閉自動註冊的情況,畢竟都是在企業內網。

推薦部署兩套調度中心,一套Web控制檯,共用MySql數據庫!

如果服務器足夠多,或者爲了跨機房,部署4套8套也是可以的。

Web控制檯

爲了查看作業任務狀態,以及調整參數,控制作業啓停,需要藉助控制檯。

獲取源碼 https://github.com/NewLifeX/AntJob ,編譯 AntJob.Web,執行 AntWeb.exe。也可以訪問公開版AntJob控制檯 http://ant.newlifex.com/ 。

首先可以看到應用管理,點擊應用名進去應用面板,管理該應用底下的作業。

雙擊應用所在行空白處,可查看修改應用信息

應用在線記錄每個應用實例(應用可以多跑)的實時狀態,應用歷史記錄操作歷史。

任務處理過程中,如果拋出異常,將會上報給調度中心,標記任務爲“錯誤”狀態,同時把錯誤信息記錄到作業錯誤中來。也可以通過應用或作業的快捷方式鏈接進來。

應用消息用於消息調度,消息生產者把消息推送給調度中心時,就是存儲在“應用消息”數據表中,消費的時候取出來,創建消息型任務,並從“應用消息”表中刪除。

數據調度

數據調度時AntJob毫無疑問的首席角色,它的使用佔比超過70%,可見其重要程度。

爲了方便處理大數據,我們需要新建一些輔助項目,數據結構來自某醫院。

新建數據集項目

新建 .netstandard2.0 類庫項目,nuget引用 NewLife.XCode,準備醫院的模型文件:

<?xml version="1.0" encoding="utf-8"?>
<Tables Version="9.16.7398.1902" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="http://www.newlifex.com http://www.newlifex.com/Model2020.xsd" NameSpace="HisData" ConnName="His" Output="Entity" BaseClass="Entity" IgnoreNameCase="True" xmlns="http://www.newlifex.com/Model2020.xsd">
  <Table Name="ZYBH0" Description="病人基本信息" IgnoreNameCase="False">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Bhid" ColumnName="BHID" DataType="Int32" Master="True" Description="病人ID" />
      <Column Name="XM" DataType="String" Description="姓名" />
      <Column Name="Ryrq" ColumnName="RYRQ" DataType="Int32" Description="入院日期" />
      <Column Name="Cyrq" ColumnName="CYRQ" DataType="Int32" Description="出院日期" />
      <Column Name="Sfzh" ColumnName="SFZH" DataType="String" Description="身份證號" />
      <Column Name="FB" DataType="String" Description="費用類別" />
      <Column Name="State" ColumnName="STATE" DataType="Int32" Description="狀態" />
      <Column Name="Flag" ColumnName="FLAG" DataType="Int32" Description="標記" />
      <Column Name="Remark" DataType="String" Length="500" Description="內容" />
      <Column Name="CreateUser" DataType="String" Description="創建者" />
      <Column Name="CreateUserID" DataType="Int32" Description="創建者" />
      <Column Name="CreateTime" DataType="DateTime" Description="創建時間" />
      <Column Name="CreateIP" DataType="String" Description="創建地址" />
      <Column Name="UpdateUser" DataType="String" Description="更新者" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新者" />
      <Column Name="UpdateTime" DataType="DateTime" Description="更新時間" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
    </Columns>
    <Indexes>
      <Index Columns="BHID" Unique="True" />
    </Indexes>
  </Table>
  <Table Name="ZYBHYZ0" Description="病人醫囑信息" IgnoreNameCase="False">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Bhid" ColumnName="BHID" DataType="Int32" Description="病人ID" />
      <Column Name="Mgroupid" ColumnName="MGROUPID" DataType="Int32" Master="True" Description="醫囑組號" />
      <Column Name="Kyzrq" ColumnName="KYZRQ" DataType="Int32" Description="開醫囑日期" />
      <Column Name="Tyzrq" ColumnName="TYZRQ" DataType="Int32" Description="停醫囑日期" />
      <Column Name="Kyzys" ColumnName="KYZYS" DataType="String" Description="開醫囑醫生" />
      <Column Name="State" ColumnName="STATE" DataType="Int32" Description="狀態" />
      <Column Name="CreateUser" DataType="String" Description="創建者" />
      <Column Name="CreateUserID" DataType="Int32" Description="創建者" />
      <Column Name="CreateTime" DataType="DateTime" Description="創建時間" />
      <Column Name="CreateIP" DataType="String" Description="創建地址" />
      <Column Name="UpdateUser" DataType="String" Description="更新者" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新者" />
      <Column Name="UpdateTime" DataType="DateTime" Description="更新時間" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
    </Columns>
    <Indexes>
      <Index Columns="BHID,MGROUPID" Unique="True" />
      <Index Columns="BHID" />
    </Indexes>
  </Table>
  <Table Name="ZYBHYZ1" Description="病人醫囑明細信息" IgnoreNameCase="False">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Dgroupid" ColumnName="DGROUPID" DataType="Int32" Master="True" Description="醫囑組號" />
      <Column Name="Yzbm" ColumnName="YZBM" DataType="String" Description="醫囑編碼" />
      <Column Name="Yzmc" ColumnName="YZMC" DataType="String" Description="醫囑名稱" />
      <Column Name="DJ" DataType="Decimal" Description="單價" />
      <Column Name="SL" DataType="Double" Description="數量" />
      <Column Name="FY" DataType="Decimal" Description="費用" />
      <Column Name="State" ColumnName="STATE" DataType="Int32" Description="狀態" />
      <Column Name="CreateUser" DataType="String" Description="創建者" />
      <Column Name="CreateUserID" DataType="Int32" Description="創建者" />
      <Column Name="CreateTime" DataType="DateTime" Description="創建時間" />
      <Column Name="CreateIP" DataType="String" Description="創建地址" />
      <Column Name="UpdateUser" DataType="String" Description="更新者" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新者" />
      <Column Name="UpdateTime" DataType="DateTime" Description="更新時間" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
    </Columns>
    <Indexes>
      <Index Columns="DGROUPID,YZBM" Unique="True" />
      <Index Columns="DGROUPID" />
    </Indexes>
  </Table>
  <Table Name="ZYYFQLD" Description="病人藥房請領單分月表202001" IgnoreNameCase="False">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Qlrq" ColumnName="QLRQ" DataType="Int32" Description="請領日期" />
      <Column Name="Qlsj" ColumnName="QLSJ" DataType="Int32" Description="請領時間" />
      <Column Name="Ksbm" ColumnName="KSBM" DataType="String" Description="請領科室" />
      <Column Name="Yzgroupid" ColumnName="YZGROUPID" DataType="Int32" Description="醫囑ID" />
      <Column Name="Bhid" ColumnName="BHID" DataType="Int32" Description="病人ID" />
      <Column Name="Yzbm" ColumnName="YZBM" DataType="String" Description="藥品編碼" />
      <Column Name="DJ" DataType="Decimal" Description="單價" />
      <Column Name="SL" DataType="Double" Description="請領數量" />
      <Column Name="Yfbm" ColumnName="YFBM" DataType="String" Description="發藥藥房" />
      <Column Name="Fyrq" ColumnName="FYRQ" DataType="Int32" Description="發藥日期" />
      <Column Name="State" ColumnName="STATE" DataType="Int32" Description="狀態" />
      <Column Name="Remark" DataType="String" Length="500" Description="內容" />
      <Column Name="CreateUser" DataType="String" Description="創建者" />
      <Column Name="CreateUserID" DataType="Int32" Description="創建者" />
      <Column Name="CreateTime" DataType="DateTime" Description="創建時間" />
      <Column Name="CreateIP" DataType="String" Description="創建地址" />
      <Column Name="UpdateUser" DataType="String" Description="更新者" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新者" />
      <Column Name="UpdateTime" DataType="DateTime" Description="更新時間" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
    </Columns>
    <Indexes>
      <Index Columns="BHID" />
    </Indexes>
  </Table>
  <Table Name="ZDSF" Description="收費字典" IgnoreNameCase="False">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="BM" DataType="String" Master="True" Nullable="False" Description="編碼" />
      <Column Name="DH" DataType="String" Description="拼音碼" />
      <Column Name="MC" DataType="String" Description="名稱" />
      <Column Name="DJ" DataType="Decimal" Description="單價" />
      <Column Name="DW" DataType="String" Description="單位" />
      <Column Name="Mzyflb" ColumnName="MZYFLB" DataType="Int32" Description="門診費用類別" />
      <Column Name="Zyfylb" ColumnName="ZYFYLB" DataType="Int32" Description="住院費用類別" />
      <Column Name="Zfbl" ColumnName="ZFBL" DataType="Double" Description="自費比例" />
      <Column Name="CreateUser" DataType="String" Description="創建者" />
      <Column Name="CreateUserID" DataType="Int32" Description="創建者" />
      <Column Name="CreateTime" DataType="DateTime" Description="創建時間" />
      <Column Name="CreateIP" DataType="String" Description="創建地址" />
      <Column Name="UpdateUser" DataType="String" Description="更新者" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新者" />
      <Column Name="UpdateTime" DataType="DateTime" Description="更新時間" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
    </Columns>
    <Indexes>
      <Index Columns="BM" Unique="True" />
    </Indexes>
  </Table>
</Tables>

再去找一個 build_netcore.tt 的T4模板,可以這裏下載 http://x.newlifex.com/XCode_BuildModel.zip 。也可以從AntJob.Web項目中拷貝一個。

記得把build_netcore.tt在vs文件屬性的自定義工具設置爲TextTemplatingFileGenerator。

由於netstandard項目輸出目錄中不包括XCode.dll等引用程序集,因此build.tt需要改一下

<#@ template language="C#" hostSpecific="true" debug="true" #>
<#@ assembly name="netstandard" #>
<#@ assembly name="$(ProjectDir)\..\..\DLL\NewLife.Core.dll" #>
<#@ assembly name="$(ProjectDir)\..\..\DLL\XCode.dll" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="XCode.Code" #>
<#@ output extension=".log" #>
<#
    // 設置當前工作目錄
    PathHelper.BasePath = Host.ResolvePath(".");
    // 導入模型文件並生成實體類,模型文件、輸出目錄、命名空間、連接名、中文文件名、表名字段名大小寫
    //EntityBuilder.Build(String xmlFile = null, String output = null, String nameSpace = null, String connName = null, Boolean? chineseFileName = true,Boolean? nameIgnoreCase = null);
    EntityBuilder.Build();
    //var tables = DAL.ImportFrom("Company.Project.xml");
    //EntityBuilder.Build(tables);
#>

我們從AntJob.Web中拷貝兩個文件 NewLife.Core.dll 和 XCode.dll 到外面的DLL目錄中,供build.tt調用。如果實際目錄不同,可以修改build.tt文件的指向。

在build.tt文件上右鍵,執行自定義工具,即可生成一批實體類。

編譯通過

新建Web項目

新建 .netcore3.1 的web項目,Nuget引用 NewLife.Cube.Core,並引用項目HisData。

該Web項目主要用於查看和管理那些數據表的數據。

修改Main函數,增加 XTrace.UseConsole,用於攔截所有日誌

public class Program
{
    public static void Main(string[] args)
    {
        XTrace.UseConsole();
        CreateHostBuilder(args).Build().Run();
    }
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Startup.cs 中引用魔方,services.AddCube()/app.UseCube()

編譯運行,瀏覽器訪問 http://localhost:5000/Admin

可以看到魔方跑起來了,但是還沒有我們需要的數據頁面。

新建His控制器區域

區域文件內容:

using System;
using System.ComponentModel;
using NewLife.Cube;
namespace HisWeb.Areas.His
{
    [DisplayName("醫院管理")]
    public class HisArea : AreaBase
    {
        public HisArea() : base(nameof(HisArea).TrimEnd("Area")) { }
        static HisArea() => RegisterArea<HisArea>();
    }
}

爲每個實體類新建一個控制器,如下

using HisData;
using NewLife.Cube;
namespace HisWeb.Areas.His.Controllers
{
    [HisArea]
    public class ZYBH0Controller : EntityController<ZYBH0>
    {
        static ZYBH0Controller() => MenuOrder = 100;
    }
}

編譯項目,執行 HisWeb.exe,刷新瀏覽器頁面,即可看到每張數據表對應了一個頁面。

生成病人數據

在引用項目HisData。

新建一個作業處理器 BuildPatient 用於隨機生成病人

using System;
using System.Collections.Generic;
using AntJob;
using HisData;
using NewLife.Security;
using XCode;
namespace HisAgent
{
    internal class BuildPatient : Handler
    {
        public BuildPatient()
        {
            var job = Job;
            job.Start = DateTime.Today;
            job.Step = 15;
        }
        protected override Int32 Execute(JobContext ctx)
        {
            // 隨機造幾個病人
            var count = Rand.Next(1, 9);
            var list = new List<ZYBH0>();
            for (var i = 0; i < count; i++)
            {
                var time = DateTime.Now.AddSeconds(Rand.Next(-30 * 24 * 3600, 0));
                var time2 = time.AddSeconds(Rand.Next(3600, 10 * 24 * 3600));
                var pi = new ZYBH0
                {
                    Bhid = Rand.Next(999999),
                    XM = Rand.NextString(8),
                    Ryrq = time,
                    Cyrq = time2,
                    Sfzh = Rand.NextString(18),
                    FB = Rand.NextString(6),
                    State = Rand.Next(8),
                    Flag = Rand.Next(2),
                };
                list.Add(pi);
            }
            list.Insert(true);
            // 成功處理數據量
            return count;
        }
    }
}

主函數中把該處理器添加到調度器

編譯運行,HisAgent將在控制檯新增一個作業,把它啓用

很快,作業處理器就跑起來了

每次定時任務所添加病人數時隨機的,我們通過Execute返回,記錄着控制檯作業任務表的“成功”字段。

去HisWeb中看看數據

很不幸,啥也沒有……

原來,我們並沒有配置數據庫連接字符串,各個應用就會默認使用SQLite數據庫,位於自己目錄中,HisAgent生成的數據,HisWeb自然就無法訪問了。

修改HisWeb輸出目錄,讓它跟HisAgent並排

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AssemblyVersion>1.0.*</AssemblyVersion>
    <Deterministic>false</Deterministic>
    <OutputPath>..\..\Bin\HisWeb</OutputPath>
    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
  </PropertyGroup>

修改配置文件appsettings.json給的鏈接字符串

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "His": {
      "connectionString": "Data Source=..\\Hisagent\\Data\\His.db",
      "providerName": "SQLite"
    }
  }
}

重新跑起來後,成功看到病人數據

清洗病人數據

由於HisAgent需要使用數據調度,除了AntJob,我們還需要從nuget引用AntJob.Extensions。

這一次,我們來實時消費病人數據,爲其生成醫囑,高仿數據清洗過程。

新增生成醫囑的作業處理器 BuildWill

using System;
using AntJob;
using HisData;
using NewLife.Security;
using XCode;
namespace HisAgent
{
    class BuildWill : DataHandler
    {
        public BuildWill()
        {
            var job = Job;
            job.Start = DateTime.Today;
            job.Step = 30;
        }
        public override Boolean Start()
        {
            // 指定要抽取數據的實體類以及時間字段
            Factory = ZYBH0.Meta.Factory;
            Field = ZYBH0._.CreateTime;
            return base.Start();
        }
        protected override Boolean ProcessItem(JobContext ctx, IEntity entity)
        {
            var pi = entity as ZYBH0;
            // 創建醫囑信息
            var will = new ZYBHYZ0
            {
                Bhid = pi.Bhid,
                Mgroupid = Rand.Next(9999),
                Kyzrq = pi.Ryrq.AddHours(1),
                Tyzrq = pi.Cyrq.AddHours(-3),
                Kyzys = Rand.NextString(8),
                State = pi.State,
            };
            will.Insert();
            return true;
        }
    }
}

數據調度的處理器基類是DataHandler,並且需要在Start之前指定實體工廠以及時間字段。調度系統將會從該表抽取數據,根據調度中心分派的時間區間(StartTime+EndTime),對時間字段進行查詢。

處理函數ProcessItem就是業務核心代碼了,也可以重寫Execute,實現批量處理。

從今天零點開始消費處理數據,步進30秒,也就是每次抽取30秒的數據來分析處理。

不要忘了在Main中把該處理器加入調度器。

跑起來,去控制檯啓用作業

可以看到,在(00:30:00, 00:30:30)區間內,得到4個病人,創建了4個醫囑。

運行HisWeb查看數據

消息調度

設計概要

計算型應用(繼承Handler)

系統架構

調度中心主從架構

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章