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

這篇文章主要來描述下Google是如何實現一套可靠的 分佈式Cron服務,服務於內部那些須要絕大多數計算做業定時調度的團隊。 在這個系統的實踐過程當中,咱們收穫了不少,包括如何設計、如何實現 使得他看上去像一個靠譜的基礎服務。 在這裏,咱們來討論下分佈式Cron可能會遇到哪些問題,以及 如何解決他。算法

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

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

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

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

可靠性Reliability

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

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

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

Cron的jobs和冪等性

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

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

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

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

大規模部署Cron

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

擴展基礎架構

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

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

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

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

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

需求擴展

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

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

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

如今,咱們但願的Cron系統已經從單機運行的狀況下解耦,如以前描述的那樣,咱們可能會遇到部分任務運行或加載失敗。這時候辛虧Job配置的通用性,在數據中心中運行一個新的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使用一個獨立的master job,見圖Figure 1,只有它才能更改共享的狀態,也只有它才能加載Cron任務。咱們這裏使用了Paxos的一個變體——Fast Paxos,這裏Fast Paxos的主節點也是Cron服務的主節點。
圖1

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

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

Master和Slave角色

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

The Master

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

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

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

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

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

the slave

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

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

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

故障恢復

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

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

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

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

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

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

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

狀態保存

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

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

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

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

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

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

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

運行一個大型的Cron

還有一些其餘的、小型的,可是一樣有趣的一些case或能影響部署一個大型的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值仍然有明顯的尖峯,圖3表示了google中cron任務加載的數量。尖峯值每每表示那些須要固定頻率在指定時間運行的任務。
圖片描述

總結

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

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


原文:https://queue.acm.org/detail....

相關文章
相關標籤/搜索