如何設計穩定性橫跨全球的 Cron 服務

Cron 是 UNIX 中一個常見的工具,用來按期執行一些用戶指定的任意任務。咱們先來分析下 Cron 的基本原則和它最多見的實現,而後咱們來回顧下像 Cron 這樣的服務應該如何運行在一個大型的、分佈式的環境中,這樣即便單機故障也不會對系統可用性形成影響。 咱們將會介紹了一個創建在少許機器上的 Cron 系統,而後結合數據中心的調度服務,從而能夠在整個數據中心中運行 Cron 任務。html

如何設計穩定性橫跨全球的 Cron 服務如何設計穩定性橫跨全球的 Cron 服務

在咱們在描述如何運行一個靠譜的分佈式 Cron 服務以前,讓咱們先來從一個 SRE 的角度來回顧下 Cron。linux

Cron 是一個通用的工具,不管是管理員仍是普通用戶均可以用它來在系統上運行指定的命令,以及指定什麼時候運行命令,這些指定運行的命令能夠是按期垃圾回收,也能夠是按期數據分析。 最多見的時間指定格式被稱爲 crontab,它不只支持簡單的時間週期(如,天天中午一次,每一個小時一次),也支持較複雜的時間週期,如每一個周6、每月的第 30 天等等。算法

Cron 一般只包含一個組件,被稱爲 crond,它是一個後臺守護程序,加載全部須要運行的 cron 定時任務,根據它們接下來的運行時間來進行排序,而後這個守護進程將會等待直到第一個任務開始執行。在這個時刻,crond 將會加載執行這個任務,以後將它放入隊列等待下一次運行。網頁爬蟲

可靠性安全

從可靠性的角度來看一個服務,須要有不少注意的地方。服務器

第一,好比 crond,它的故障域本質上來講只是一臺機器,若是這個機器沒有運行,不管是 cron 調度仍是加載的任務都是不可運行的。所以,考慮一個很是簡單的分佈式的例子 ——— 咱們使用兩臺機器,而後 cron 調度在其中一臺機器上運行任務(好比經過 ssh)。而後產生了一個故障域了:調度任務和目標服務器均可能失敗。網絡

另一個須要注意的地方是,即便是 crond 重啓(包括服務器重啓),上面部署的 crontab 配置也不該該丟失。crond 執行一個任務而後就‘忘記’了這個任務的狀態,它並不會嘗試去跟蹤這個任務的執行狀態,包括是否該執行是否已經執行。架構

anacron是一個例外,它是crontab的一個補充,它嘗試運行哪些由於服務器宕機而應該執行卻沒執行的任務。這僅限於每日或者更小執行頻率的任務,但對於在工做站和筆記本電腦上運行維護工做很是有用。經過維護一個包括最後執行時間的配置文件,使得運行這些特殊的任務更加方便。ssh

Cron 的任務和冪等性分佈式

Cron 的任務用來執行按期任務,可是除此以外,卻很難在進一步知道它們的功能。讓咱們先把要討論的主題拋開一邊,如今先來就 Cron 任務自己來作下探討,由於只有理解了 Cron 任務的各類各樣的需求,才能知道它是如何影響咱們須要的可靠性要求,而這一方面的探討也將貫穿接下來的文章。

有一些 Cron 任務是冪等性的,這樣在某些系統故障的狀況下,能夠很安全的執行它們屢次,好比,垃圾回收。然而有些 Cron 任務卻不該該被執行屢次,好比某個發送郵件的任務。

還有更復雜的狀況,有些 Cron 任務容許由於某些狀況而「忘了」運行,而某些 Cron 任務卻不能容忍這些,好比,垃圾回收的 Cron 任務每 5 分鐘調度一次,即便某一次沒有執行也不會有太大的問題,然而,一個月一次的支付薪水的任務,卻絕對不容許有失誤。

Cron 任務的各類不一樣的類型使得不可能有一個通用的解決方案,使得它能夠應對各類各樣的失敗。因此,在本文中上面說的那些狀況,咱們更傾向於錯過某一次的運行,而不是運行它們兩次或者更多。Cron 任務的全部者應該(也必須)監控着它們的任務,好比返回任務的調用結果,或者單獨發送運行的日誌給所屬者等等,這樣,即便跳過了任務的某次執行,也可以很方便的採起對應的補救動做。當任務失敗時,咱們更傾向於將任務狀態置爲 「fail closed」 來避免產生系統性的不良狀態。

大規模部署 Cron

當從單機到集羣部署 Cron 時,須要從新思考如何使 Cron 在這種環境下良好的運行。在對 Google 的 Cron 進行解說以前,讓咱們先來討論下單機以及多機之間的區別,以及針對這變化如何設計。

擴展基礎架構

常規的 Cron 僅限於單個機器,而大規模部署的 Cron 解決方案不能僅僅綁定到一個單獨的機器。假設咱們擁有一個 1000 臺服務器的數據中心,若是即便是 1/1000 的概率形成服務器不可用都能摧毀咱們整個 Cron 服務,這明顯不是咱們所但願的。

因此,爲了解決這個問題,咱們必須將服務與機器解耦。這樣若是想運行一個服務,那麼僅僅須要指定它運行在哪一個數據中心便可,剩下的事情就依賴於數據中心的調度系統(固然前提是調度系統也應該是可靠的),調度系統會負責在哪臺或者哪些機器上運行服務,以及可以良好的處理機器掛掉這種狀況。 那麼,若是咱們要在數據中心中運行一個任務,也僅僅是發送一條或多條 RPC 給數據中心的調度系統。

然而,這一過程顯然並非瞬時完成的。好比,要檢查哪些機器掛掉了(機器健康檢查程序掛了怎麼辦),以及在另一些機器上從新運行任務(服務依賴從新部署從新調用任務)都是須要花費必定時間的。

將程序轉移到另一個機器上可能意味着損失一些存儲在老機器上的一些狀態信息(除非也採用動態遷移),從新調度運行的時間間隔也可能超過最小定義的一分鐘,因此,咱們也必須考慮到上述這兩種狀況。一個很直接的作法,將狀態文件放入分佈式文件系統,如 GFS,在任務運行的整個過程當中以及從新部署運行任務時,都是用它來記錄使用相關狀態。 然而,這個解決方案卻不能知足咱們預期的時效性這個需求,好比,你要運行一個每五分鐘跑一次的 Cron 任務,從新部署運行消耗的 1-2 分鐘對這個任務來講也是至關大的延遲了。

及時性的需求可能會促使各類熱備份技術的使用,這樣就可以快速記錄狀態以及從原有狀態快速恢復。

需求擴展

將服務部署在數據中心和單服務器的另外一個實質性的區別是,如何規劃任務所須要的計算資源,如 CPU 或內存等。

單機服務一般是經過進程來進行資源隔離,雖然如今 Docker 變得愈來愈廣泛,可是使用它來隔離一切目前也不太是很通用的作法,包括限制crond以及它所要運行的任務。

大規模部署在數據中心常用容器來進行資源隔離。隔離是必要的,由於咱們確定但願數據中心中運行的某個程序不會對其它程序產生不良影響。爲了隔離的有效性,在運行前確定得先預知運行的時候須要哪些資源——包括 Cron 系統自己和要運行的任務。這又會產生一個問題,即若是數據中心暫時沒有足夠的資源,那麼這個任務可能會延遲運行。這就要求咱們不只要監控 Cron 任務加載的狀況,也要監控 Cron 任務的所有狀態,包括開始加載到終止運行。

如今,咱們但願的 Cron 系統已經從單機運行的狀況下解耦,如以前描述的那樣,咱們可能會遇到部分任務運行或加載失敗。這時候幸好任務配置的通用性,在數據中心中運行一個新的 Cron 任務就能夠簡單的經過 RPC 調用的方式來進行,不過不幸的是,這樣咱們只能知道 RPC 調用是否成功,卻沒法具體知道任務失敗的具體地方,好比,任務在運行的過程當中失敗,那麼恢復程序還必須將這些中間過程處理好。

在故障方面,數據中心遠比一臺單一的服務器複雜。Cron 從原來僅僅的一個單機二進制程序,到整個數據中心運行,其期間增長了不少明顯或不明顯的依賴關係。做爲像 Cron 這樣的一個基礎服務,咱們但願獲得保證的是,即便在數據中心中運行發生了一些 「Fail」(如,部分機器停電或存儲掛掉),服務依然可以保證功能性正常運行。爲了提升可靠性,咱們應該將數據中心的調度系統部署在不一樣的物理位置,這樣,即便一個或一部分電源掛掉,也能保證至少 Cron 服務不會所有不可用。

Google 的 Cron 是如何建設的

如今讓咱們來解決這些問題,這樣才能在一個大規模的分佈式集羣中部署可靠的 Cron 服務,而後在着重介紹下 Google 在分佈式 Cron 方面的一些經驗。

跟蹤 Cron 任務的狀態

向上面描述過的那樣,咱們應該跟蹤 Cron 任務的實時狀態,這樣,即便失敗了,咱們也更加容易恢復它。並且,這種狀態的一致性是相當重要的:相比錯誤的多運行 10 遍相同的 Cron 任務,咱們更能接受的是不去運行它。回想下,不少 Cron 任務,它並非冪等性的,好比發送通知郵件。

咱們有兩個選項,將 Cron 任務的數據統統存儲在一個靠譜的分佈式存儲中,或者僅僅保存任務的狀態。當咱們設計分佈式 Cron 服務時,咱們採起的是第二種,有以下幾個緣由:

分佈式存儲,如 GFS 或 HDFS,每每用來存儲大文件(如 網頁爬蟲程序的輸出等),而後咱們須要存儲的 Cron狀態卻很是很是小。將如此小的文件存儲在這種大型的分佈式文件系統上是很是昂貴的,並且考慮到分佈式文件系統的延遲,也不是很適合。

像 Cron 服務這種基礎服務,它須要的依賴應該是越少越好。這樣,即便部分數據中心掛掉,Cron 服務至少也能保證其功能性並持續一段時間。這並不意味着存儲應該直接是 Cron 程序的一部分(這本質上是一個實現細節)。Cron 應該是一個可以獨立運做的下游系統,以便供用戶操做使用。

使用 Paxos

咱們部署多個實例的 Cron 服務,而後經過 Paxos 算法來同步這些實例間的狀態。

Paxos 算法和它其它的替代算法(如 Zab,Raft 等)在分佈式系統中是十分常見的。具體描述 Paxos 不在本文範圍內,它的基本做用就是使多個不可靠節點間的狀態保持一致,只要大部分 Paxos 組成員可用,那麼整個分佈式系統,就能做爲一個總體處理狀態的變化。

分佈式 Cron 使用一個獨立的主任務,見下圖,只有它才能更改共享的狀態,也只有它才能加載 Cron 任務。咱們這裏使用了 Paxos 的一個變體—— Fast Paxos,這裏 Fast Paxos 的主節點也是 Cron 服務的主節點。

如何設計穩定性橫跨全球的 Cron 服務如何設計穩定性橫跨全球的 Cron 服務

若是主節點掛掉,Paxos 的健康檢查機制會在秒級內快速發現,並選舉出一個新的主節點。一旦選舉出新的主節點,Cron 服務也就隨着選舉出了一個新的 Cron 主節點,這個新的 Cron 主節點將會接手前一個主節點留下的全部的未完成的工做。在這裏 Cron 的主節點和 Paxos 的主節點是同樣的,可是 Cron 的主節點須要處理一下額外的工做而已。快速選舉新的主節點的機制可讓咱們大體能夠容忍一分鐘的故障時間。

咱們使用 Paxos 算法保持的最重要的一個狀態是,哪些 Cron 任務在運行。對於每個運行的 Cron 任務,咱們會將其加載運行的開始以及結束同步給必定數量的節點。

主節點和從節點角色

如上面描述的那樣,咱們在 Cron 服務中使用 Paxos 並部署,其擁有兩個不一樣的角色,主節點以及從節點。讓咱們來就每一個角色來作具體的描述。

主節點

主節點用來加載 Cron 任務,它有個內部的調度系統,相似於單機的crond,維護一個任務加載列表,在指定的時間加載任務。

當任務加載的時刻到來,主節點將會 「宣告」 它將會加載這個指定的任務,而且計算這個任務下次的加載時間,就像 crond 的作法同樣。固然,就像 crond 那樣,一個任務加載後,下一次的加載時間可能人爲的改變,這個變化也要同步給從節點。簡單的標識 Cron 任務還不夠,咱們還應該將這個任務與開始執行時間相關聯綁定,以免 Cron 任務在加載時發生歧義(特別是那些高頻的任務,如一分鐘一次的那些)。這個「通告」經過 Paxos 來進行。下圖展現了這一過程。

如何設計穩定性橫跨全球的 Cron 服務如何設計穩定性橫跨全球的 Cron 服務

保持 Paxos 通信同步很是重要,只有 Paxos 法定數收到了加載通知,這個指定的任務才能被加載執行。Cron 服務須要知道每一個任務是否已經啓動,這樣即便主節點掛掉,也能決定接下來的動做。若是不進行同步,意味着整個 Cron 任務運行在主節點,而從節點沒法感知到這一切。若是發生了故障,頗有可能這個任務就被再次執行,由於沒有節點知道這個任務已經被執行過了。

Cron 任務的完成狀態經過 Paxos 通知給其它節點,從而保持同步,這裏要注意一點,這裏的「完成」 狀態並非表示任務是成功或者失敗。咱們跟蹤 Cron 任務在指定調用時間被執行的狀況,咱們一樣須要處理一點狀況是,若是 Cron 服務在加載任務進行執行的過程當中失敗後怎麼辦,這點咱們在接下來會進行討論。

主節點另外一個重要的特性是,無論是出於什麼緣由主節點失去了其主控權,它都必須立馬中止同數據中心調度系統的交互。主控權的保持對於訪問數據中心應該是互斥了。若是不這樣,新舊兩個主節點可能會對數據中心的調度系統發起互相矛盾的操做請求。

從節點

從節點實時監控從主節點傳來的狀態信息,以便在須要的時刻作出積極響應。全部主節點的狀態變更信息,都經過 Paxos 傳到各個從節點。和主節點相似的是,從節點一樣維持一個列表,保存着全部的 Cron 任務。這個列表必須在全部的節點保持一致(固然仍是經過 Paxos)。

當接到加載任務的通知後,從節點會將此任務的下次加載時間放入本地任務列表中。這個重要的狀態信息變化(這是同步完成的)保證了系統內部 Cron 做業的時間表是一致的。咱們跟蹤全部有效的加載任務,也就是說,咱們跟蹤任務什麼時候啓動,而不是結束。

若是一個主節點掛掉或者由於某些緣由失聯(好比,網絡異常等),一個從節點有可能被選舉成爲一個新的主節點。這個選舉的過程必須在一分鐘內運行,以免 Cron 任務丟失的狀況。一旦被選舉爲主節點,全部運行的加載任務(或部分失敗的),必須被從新驗證其有效性。這個多是一個複雜的過程,在 Cron 服務系統和數據中心的調度系統上都須要執行這樣的驗證操做,這個過程有必要詳細說明。

故障恢復

如上所述,主節點和數據中心的調度系統之間會經過 RPC 來加載一個邏輯 Cron 任務,可是,這一系列的 RPC 調用過程是有可能失敗的,因此,咱們必須考慮到這種狀況,而且處理好。

回想下,每一個加載的 Cron 任務會有兩個同步點:開始加載以及執行完成。這可以讓咱們區分開不一樣的加載任務。即便任務加載只須要調用一次 RPC,可是咱們怎麼知道 RPC 調用實際真實成功呢?咱們知道任務什麼時候開始,可是若是主節點掛了咱們就不會知道它什麼時候結束。

爲了解決這個問題,全部在外部系統進行的操做,要麼其操做是冪等性的(也就是說,咱們能夠放心的執行它們屢次),要麼咱們必須實時監控它們的狀態,以便能清楚的知道什麼時候完成。

這些條件明顯增長了限制,實現起來也有必定的難度,可是在分佈式環境中這些限制倒是保證 Cron 服務準確運行的根本,可以良好的處理可能出現的 「fail」。若是不能妥善處理這些,將會致使 Cron 任務的加載丟失,或者加載屢次重複的 Cron 任務。

大多數基礎服務在數據中心(好比 Mesos)加載邏輯任務時都會爲這些任務命名,這樣方便了查看任務的狀態,終止任務,或者執行其它的維護操做。解決冪等性的一個合理的解決方案是將執行時間放在名字中 ——這樣不會在數據中心的調度系統裏形成任務異變操做 —— 而後在將它們分發給 Cron 服務全部的節點。若是 Cron 服務的主節點掛掉,那麼新的主節點只須要簡單的經過預處理任務名字來查看其對應的狀態,而後加載遺漏的任務便可。

注意下,咱們在節點間保持內部狀態一致的時候,實時監控調度加載任務的時間。一樣,咱們也須要消除同數據中心調度交互時可能發生的不一致狀況,因此這裏咱們以調度的加載時間爲準。好比,有一個短暫可是頻繁執行的 Cron 任務,它已經被執行了,可是在準備把狀況通告給其它節點時,主節點掛了,而且故障時間持續的特別長——長到這個 Cron 任務都已經成功執行完了。而後新的主節點要查看這個任務的狀態,發現它已經被執行完成了,而後嘗試加載它。若是包含了這個時間,那麼主節點就會知道,這個任務已經被執行過了,就不會重複執行第二次。

在實際實施的過程當中,狀態監督是一個更加複雜的工做,它的實現過程和細節依賴與其它一些底層的基礎服務,然而,上面並無包括相關係統的實現描述。根據你當前可用的基礎設施,你可能須要在冒險重複執行任務和跳過執行任務 之間作出折中選擇。

狀態保存

使用 Paxos 來同步只是處理狀態中遇到的其中一個問題。Paxos 本質上只是經過一個日誌來持續記錄狀態改變,而且隨着狀態的改變而進行將日誌同步。這會產生兩個影響:第一,這個日誌須要被壓縮,防止其無限增加;第二,這個日誌自己須要保存在一個地方。

爲了不其無限增加,咱們僅僅取狀態當前的快照,這樣,咱們可以快速的重建狀態,而不用在根據以前全部狀態日誌來進行重演。好比,在日誌中咱們記錄一條狀態 「計數器加 1」,而後通過了 1000 次迭代後,咱們就記錄了 1000 條狀態日誌,可是咱們也能夠簡單的記錄一條記錄 「將計數器設置爲 1000」來作替代。

若是日誌丟失,咱們也僅僅丟失當前狀態的一個快照而已。快照實際上是最臨界的狀態 —— 若是丟失了快照,咱們基本上就得從頭開始了,由於咱們丟失了上一次快照與丟失快照期間全部的內部狀態。從另外一方面說,丟失日誌,也意味着,將 Cron 服務拉回到有記錄的上一次快照所標示的地方。

咱們有兩個主要選擇來保存數據: 存儲在外部的一個可用的分佈式存儲服務中,或者,在內部一個系統來存儲 Cron 服務的狀態。當咱們設計系統時,這兩點都須要考慮。

咱們將 Paxos 日誌存儲在 Cron 服務節點所在服務器本地的磁盤中。默認的三個節點意味着,咱們有三份日誌的副本。咱們一樣也將快照存儲在服務器自己,然而,由於其自己是很是重要的,咱們也將它在分佈式存儲服務中作了備份,這樣,即便小几率的三個節點機器都故障了,也可以服務恢復。

咱們並無將日誌自己存儲在分佈式存儲中,由於咱們以爲,丟失日誌也僅僅表明最近的一些狀態丟失,這個咱們實際上是能夠接受的。而將其存儲在分佈式存儲中會帶來必定的性能損失,由於它自己在不斷的小字節寫入不適用與分佈式存儲的使用場景。同時三臺服務器全故障的機率過小,可是一旦這種狀況發生了,咱們也能自動的從快照中恢復,也僅僅損失從上次快照到故障點的這部分而已。固然,就像設計 Cron 服務自己同樣,如何權衡,也要根據本身的基礎設施狀況來決定。

將日誌和快照存本地,以及快照在分佈式存儲備份,這樣,即便一個新的節點啓動,也可以經過網絡從其它已經運行的節點處獲取這些信息。這意味着,啓動節點與服務器自己並無任何關係,從新安排一個新的服務器(好比重啓)來擔當某個節點的角色 其本質上也是影響服務的可靠性的問題之一。

運行一個大型的 Cron

還有一些其它的、小型的,可是一樣有趣的一些狀況或能影響部署一個大型的 Cron 服務。傳統的 Cron 規模很小:最多包含數十個 Cron 任務。然而,若是在一個數據中心的超過千臺服務器來運行 Cron 服務,那麼你就會遇到各類各樣的問題。

一個比較大的問題是,分佈式系統經常要面臨的一個經典問題:驚羣問題,在 Cron 服務的使用中會形成大量的尖峯狀況。當要配置一個天天執行的 Cron 任務,大多數人第一時間想到的是在半夜執行,而後它們就這麼配置了。若是一個 Cron 任務在一臺機器上執行,那沒有問題,可是若是你的任務是執行一個涉及數千 worker 的 mapreduce 任務,或者,有 30 個不一樣的團隊在數據中心中要配置這樣的一個天天運行的任務,那麼咱們就必需要擴展下crontab的格式了。

傳統的crontab,用戶經過定義「分鐘」,「小時」,「每個月(或每週)第幾天」,「月數」來指定 cron 任務運行的時間,或者經過星號(*)來表明每一個對應的值。如,天天凌晨運行,它的 crontab 格式爲0 0 * * *,表明天天的 0 點 0 分運行。咱們在此基礎之上還推出了問號(?)這個符號,它標示,在這個對應的時間軸上,任什麼時候間均可以,Cron 服務就會自由選擇合適的值,在指定的時間段內隨機選擇對應的值,這樣使任務運行更均衡。如 0 ? * * *,表示天天 0-23 點鐘,隨機一個小時的 0 分來運行這個任務。

儘管加了這項變化,由 Cron 任務所形成的 load 值仍然有明顯的尖峯,下圖表示了 Google 中 cron 任務加載的數量。尖峯值每每表示那些須要固定頻率在指定時間運行的任務。

如何設計穩定性橫跨全球的 Cron 服務如何設計穩定性橫跨全球的 Cron 服務

總結

Cron 服務做爲 UNIX 的基礎服務已經有接近 10 年。當前整個行業都朝着大型分佈式系統演化,那時,表示硬件的最小單位將會是數據中心,那麼大量的技術棧須要對應改變,Cron 也不會是例外。仔細審視下 Cron 服務所須要的服務特性,以及 Cron 任務的需求,都會推進咱們來進行新的設計。

基於 Google 的解決方案,咱們已經討論了 Cron 服務在一個分佈式系統中對應的約束和可能的設計。這個解決方案須要在分佈式環境中的強一致性保證,它的實現核心是經過 Paxos 這樣一種通用的算法,在一個不可靠的環境中達成最終一致。使用 Paxos,正確對大規模環境下 Cron 任務失敗狀況的分析,以及分佈式的環境的使用,共同造就了在 Google 內部使用的健壯的 Cron 服務。

原文來自:https://linux.cn:443/article-7860-1.html

本文地址:http://www.linuxprobe.com/stable-cron-service.html

相關文章
相關標籤/搜索