1. 程式人生 > 實用技巧 >程式設計師世界中的真金白銀——Erlang的容錯架構

程式設計師世界中的真金白銀——Erlang的容錯架構

在現實世界中容錯就是真金白銀。程式設計師並不完美,需求往往也不完善。正如航空工程師處理有缺陷的鋼材和鋁材一樣,為了有效處理有缺陷的程式碼和資料,我們需要能夠容錯的系統,以防系統在遭遇突發狀況時土崩瓦解。

和許多其他程式語言一樣,Erlang也具備異常處理機制來捕獲特定程式碼段的錯誤,不過它還有一套獨一無二的可以有效處理程序故障的程序連結系統,我們即將在此進行討論。

1.程序連結如何工作

Erlang程序意外退出時,會產生一個退出訊號。所有與瀕死程序連結的程序都會收到這個訊號。預設情況下,接收方會一併退出並將訊號傳播給與它連結的其他程序,直到所有直接或間接連結在一起的程序統統退出為止(參見圖1-2

)。這種級聯行為可以使一組程序像單個應用一樣退出,因此係統整體重啟時你不必擔心是否還有殘存下來未能完全關閉的程序。

1-2 崩潰程序發出的退出訊號被傳播到所有與之連結的程序,一般情況下它們會共同退出,以便完成對整個程序組的清理工作

前面我們曾提到過利用程序來清理複雜狀態。其基本原理是:每個程序完整封裝自己的全部狀態,因此程序退出時系統的其餘部分不會受損。如同單個程序一樣,這一點對相互連結的程序組也同樣適用。一個程序崩潰,與之協作的其他程序也一併退出,如此便可乾淨利落地抹掉之前建立的所有複雜狀態,既節省了程式設計師的時間也減少了錯誤。

鼓勵崩潰

當你還在絕望地糾結於如何挽回那些你可能根本無能為力的局面時,

Erlang的哲學卻是“鼓勵崩潰”—精確記錄下事發位置和經過後,把一切徹底拋下重新再來。這不太常見,但的確是一條強大的容錯祕訣,而且按這個思路建立起來的系統無論多複雜度都可除錯。

2.監督與退出訊號捕捉

OTP實現容錯的主要途徑之一就是改寫退出訊號預設的傳播行為。通過設定trap_exit程序標記,你可以令程序不再服從外來的退出訊號,而是將之捕捉。這種情況下,程序接收到訊號後,會先將其轉為一條格式為{'EXIT', Pid, Reason}的訊息,該訊息描述了哪個程序出於什麼原因而發生故障,然後這條訊息會像普通訊息一樣被丟入信箱,捕捉到訊號的程序就能分檢並處理這類訊息了。

這類會捕捉訊號的程序有時被稱為

系統程序,它們執行的程式碼往往有別於普通的工作程序(即通常不捕捉訊號的程序)。身為防範退出訊號進一步傳播的壁壘,系統程序阻斷了與之連結的其他程序和外界之間的聯絡,因而可用於彙報故障乃至重啟故障的子系統,正如圖1-3所示。我們將這類程序稱為監督者

1-3 監督者、工作者和訊號:某工作程序的崩潰被級聯傳播至所有與之連結的其他程序,訊號傳播至監督者後,監督者將程序組重啟。同一監督者轄區內的其他程序組則不受影響

停止並重啟整個子系統的目的在於將系統恢復到一個已知的可正常工作的狀態。這有點類似於重啟電腦:通過重啟你可以快刀斬亂麻地將電腦迅速恢復到可工作狀態。但重啟整臺電腦的問題在於粒度太大。理想狀況下,應該可以只重啟系統的一部分,粒度越小越好。Erlang的程序連結與監督者共同提供了一種細粒度的“重啟”機制。

不過,如果就到此為止,你還是得自己從頭實現監督機制,這需要縝密的思考和豐富的經驗,bug的清除和各種邊界情況的處理也要花費大量的時間。幸運的是,OTP框架提供了你所需要的一切:既有運用監督機制來構建應用程式的一套方法,也有穩定的、經過實戰考驗的基礎庫。

OTP允許監督者按預設的方式和次序來啟動程序。我們還可以告知監督者如何在單個程序故障時重啟其他程序、一段時間內嘗試重啟多少次後放棄重啟等。你所要做的就是提供一些引數和回撥。

然而系統不應該只允許一層監督者工作者結構。在任何複雜系統中,你都可以用多層的監督樹在多個層級重啟子系統來解決各種意外問題。

3.程序的分層容錯

通過分層可以將相關的子系統歸於同一個監督者的轄區之內。更重要的是,這樣做可以定義多個層級的基準工作狀態,隨時供你重置。在圖1-4中,你可以看到兩個分別受獨立監督程序監督的工作程序組AB。這兩個組和它們的監督者共同形成了一個更大的程序組C,並由樹中更高層的一個監督者負責。

1-4 一個分層的監督者工作者系統。如果出於某種原因監督者A崩潰或退出,它轄區內所有尚還存活的程序都會被強制關閉,同時C會收到通知,於是程序樹的左半邊會被重啟。監督者B則不受影響,除非C決定關閉整個系統

我們假設A組程序的任務是輸出供B組使用的資料流。無須B組,A組也可正常工作。更具體一點,比方說A組在處理和編碼多媒體資料,B組則予以展現。我們再假設A組處理的資料中有一小部分受損,且資料損壞的模式無法在開發應用時預測。

這種畸形資料會導致A組的程序工作異常。按照鼓勵崩潰的哲學,程序不會嘗試去解決問題而是直接崩潰;由於程序相互隔離,其他程序並不會受到錯誤輸入的影響。監督者檢測到程序崩潰後,會將A組重啟以回退到預設的基準狀態,從而使整個系統恢復到一個已知的基準點。美妙的是身為展現系統的B組完全不知曉也不關心這個過程。只要A組能為B組持續提供足夠的優質資料,使後者能為使用者展現質量過關的內容,你的系統就是成功的。

通過隔離系統中不相關的部分並將它們組織成監督樹,你可以劃分出多個子系統,每個都可獨立地在幾分之一秒內完成重啟,這樣一來,即便你的系統碰上不可預期的錯誤也可以穩健地執行。若A組無法正常重啟,它的監督者最終會放棄重啟並將問題上報至C組的監督者。在這種情況下,C組的監督者會一併關閉B組然後停工。想象一下若系統中同時執行著幾百個C這樣的子系統,這就相當於因資料錯誤而丟棄了一個多媒體連線,其他連線仍然照常工作。

然而,既然大家都跑在同一臺機器上,就不得不共用一些東西:記憶體、硬碟驅動器、網路連線,乃至處理器和所有相關電路,還有一樣最重要的,就是從同一個插座上接出來的那根電源線。如果這些東西中有一樣發生故障或斷開,不管怎麼分層怎麼做程序隔離都無法避免宕機。這就把我們帶入了下一個主題,也就是分散式—能助你實現最高級別的容錯並令你的解決方案伸縮自如的,正是Erlang的這個特性。

本文摘自——《Erlang/OTP併發程式設計實戰》

轉載於:https://blog.51cto.com/turingbook/916090