實戰錄 | 基於redis的分佈式HA調度器解決方案

實戰錄》導語

雲端衛士的新欄目《實戰錄》將會定期分享一些我們的工程師夥伴們在產品研發的過程中總結的實踐經驗,希望對於熱愛技術且關注安全領域的受衆有所裨益。本期分享人爲雲端衛士大數據工程師王國偉,將帶來基於redis的分佈式HA調度器解決方案。


1項目背景

由於雲端衛士的安全大數據平臺的離線、實時數據處理以及其他業務均有大量的定時任務的場景需求,要求定時任務器做到高可用、高併發。

具體的功能性需求如下:

1.高併發的支持:避免部分模塊(比如關係庫)形成性能瓶頸
2.分佈式HA:要求多活節點,且保證分佈式一致性,且保證同類節點的負載均衡
3.fail-over(故障轉移):要求對於調度任務能夠支持故障轉移,保證任務調度的可靠性
4.輕量級且業務無關
5.支持網絡穿透:
部分任務訪問目標地址會有獨立安全域,並不對所有分佈式節點開放
6.支持個性化jobclas代碼熱加載:避免更新時部分節點的服務中斷

 針對以上功能點,我們團隊針對現有的定時器的實現方案作了一個對比:


基於以上考慮,團隊決定自己開發一個基於redis存儲的HA的分佈式任務調度系統。


2項目架構

項目採用分佈式的方式,各功能節點拓撲結構:



各節點角色

•quartz-proxy:作爲本項目的核心調度器,proxy集羣爲無中心的設計,支持負載均衡,支持group級別的負載均衡以及fail-over
•redis:主要用於proxy端的jobstore以及分佈式協調
•client:作爲任務的CURD的發起端,與proxy進行交互實現任務的CURD的操作
•manger web console:除了兼具普通client端具有的CURD還包括了任務的web端的展現以及操作


3實現細節

基於以上設計,這裏介紹下我們在具體開發遇到的以下幾點技術關鍵點或是難點,給大家提供下技術參考。

分佈式鎖

由於proxy設計上爲無中心設計,需要通過分佈式鎖才保證集羣狀態的一致性,這裏沒有采用zookeeper作爲分佈式鎖的支持,是由於本項目原則上堅持輕量化,且儘量保證較少的依賴組件,所以採用了redis爲分佈式鎖的實現。


實現分佈式鎖,我認爲分佈式鎖需要支持以下三個基本的特性:
原子的非共享資源的佔用
 採用redis的setnx這個原子操作來標誌非共享資源的歸屬,實現原子的非共享資源的佔用
極端情況下鎖佔用(死鎖)的搶佔
 如果超過一定時間鎖依然未釋放成功,其他節點可以強行搶佔當前鎖
鎖釋放通知,保證鎖再次參與競爭的及時性
由於基於輪詢的方式對於實時性以及性能無法保證,所以這裏借用redis的PubSub模型實現消息的通知,當節點未能獲取鎖時,subscribe鎖釋放消息channel,噹噹前鎖釋放之後,向channel publish消息通知其他節點繼續參與鎖競爭。

jobstore(任務的分佈式存儲)

由於本項目的是基於quartz實現,自定義quartz任務的持久化端需要實現org.quartz.spi.JobStore接口,這裏不再羅列所有方法,需要實現的主要的方法如下:

//*jobstore初始化
public void initialize(ClassLoadHelper loadHelper,SchedulerSignaler signaler)
//任務存儲
public void storeJobAndTrigger(JobDetail newJob, OperableTrigger newTrigger)
//任務觸發
List<TriggerFiredResult> triggersFired(List<OperableTrigger> triggers) throws JobPersistenceException;
//任務完成後的回調
void triggeredJobComplete(OperableTrigger trigger, JobDetail jobDetail, CompletedExecutionInstruction triggerInstCode);
List<OperableTrigger> acquireNextTriggers(long noLaterThan, int maxCount, long timeWindow)

由於存儲採用redis,這裏基於分桶策略實現任務的優先級存儲,採用redis的有序隊列實現:

waiting_triggers:等待調度的任務隊列,所有剛加入到任務都會在計算下一次的調度時間之後按照時間順序加入此隊列
acquired_triggers:quartz會基於quartz自身任務加載策略,調用acquireNextTriggers方法load需要近期調度的任務到內存中,將任務從waiting_triggers加入到acquired_triggers隊列中
completed_triggers:當任務調度完成,會調用triggeredJobComplet,此方法將acquired_triggers轉移到此隊列中。

paused_triggers:暫停的觸發器
blocked_triggers:阻塞的觸發器
error_triggers:任務出錯時將觸發器移到此隊列中進行任務的錯誤處理

網絡穿透,支持子安全域的負載均衡以及fail-over

各個proxy在配置時會設置各個proxy的group屬性,添加任務時可以指定此字段(比如此任務只有group1的proxy網絡可達) Job信息再添加時Proxy在領取當前proxy的調度任務時需要檢查當前任務所屬的group,如果不爲空且不屬於當前proxy,則放棄領取此任務,具備相應group條件的公平競爭此任務調度權限。

個性化jobclas代碼熱加載

實現ClassLoader來支持自定義的jobsclass的熱加載。

可視化


4建議

以上爲我們團隊在分佈式調度器的基於redis的實踐,此調度器已經在我們項目中穩定運行1年以上,性能以及穩定性經過實踐的檢驗,滿足了我們項目組對分佈式調度器的需求。


如果需要重量級的實現,大家可以參考噹噹最近開源的分佈式調度器的實現:https://github.com/dangdangdotcom/elastic-job

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