分散式計劃任務設計與實現

http://netkiller.github.io/journal/scheduler.html

Mr. Neo Chen (陳景峯), netkiller, BG7NYT


中國廣東省深圳市龍華新區民治街道溪山美地
518131
+86 13113668890


版權聲明

轉載請與作者聯繫,轉載時請務必標明文章原始出處和作者信息及本聲明。

文檔出處:
http://netkiller.github.io
http://netkiller.sourceforge.net

微信掃瞄二維碼進入 Netkiller 微信訂閲號

QQ群:128659835 請註明“讀者”

2014-09-30

摘要

本文主要通過分散式計劃任務軟件設計講述分散式軟件開發。


目錄

1. 什麼是分散式計劃任務

首先我們解釋一下計劃任務,計劃任務是指有計劃的定時運行或者周期性運行的程序,我們最常見的就是Linux “crontab”與Windows “計劃任務程序”,我們也常常借助他們實現我們的計劃任務,因它們的時間調度程序非常成熟,無需我們再開發一套。

2. 為什麼採用分散式計劃任務

起初,我們也跟大多數人一樣採用crontab調度程序,但隨着項目越來越大,系統越來越複雜,就抱漏出許多問題。

首先是高可用HA需求,當運行計劃任務的伺服器一旦出現故障,所有的計劃任務將停止工作。

其次是性能問題,越來越多的大型計劃任務程序出現,對CPU/IO密集操作,單個節點已經不能滿足我們的需求。

讓計劃任務7*24*365不間斷運行,必需有一套行之有效的方案才行,我意識到必須開發一個全新的分散式計劃任務框架,這樣開發人員無需關注怎樣實現分散式運行,集中寫任務即可。

我首先提出這個框架必需具備幾個特性:

分散式計劃任務需具備以下特性
  1. 故障轉移,我們至少使用兩個節點,當一個節點出現問題,通過健康狀態檢查程序,另一個節點會自動接管任務。

  2. 分散式運行,一個任務可以運行在多個節點之上,能夠同時運行,能夠調整運行的前後順序,能夠並發互斥控制。

  3. 節點可動態調整,最少兩個節點,可以隨時新增節點,卸載節點。

  4. 狀態共享,任務可能會涉及的通信,例如狀態同步等等。

3. 何時使用分散式計劃任務

何時使用分散式計劃任務
  1. 遇到性能問題,遇到性能問題你可能首先想到的是分伺服器,但很多應用不具備跨伺服器運行。

  2. 高可用,一個節點出現故障,另一個節點將接管並繼續運行。

  3. 災備,你可以將兩個或兩個以上的計劃任務節點分別部署在兩個以上的機房,通過HA特性任何一個機房出現故障,其他機房仍會繼續運行。

4. 分散式計劃任務的部署

兩個節點部署

兩個節點可以實現“主”、“備”方案,隊列(排隊)運行方案與並行方案,其中並行方案又分為不同運行于非同步運行,還涉及到互斥運行。

兩個以上節點部署

多節點建議採用隊列運行方案,並行方案,但不建議使用互斥並行方案(浪費資源)

5. 誰來寫分散式計劃任務

當我們的分散式計劃任務框架一旦完成,任務的編寫部分非常輕鬆,只需繼承框架程序便具備分散式運行的特性。

6. 怎麼實現分散式計劃任務

計劃任務是一個相當複雜的一塊,有操作系統計劃任務,有運用程序計劃任務,有基于TCP/IP的訪問的,有基于命令行訪問的,有定時執行的,有周期運行的,還有基于某些條件觸發運行的。總之解決計劃任務災備,要比web,cache, database 複雜的多。

圖 1. 分時方案
分時方案

嚴格劃分時間片,交替運行計劃任務,當主系統宕機後,備用系統仍然工作,只不過處理周期拉長了。缺點:周期延長了

圖 2. HA 高可用方案
HA 高可用方案

正常情況下主系統工作,備用系統守候,心跳檢測發現主系統出現故障,備用傳統啟動。缺點:單一系統,不能負載均衡,只能垂直擴展(硬件升級),無法水平擴展

圖 3. 多路心跳方案
多路心跳方案

上面的HA是三層的基于VIP技術實現,下面這個方案我採用多路心跳,做服務級,進程級,IP與連接埠級別的心跳檢測,做正常情況下主系統工作,備用系統守候,心跳檢測發現主系統出現故障,備用傳統啟動,當再次檢測到主系統工作,將執行權交回主系統.缺點:開發複雜,程序健壯性要求高

圖 4. 任務搶佔方案
任務搶佔方案

A,B 兩台伺服器同時工作,啟動需要一前一後,誰先啟動誰率先加鎖,其他伺服器只能等待,他們同時對互斥鎖進行監控,一旦發現鎖被釋放,其他服務誰先搶到誰運行,運行前首先加排他鎖。 優點:可以進一步優化實現多伺服器橫向擴展。 缺點:開發複雜,程序健壯性要求高,有時會出現不釋放鎖的問題。

圖 5. 任務輪循或任務輪循+搶佔排隊方案
任務輪循或任務輪循+搶佔排隊方案

任務輪循或任務輪循+搶佔排隊方案
  1. 每個伺服器首次啟動時加入隊列。

  2. 每次任務運行首先判斷自己是否是當前可運行任務,如果是便運行。

  3. 否則檢查自己是否在隊列中,如果在,便推出,如果不在隊列中,便加入隊列。

6.1. 分散式互斥鎖

互斥鎖也叫排它鎖,用於並發時管理多進程或多綫程同一時刻只能有一個進程或者綫程操作一個功能。如果你理解什麼是互斥鎖,便很容易理解分散式鎖。

我們將進程,綫程中的鎖延伸到互聯網上,實現對一個節點運行的進程或綫程加鎖,解鎖操作。這樣便能控制節點上進程或綫程的並發。

			
+------------------+                             +------------------+
| Server A         |                             | Server B         |
+------------------+      +---------------+      +------------------+
| Thread-1         |      | Cluster Mutex |      | Thread-1         |
| Thread-2         |----> +---------------+ <----| Thread-2         |
| Thread-3         |      | A Thread-2    |      | Thread-3         |
+------------------+      +---------------+      +------------------+
                                  |
                                  V
                          +---------------+
                          | Cluster Mutex | 
                          +---------------+
                          | A Thread-2    |
                          +---------------+
			
			

上圖中有兩台伺服器上運行任務,其中Server A 的 Thread-2 做了加鎖操作,其他程序必須等待它釋放鎖才能運行。

你會問如果 Server A 宕機怎麼辦,是否會一直處于被鎖狀態?我的答案是每個鎖都有一個超時閥值,一旦超時便自動解鎖。

另外我們還要考慮“域”的問題,你也可以叫它命令空間,主要是防止鎖出現同名被覆蓋。

6.2. 隊列

排隊運行

			
			
			
+------------------+                             +------------------+
| Server A         |                             | Server B         |
+------------------+      +---------------+      +------------------+
| Thread-1         |      | Task Queue A  |      | Thread-1         |
| Thread-2         |----> +---------------+ <----| Thread-2         |
| Thread-3         |      | A Thread-2    |      | Thread-3         |
+------------------+      | B Thread-1    |      +------------------+
                          | B Thread-3    |
                          | A Thread-3    |
                          +---------------+
                                  |
                                  | <sync>
                                  V
                          +---------------+
                          | Task Queue B  |
                          +---------------+
                          | A Thread-2    |
                          | B Thread-1    |
                          | B Thread-3    |
                          | A Thread-3    |
                          +---------------+
			
			

從上圖中我可以看到Task Queue中排隊情況,運行是自上而下的。

注意Task Queue 需要兩個節點,它們是主從結構,A 節點實時向 B 節點同步sh狀態。如果 A 節點出現故障, B 節點立即取代 A 節點。

6.3. 其他

計劃任務可以分散式運行了,但並不能保證萬無一失,配套其他伺服器也要做調整。例如資料庫,緩存等等。