1. 程式人生 > >基於Netty+Zookeeper+Quartz排程分析

基於Netty+Zookeeper+Quartz排程分析

前言

前幾篇文章分別從使用和原始碼層面對Quartz做了簡單的分析,在分析的過程中也發現了Quartz不足的地方;比如底層排程依賴資料庫的悲觀鎖,誰先搶到誰排程,這樣會導致節點負載不均衡;還有排程和執行耦合在一起,導致排程器會受到業務的影響;下面看看如何來解決這幾個問題;

思路

排程器和執行器拆成不同的程序,排程器還是依賴Quartz本身的排程方式,但是排程的並不是具體業務的QuartzJobBean,而是統一的一個RemoteQuartzJobBean,在此Bean中通過Netty遠端呼叫執行器去執行具體業務Bean;具體的執行器在啟動時註冊到Zookeeper中,排程器可以在Zookeeper獲取執行器資訊,並通過相關的負載演算法指定具體的執行器去執行,以下看簡單的實現;

執行器

1.執行器配置檔案

配置了執行器的名稱,執行器啟動的ip和埠以及Zookeeper的地址資訊;

2.執行器服務

ExecutorServer通過Netty啟動服務,並向Zookeeper註冊服務,部分程式碼如下:

在Netty中指定了編碼器解碼器,同時指定了ExecutorServerHandler用來處理排程器傳送來的訊息(更多程式碼檢視專案原始碼);最後向Zookeeper註冊服務,路徑格式如下:

job_registry是固定值,firstExecutor是配置的具體執行器名稱;

3.配置載入任務

添加註解類,用來指定具體的業務Job:

例如具體的業務Task如下所示:

在啟動執行器服務時,載入有ExecutorTask註解的任務類,此處定義的name要和排程端的名稱相互匹配;

4.執行具體業務

Netty中指定了ExecutorServerHandler用來處理接受的排程器資訊,通過反射的方式來呼叫具體的業務Job,部分程式碼如下:

serviceName對應的就是定義的”firstTask”,然後通過serviceName找到對應的Bean,然後反射呼叫,最終返回結果;

排程器

排程器還是依賴Quartz的原生排程方式,只不過排程器不在執行相關業務Task,所以相關配置也是類似,同樣依賴資料庫;

1.定義排程任務

同樣在排程端定義了名稱問firstTask的任務,可以發現此類是RemoteQuartzJobBean,並不是具體的業務Task;同時也指定了jobDataMap,用來指定執行器名稱和發現的Zookeeper地址;

2.RemoteQuartzJobBean

此類同樣繼承於QuartzJobBean,這樣Quartz才能排程Bean,在此Bean中通過jobKey和executorBean建立了IJobHandler的代理類,具體程式碼如下:

在Request中指定了InterfaceName為jobKey.getName(),也就是這裡的firstTask;通過Zookeeper發現服務時指定了executor.getExecutorName(),這樣可以在Zookeeper中找到具體的執行器地址,當然這裡的地址可能是一個列表,可以通過負載均衡演算法(隨機,輪詢,一致性hash等等)進行分配,獲取到地址後通過Netty遠端連線執行器,傳送執行job等待返回結果;

簡單測試

分別執行排程器和執行器,相關日誌如下:

1.執行器日誌

2.排程器日誌

總結

本文通過一個例項來分析如何解決原生Quartz排程存在不足的問題,主要體現在排程器與執行器的隔離上,各司其責發揮各自的優勢;