數(shù)據(jù)工程中的任務(wù)調(diào)度實踐

數(shù)據(jù)工程領(lǐng)域當前遇到的挑戰(zhàn)

現(xiàn)代系統(tǒng)正變得越來越復(fù)雜,從單線程到多線程,從單體到微服務(wù),從單節(jié)點到分布式,從本地到云端... … 復(fù)雜度使得程序產(chǎn)生預(yù)期的結(jié)果需要越來越多的必要條件,而每種條件都有其自身的成功概率,即使每種條件的成功概率都很高,根據(jù)墨菲定律,或早或晚一定會遇到不可預(yù)知的結(jié)果。

在數(shù)據(jù)工程領(lǐng)域,這個問題尤其突出,目前大數(shù)據(jù)領(lǐng)域幾乎都是分布式運行的任務(wù)、高可用的消息隊列、多備份的存儲、主從節(jié)點等等。在這樣的情況下,如何構(gòu)建穩(wěn)定、健壯的任務(wù)或服務(wù)成為了一個挑戰(zhàn)。而數(shù)據(jù)工程實踐中每日調(diào)度的任務(wù)成為了與各個復(fù)雜數(shù)據(jù)組件交互的最主要形式,所以本文我們將以數(shù)據(jù)工程的任務(wù)調(diào)度為例,利用混沌工程幫助我們找出系統(tǒng)薄弱點或脆弱性,引出我們沉淀的基于日志驅(qū)動的任務(wù)調(diào)度實踐。

什么是混沌工程?

混沌工程是在系統(tǒng)上進行實驗的學(xué)科, 目的是建立對系統(tǒng)抵御生產(chǎn)環(huán)境中失控條件的能力以及信心。(摘自:混沌工程原則)
混沌工程通過以下四個步驟來找出系統(tǒng)中隱藏的“混沌”:

  1. 用系統(tǒng)在正常行為下的一些可測量的輸出來定義“穩(wěn)定狀態(tài)”。
  2. 假設(shè)這個系統(tǒng)在控制組和實驗組都會繼續(xù)保持穩(wěn)定狀態(tài)。
  3. 在實驗組中引入反映真實世界事件的變量,如服務(wù)器崩潰、硬盤故障、網(wǎng)絡(luò)連接斷開等。
  4. 通過控制組和實驗組之間的狀態(tài)差異來反駁穩(wěn)定狀態(tài)的假說。
    混沌工程實驗是通過向現(xiàn)有系統(tǒng)注入故障,從而發(fā)現(xiàn)系統(tǒng)的薄弱點,從而可以有針對性地提高整個系統(tǒng)的健壯性。

環(huán)境都是高可用的,應(yīng)該很健壯啊

有些人可能會有疑問,“我們環(huán)境都是高可用的,應(yīng)該很健壯啊”。這個問題還是要分開來看,健壯的系統(tǒng)能夠忠實執(zhí)行程序邏輯并得到最終結(jié)果。但是如果程序邏輯就是有問題的呢?
或者,即使程序邏輯沒有問題,由于程序本身的脆弱性,在系統(tǒng)從錯誤中恢復(fù)時可能沒有處理一些必要的邏輯,導(dǎo)致最終結(jié)果出現(xiàn)問題。因此,系統(tǒng)級別的高可用性或健壯性與程序級別的健壯性是兩回事,必須分開看待。二者缺一不可。
在工程實踐中,我們往往會過于關(guān)注基礎(chǔ)設(shè)置是否具備高可用性,而忽略了程序邏輯的健壯性和錯誤恢復(fù)處理。

僅僅是冪等就足夠了嗎?

有些人認為他們的程序是冪等的,因此即使出錯了可以重跑,所以不需要考慮其他。這里先講一下冪等的定義:總能夠用同樣的參數(shù)重復(fù)執(zhí)行,并得到相同的結(jié)果。

那我們可以按照混沌工程的四個步驟來模擬一個場景出來:

  1. 用系統(tǒng)在正常行為下的一些可測量的輸出來定義“穩(wěn)定狀態(tài)”,這里嘗試定義“穩(wěn)定狀態(tài)”為:

    • ETL計算結(jié)果穩(wěn)定且正確(冪等)
    • 資源隊列占用合理,提交的任務(wù)不需要等待太久就可以運行
    • 過去的數(shù)據(jù)不會被重復(fù)計算
    • 同一時刻不發(fā)生重復(fù)計算
  2. 假設(shè)這個系統(tǒng)在控制組和實驗組都會繼續(xù)保持穩(wěn)定狀態(tài)。

  3. 在實驗組中引入反映真實世界事件的變量:手動調(diào)度任務(wù)時頁面卡住了,習(xí)慣性多點了幾次,刷新頁面后發(fā)現(xiàn)調(diào)度起來了十幾個任務(wù)。

  4. 通過控制組和實驗組之間的狀態(tài)差異來反駁穩(wěn)定狀態(tài)的假說:

    • 資源隊列被打滿,新調(diào)度的任務(wù)都得排隊(不符合穩(wěn)定狀態(tài)條件2)
    • 重跑并覆蓋過去已經(jīng)運行過的數(shù)據(jù)(計算資源浪費,不符合穩(wěn)定狀態(tài)條件3)
    • 計算同一天的數(shù)據(jù)幾十次,而最終只留了最后那份(計算資源浪費,不符合穩(wěn)定狀態(tài)條件4)

通過上面的模擬實驗我們可以知道,雖然ETL是滿足冪等性(即穩(wěn)定狀態(tài)條件1)的,但是由于沒有滿足其他幾個穩(wěn)定狀態(tài),所以我們可以說它仍然是存在脆弱性的。

反脆弱的任務(wù)調(diào)度應(yīng)該是什么樣的?

對于一個每日運行的任務(wù)來說,理想情況下它應(yīng)該每天都能成功完成,但實際情況下很可能會遇到失敗的情況。不同的調(diào)度引擎往往對失敗的情況有不同的處理方式。例如,有的調(diào)度引擎會忽略過去失敗的任務(wù)并繼續(xù)開啟下一個調(diào)度周期。比如跳過了2022年2月2日的任務(wù),繼續(xù)運行2022年2月3日的任務(wù)。但當發(fā)現(xiàn)任務(wù)出現(xiàn)問題后,就需要手動補數(shù)據(jù)并重新運行2022年2月2日的任務(wù),以便將數(shù)據(jù)完整地補齊。如果在這期間有報表使用了2022年2月2日的數(shù)據(jù),那么這些報表的數(shù)據(jù)肯定是不準確的。

然而,完全依賴任務(wù)調(diào)度工具有一些缺點。首先,它完全依賴于調(diào)度工具的任務(wù)歷史記錄。如果沒有配置失敗通知機制,那么需要一個個去查看哪個任務(wù)掛掉了。其次,如果允許失敗任務(wù)之后的任務(wù)繼續(xù)運行,可能會導(dǎo)致對順序有要求的場景出現(xiàn)問題。比如,假設(shè)2月3日的數(shù)據(jù)在MySQL中進行了upsert操作,而2月2日的數(shù)據(jù)在重新運行失敗任務(wù)后又執(zhí)行了一次,那么就會導(dǎo)致用舊的數(shù)據(jù)覆蓋新的數(shù)據(jù)的問題等等。

雖然一些調(diào)度框架在適當?shù)呐渲脮r可以解決上述問題,但無法保證所有調(diào)度框架都能解決這些問題,或者你的項目對調(diào)度框架沒有自由選擇的余地。因此,在實踐中,我們需要在ETL任務(wù)和實際調(diào)度框架(如Airflow等)中引入“日志驅(qū)動”的這一層抽象隔離,以便為任務(wù)運行添加一層統(tǒng)一的邏輯處理。這樣做可以使得ETL任務(wù)在不同的調(diào)度框架下表現(xiàn)一致,更加符合大家的預(yù)期,也不局限于某一個特定的調(diào)度框架。這樣我們就得以擺脫特定框架的局限,而不是讓每個人都要熟悉常見、不常見的調(diào)度框架。

數(shù)據(jù)工程的任務(wù)調(diào)度實踐

前述問題的一個難點在于在處理重復(fù)提交的任務(wù)時如何隔離不同調(diào)度系統(tǒng)的具體實現(xiàn)。

我認為數(shù)據(jù)工程的任務(wù)調(diào)度應(yīng)該以“日志驅(qū)動”作為解決方案。而日志驅(qū)動的重要部分“日志解耦”正是處理這個問題的利器:我們需要判斷是不是有任務(wù)已經(jīng)提交過了,對不對?同時要考慮到隔離不同調(diào)度系統(tǒng),所以方案自然是需要一個單獨的地方保存這些調(diào)度日志,以便在調(diào)度任務(wù)時檢查是否需要調(diào)度(是否有相同任務(wù)在運行或者這個任務(wù)是否已經(jīng)運行過等),從而解耦不同具體組件的日志。

注意這里的“日志”并不是任務(wù)運行的日志,而是調(diào)度任務(wù)的日志,記錄的是那個任務(wù)在哪個時間調(diào)度,狀態(tài)是什么等等。
應(yīng)用“日志驅(qū)動”所帶來的的好處,遠不止于此,因為留存了任務(wù)調(diào)度日志的記錄,在這個基礎(chǔ)上很多事情變得可能。其實“日志驅(qū)動”和“斷點續(xù)傳”這個概念很像,只不過沒有應(yīng)用在下載文件上,而是應(yīng)用到了任務(wù)調(diào)度。日志驅(qū)動有幾個核心要點:

  • 自行記錄任務(wù)運行歷史,而不依賴與調(diào)度框架的功能。這樣就做到了與不同調(diào)度框架解綁;
  • 調(diào)度是有序的,上個周期任務(wù)失敗了,不會跳過它運行下個周期的任務(wù),每次調(diào)度還是會先執(zhí)行之前失敗的任務(wù),直到它成功;

日志驅(qū)動也帶來了幾點好處:

  • 可以解決重復(fù)調(diào)度的問題,當任務(wù)運行后發(fā)現(xiàn)有相同任務(wù)在運行或者已經(jīng)運行過了,當前任務(wù)可以直接退出或者kill掉之前的任務(wù)
  • 補數(shù)據(jù)操作更加容易實現(xiàn)且靈活而不容易出錯
  • 更加靈活的任務(wù)依賴配置(任務(wù)上下游不一定是同頻率或者必須在一個dag里面)
  • 更加靈活的調(diào)度起始設(shè)置,例如對于kafka offset和自增主鍵的支持
  • 更加統(tǒng)一且容易的運維操作(不同的項目、不同的調(diào)度引擎,都可以基于日志驅(qū)動(的表)來進行運維操作)
  • 可以記錄更加詳細的任務(wù)狀態(tài),比如讀到多少條數(shù)據(jù),寫了多少條數(shù)據(jù)等等(特指結(jié)構(gòu)化記錄,而不是普通的執(zhí)行日志),方便做統(tǒng)計查看
  • 可以自行選擇事務(wù)級別,或者說可以讓用戶選擇是否要“臟讀”數(shù)據(jù)
  • 還有一些其他好處,篇幅有限就不在此展開了


回到現(xiàn)實場景,任務(wù)失敗的情況大致可以分為兩種:

  • 重試就可以成功(網(wǎng)絡(luò)閃崩,排隊超時等)
  • 代碼、環(huán)境有問題需要人工介入的

對于重試就可以成功的情況,往往在下一次調(diào)度就可以自動補上之前失敗的任務(wù)的數(shù)據(jù);如果不想等到下一個周期,可以人工馬上調(diào)度一次。

對于無法重試成功的情況,往往每次調(diào)度都會掛掉,但是只會嘗試最開始的那天的任務(wù),因為前置的任務(wù)沒有成功,只是在每天重試 2022-02-02 的任務(wù); 無法重試成功的任務(wù),仍然需要人工介入,修復(fù)(環(huán)境、邏輯、上游數(shù)據(jù)等問題)之后,自動(按順序)補上之前掛掉的任務(wù)的數(shù)據(jù);

總結(jié)

通過混沌工程的虛擬實驗我們知道常規(guī)的任務(wù)調(diào)度并不足夠健壯,而日志驅(qū)動的加入可以讓ETL任務(wù)更加穩(wěn)定。同時日志驅(qū)動也帶來了諸多好處,不僅僅解決了混沌工程的穩(wěn)定性問題,也更加豐富了數(shù)據(jù)工程實踐。


文/Thoughtworks 張志豪
原文鏈接:數(shù)據(jù)工程中的任務(wù)調(diào)度實踐-Thoughtworks洞見

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容