1. 程式人生 > >集群環境下定時任務調度問題與方案探討

集群環境下定時任務調度問題與方案探討

執行 定時任務 集群 調度

  • 摘要

  • 問題:從單機擴展到集群

  • 方案一:不做改造,直接擴展

  • 方案二:多處調度、一處執行

  • 方案三:一處調度、一處執行

  • 方案四:一處調度、多處執行

  • 方案五:多處調度、多處執行


摘要

從改造工作量、可用性、負載均衡、資源利用等方面,簡單介紹了幾種集群環境下定時任務調度的方案。


問題:從單機擴展到集群

單機環境的定時任務很簡單。無論是用比較原始的Timer,還是用自成體系的quartz、spring-scheduler,都可以輕松寫意的實現功能。


但是,當應用水平擴展到集群環境下時, 定時任務會出現重復調度、重復執行,可能帶來資源浪費、數據錯誤等問題。


方案一:不做改造,直接擴展

對有些定時任務來說,重復調度、重復執行並不構成大問題。例如刪除過期數據任務、數據監控任務等,除了會造成一定的資源浪費之外,其實無傷大雅。


從功能上來說,只有嚴格冪等的任務才可以直接擴展。性能方面來說,這個方案適合對網絡、內存等資源壓力小的定時任務。另外,如果定時任務需要處理的資源本身不在集群服務之間共享,那麽必然要使用這個方案:例如檢查服務本地緩存數據過期情況的定時任務就屬於這種情況。


工作量

由於不需要對單機環境的定時任務進行改造,因此這個方案的改造工作量是非常小的。

只不過,在開發單機環境的定時任務時就要重點考慮如何保證任務的冪等性,這可能是一項額外的工作。


可用性

高可用性是沒的說的。集群中的每臺服務都有一套獨立、完整的任務調度、執行功能,只要有一臺服務存活,就可以保證任務完整執行。


負載均衡

在負載均衡方面,這個方案是最差的:它實際上是“負載翻倍”。每臺服務器需要都承擔一個任務的全部壓力;對網絡、數據庫等共享資源來說,壓力更是N倍增長。所以前面才會提到:這個方案適合對網絡、內存等資源性能壓力小的任務。


資源利用

如果說這個方案在負載均衡方面得分為0的話,資源利用上就是負分。


方案二:多處調度、一處執行

方案一可以說是“多處調度、多處執行”,方案二則是“多處調度、一處執行”。這種方案的基本思路是:雖然多臺服務同時運行調度機制,但通過某種機制來保證只有一臺服務能最終執行任務。這種機制可以是quartz的集群調度功能,也可以是zookeeper的多活選主機制,還可以是分布式鎖機制。

由於這個方案保證“一處執行”,因此它並不要求定時任務具有冪等性,適用性更廣。


工作量

整體而言,這個方案只改造定時任務的調度機制,不涉及執行機制。而對於定時任務來說,調度機制比較統一,執行功能則變化更多。因此,盡管工作量與實際使用的機制有關,但這個方案並不算太麻煩。

以我此前參與過的一個的定時任務多活改造為例,從使用spring-scheduler的單機調度功能改造為quartz的集群調度功能,工作量、工期、風險等都在可控範圍之內。


可用性

只要實現了集群部署,可用性一定是上了一個臺階的。


不過,一般來說,這個方案總會引入一個第三方機制來決定由哪臺服務來執行任務(quartz使用數據庫、zookeeper使用ZK服務等)。這個第三方機制,無論多麽可靠、可用,多多少少還是會引入一些不可用風險。例如使用zookeeper選主機制,如果因為網絡、機房等緣故導致選主失敗,進而使得“多處調度”之後“處執行”,也會使得定時任務引發問題。


另外,“一處執行”也會增加可用性風險。如果某個定時任務由於某臺服務自身的問題執行失敗,我們需要額外的機制(如spring-batch的restart機制)來處理。


只不過這種風險概率非常低,大部分時候我們都直接忽略掉了。


負載均衡

負載均衡是這個方案的一個“黑點”。由於只能保證“一處執行”,同一個定時任務的所有壓力都在這一臺服務上;其它服務即使空閑、可用,也只能袖手旁觀。


但是,這個方案可以把不同的定時任務“負載均衡”到不同的服務上執行,從而避免所有任務都在同一臺服務上執行的極端情況。


資源利用

簡單分析一下,我們可以知道,當不同任務的資源占用(內存/cpu/網絡等資源的使用量,以及資源占用時間)比較平均時,這個方案對資源的利用率比較高。但是如果各任務間相差較大(如任務A執行時間1小時,任務B執行時間2分鐘),就會造成一定的資源浪費。


方案三:一處調度、一處執行

方案二的調度機制會在多臺服務上同時運行,因此也可以稱之為“分布調度,一處運行”。方案三則將調度機制集中到一套調度服務上,由調服服務進行“集中調度”,然後再由應用服務“一處執行”。

關於這種方案已有不少實現,如ELASTIC-JOB等等。不過其中有一些方案,實際上是“多處調度、一處執行”。按下不表。


工作量

這種方案的改造量會比較大。


雖然定時任務可以分為調度和執行兩部分,但大部分情況下,這兩部分代碼結合得都比較緊密。方案三實際上是將“調度”功能與“執行”工作剝離開(有時甚至會把二者部署為不同的服務)。拆分原有代碼的工作量可見一斑。


可用性

參見方案二。


負載均衡

參見方案二。


資源利用

參見方案二。


方案四:一處調度、多處執行

方案四是方案三的一個擴展。在方案三把“調度”與“執行”拆開以後, 這個方案開始把觸角伸向了“執行”功能,並將“執行”功能拆分成“讀取-處理”兩部分(是的,參考了spring-batch的reader-processer-writer模式)。


在方案四中,“調度”功能只是調度“讀取”功能,即在指定的時間點讀取出所有需要處理的數據。讀出數據之後,利用消息隊列等機制,將數據分給多個“處理”服務。


在我們系統中就有類似機制。某個任務使用的是quartz的集群部署方案來保證“一處調度”;任務運行時,會先讀取出當天需要處理的數據,並將其數據發送到ActiveMQ的隊列中,交由相關功能來處理。


工作量

這個方案不僅要把“調度”功能單獨拆出來,還要把“執行”功能再次拆分為“讀取”和“處理”,並分別進行部署。其中的難度可想而知。


可用性

相比方案二和方案三,這個方案引入了更多的外部依賴。所以至少理論上,它的可用性(可靠性?)是最低的。


負載均衡

這個方案的負載均衡能力來自於將“讀取”到的數據分發給“處理”服務時所使用的機制。如果使用消息隊列(如ActiveMQ),則它也能將任務負載均衡地分發給集群中的多臺服務。


資源利用

對定時任務來說,這個方案可以相當充分地利用系統資源。

但是,對消息隊列、網絡等“額外”資源來說,這個方案在很大程度上會加重它們的負擔。


方案五:多處調度、多處執行

雖然方案一也是“多處調度、多處執行”,但方案五跟它是有差別的。


方案五將“執行”機制進一步拆分為“讀取”、“判斷”、“執行”三部分。首先讀取定時任務所需處理的數據全集;然後利用分布式鎖等機制,逐個判斷讀取到的某條數據是否應當由當前服務執行;只有通過了判斷的數據才會進入到執行階段。


相比“執行”,方案五對“調度”機制基本沒做處理。單機環境下怎麽調度,集群環境下仍然怎麽調度。


工作量

方案五不變更“調度”機制,改造工作量全在“執行”機制上。而對執行機制的改造也只是“插入”一個步驟,而並不修改、拆分基本流程。這種改造工作相對來說還是比較輕松的。


可用性

同樣實現了集群部署,方案五的可用性是有保證的。由於調度、執行都在多臺服務上同時運行,無論哪一臺服務出現問題,其它服務仍能正常的調度和執行任務,受到影響的可能只是那臺服務上正在處理的一條(一批)數據。


並且,它對外部環境的依賴也比較小(基本也就引入了一個分布式鎖),相對其它方案來說,風險也更低。


負載均衡

懶得敲字了。


資源利用

方案五相當於把一個任務所需處理的數據平均分成N份,均勻的交給集群中的服務來處理。因此,它對資源的利用率可以說是最高的。


不過,在讀取數據階段,由於要讀取一個“全集”,可能會帶來一些資源壓力。


本文出自 “編程的摩羯男” 博客,請務必保留此出處http://winters1224.blog.51cto.com/3021203/1963788

集群環境下定時任務調度問題與方案探討