基於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排程存在不足的問題,主要體現在排程器與執行器的隔離上,各司其責發揮各自的優勢;