quartz叢集增強版🎉

funnyZpC發表於2024-11-07

quartz叢集增強版😘

轉載請著名出處https://www.cnblogs.com/funnyzpc/p/18534034

開源地址https://github.com/funnyzpc/quartz

這是除了mee_admin之外,投入時間精力最多的一次開源了,quartz叢集增強版前前後後花費了我四月有餘的時間精力開發而來,實屬不易~
先簡要的說下:quartz叢集增強版quartz2.3.2 版本改造,對原有功能進行了部分縮減, 同時也對現有的痛點做了大量的增強,這些增強包括但不限於如下:

1. 修改任務引數序列化及儲存

將預設引數儲存方式由blob大欄位方式修改為定長字串方式,保證後管修改即所得,同時內部使用 org.json 庫對json字串序列化及反序列化,使用起來更便捷
同時也將傳參配置由原版的 K/V(物件序列化) 改為(Map)物件(eg:{"aa":1})或(Array)列表(eg:[11,true,{"bb":"her"}]) 的方式,使用時視json結構,可使用
context.getJobDataMap()context.getJobDataList() 直接獲取配置的json引數,更簡單快捷;順帶一提:也可透過 context.getJobData() 拿出原始儲存的json字串😊

2. 分離了執行端及配置管理端

這是很大的改變,如果是執行端使用,只需新增依賴 quartz-core,如果是後管配置則只需要連線執行庫(資料庫)後使用依賴 quartz-client 來配置,管理叢集或分散式的執行端也只需要
使用此來配置即可(前提是連線同一個庫表), 這個改變在原版的quartz中是不可想象的,原版的quartz執行端與配置端耦合這種不太美妙的方式著實令人頭疼與不解😂

3. 使用樂觀鎖

這同樣也是重大的變化,原版採用lock錶行級悲觀鎖,每次變更都要競爭同一個 STATE_ACCESSTRIGGER_ACCESS ,同時每次執行加鎖釋放鎖的過程還需配合執行緒本地變數(ThreadLocal)來使用,看起來有些沒必要而十分的笨重的同時系統開銷也比較大
quartz集權增強版不再使用 lock 表悲觀鎖,而是使用 UPDATE ... WHERE ... 方式(樂觀鎖),雖同樣存在一定的查詢開銷,但寫db的開銷大大地減少了。
同時,需要說明的是使用樂觀鎖似乎還是有些不夠,可目前並沒有測試出bug(也許是測試不夠嚴格吧...),如果有好的idea 懇請告知哈~ 🤦

4. 簡化了表結構以及數量

由於底層去掉了triggermemory 單機等相關邏輯,故表結構及邏輯也做了相應的簡化,由原版的11張表簡化為4張表,對,沒錯~,就是四張表,四張表就ok💪
表的整體設計主要參考了 mee_timed 這個同樣由我寫的定時任務元件,目前 這四張表為:

  • QRTZ_APP 應用表: 用於定義應用,尤其是對於管理分散式應用十分有用,同時執行端啟動時會自動新增或更新 無需手動新增資料
  • QRTZ_NODE 節點表:用於定義應用下的執行節點,這是對於叢集應用十分有用,比如你需要增量釋出時可透過啟停節點以增量更新執行端,同樣表資料也是自動新增或更新,十分方便
  • QRTZ_JOB 任務配置表: 配置任務的基本資訊(不包含執行事件項,一個job任務對應多個執行配置/時間項),此表主要定義任務的執行類以及關聯的應用資訊
  • QRTZ_EXECUTE 執行配置表: 執行配置必須關聯一個任務配置(job),執行配置也有獨立的狀態可供後管操作

5. 去掉了group(組)

這是個不太有用的概念,組在絕大數使用quartz的開發者來說十分困惑(至少我經歷的專案大多是這樣),group的使用如有不慎會跟預期存在差異,因為有 group 這一層的存在,配置任務也略顯有些臃腫。。。🤨🤨
去掉了 group 的同時也去掉了 TriggerKey 及相關的邏輯,這樣就基本淡化了 group 的概念及使用,好處不言而喻。

6. 相容原版quartz的配置項及整合方式

對於常用的springboot框架,整合方式與原版的quartz的方式並無太大區別,主要區別是隻需匯入 quartz叢集增強版 提供的表即可,starterautoconfigure)中無用的配置類及方法都做了相容
還有就是原有的context稍有變化,主要是去掉了TriggerKey以及增強json傳值帶來的變更,基本使用就無任何區別~ 😅😅

7. 缺火/熄火(MisfireHandler)及叢集(ClusterManager)處理

首先,這個變更也蠻大的。。。
先說下缺火是什麼,由於quartz內所有執行時間點都是透過對應型別的 Trigger(eg: CronTriggerImplSimpleTriggerImpl) 計算出來的,一旦出現不可預知的錯誤以及停機,則執行時間點無法向前推進,如果補償或恢復機制,則 fireTrigger 的任務掃描無法掃到造成任務無法繼續執行,這就是缺火(Misfire),
解釋的並不好,請大神給斧正哈😂
因為原版的quartz在這兩塊存在併發(叢集) 以及全域性悲觀鎖的存在,一旦觸發 Misfire 則可能導致任務存在不可知的問題,同時 MisfireHandlerClusterManager 為兩個獨立不同的執行緒任務(輪詢),但使用的鎖是同一把,邏輯處理就存在時效性問題(延遲)
所以對於本 quartz叢集增強版 就不同了😉😉, 現在我將這兩招合一, 使用 ClusterMisfireHandler 來處理 Misfire 任務以及節點check及清理任務,同樣是使用資料庫鎖,但是在 ClusterMisfireHandler 內部做了併發最佳化,以保證同一時間一個應用下只有一個節點執行,這點而很重要!

8. 其他

  • 簡化了執行緒池的管理同時也相容原有的 SimpleThreadPool
  • 由於傳參的變化也無需對儲存引數的大欄位的操作做各個廠商的資料庫的相容

架構設計

quartz-client+後管整合效果

目前可能存在及已知的問題

  • 不管是 ClusterMisfireHandler 維護叢集及缺火,還是 QuartzSchedulerThread 掃任務 都是按頻度來的,其中 QuartzSchedulerThread 是5s一次迴圈 ,ClusterMisfireHandler 則是15s一次迴圈,這樣問題就來了
  • 新增任務時,若您是10點整新增的任務則任務最快也得10點過5s後才可執行,這是目前 任務掃描的頻度及當前架構而為之的
  • QuartzSchedulerThread 在掃描批次任務後不斷迴圈以到執行時間點時,若關閉節點及應用也只有等當前次執行完成後才會停止任務
  • QuartzSchedulerThread 的鎖的處理(叢集環境下如何保證一個任務只被一個節點執行)可能存在缺陷,但目前排查不出問題,這需要使用者花時間測試以排查根本原有
  • 去掉 group 對於 group 的使用者來說這是不好的訊息,這算一個吧😂
  • 目前僅支援 postgresql、mysql、oracle 這三個廠商的資料庫支援,並經過測試透過,這是使用限制
  • client sdk (quartz-client) 雖已經過測試透過,可能存在未知bug及設計缺陷

相關文章