《Elixir in Action》書評及作者問答錄(作者 Sergio De Simone ,譯者 邵思華 發布於 2015年9月29日)
《Elixir in Action》是由Manning所出版的一本新書,本書為讀者介紹了Elixir這門語言以及Erlang虛擬機,同時也討論了與並發編程、容錯以及與高可用性相關的話題。InfoQ有幸與本書的作者Sa?a Juri?進行了一次訪談。
《Elixir in Action》的內容源自於Juri?在Erlang方面的經驗,他為此特意創建了一個博客,為來自面向對象背景的程序員展現Erlang的優勢。Juri?之後轉而使用Elixir,這是一種函數式的並發編程語言,它的目標是提供一種比起以Prolog為啟發創建的Erlang更簡便的語法,以及更高等的抽象能力。
本書的內容采取了一種漸進式的方式,首先介紹了Elixir的語法與它的基本特性,例如宏、模式匹配、模塊、多態等等,隨後介紹了如何創建一個容錯的、高可用的、並發的分布式系統。在全書的一些核心章節中涵蓋了Erlang平臺的大量知識,其中的主題包括管理進程、持久化、通過supervision樹進行運行時錯誤處理的多種方式,以及“任其崩潰”(let it crash)等設計哲學。
總的來說,《Elixir in Action》一書不僅為如何使用Elixir語言與Erlang VM,同時也為讀者如何邁進高可用性系統這一領域打下了堅實的基礎。InfoQ與Sa?a Juri?進行了一次訪談,以了解這本書背後的更多知識。
InfoQ:是什麽原因促使你編寫了本書,它與現有的一些針對Elixir的書籍又有何不同之處呢?
Sa?a:《Elixir in Action》的目標是幫助那些具有編程經驗,但缺乏Erlang、Elixir或函數式編程背景知識的程序員開發能夠在生產環境中運行的系統。在這本書的構思階段,我就假設本書的大部分讀者都來自於面向對象語言的背景。對於這些讀者來說,書中的許多內容都是新穎的:新的語言、運行時與生態系統,包括函數式編程與類似於Actor的並發編程思想。讀者需要打下大量的知識基礎,而這可能會令人望而生畏懼。
我相信,通過一種更為專註的方式,初學者的學習過程將能夠得到極大的簡化。因此,我並不打算編寫一本完整的參考書,而是決定專註於那些對於多數目標讀者來說有些不尋常的核心概念:函數式編程、並發,以及OTP框架的核心思想。我相信一旦讀者開始對這些主題感覺到“心意相通”,他們在學習本書並未敘述的一些內容時就會更容易。舉例來說,如果讀者掌握了Erlang中的並發知識,並且理解了大多數最重要的OTP概念(GenServer、Supervisor、Application),那麽他們就能夠較容易地自行學習其它的抽象概念,例如Task、Agent或GenEvent。
我相信,這是目前唯一一本以這種方式進行撰寫的Elixir書籍。任何一位想要使用Elixir/Erlang技術打造可伸縮的、高容錯性的分布式系統,都必須學習Elixir in Action一書中所介紹的各種材料。當然,你也可以通過其它資源學習這些內容,但我認為,本書是目前唯一一本面向Elixir介紹以上所有主題的書籍。
InfoQ:你能否簡單地介紹一下你在Elixir方面的經驗?它對於你實現工作目標提供了哪些程度的幫助?
Sa?a:對我來說,Elixir最重要的一方面實際上是Erlang VM,這是一切功能的基礎,這也是我首次接觸Erlang時對於我幫助最大的內容。大約5年以前,我當時需要實現一個基於長輪詢的服務器推,它需要不斷地將頻繁變化的數據傳輸給數以千計的連接用戶。經過一段時間的評估之後,我們最終選擇了Erlang,我從中也收獲了許多經驗。Erlang以一種結構化的方式幫助我克服了重重阻礙:使用它創建解決方案的過程非常順利,即使我對它的開發平臺還有些陌生。最終設計出的系統具備了高伸縮性、高效性以及靈活性。我能強烈地感覺到Erlang在支持著我,即使我犯下了各種錯誤。這套系統能夠處理各種預料之外的狀況,而我甚至還沒有在這方面做過什麽計劃。
InfoQ:Elixir特別適合於哪些類型的項目?
Sa?a:在我看來,Elixir/Erlang適合於任何類型的服務端系統:即需要持續運行,並且盡可能持續提供服務的軟件。這方面一個很明顯的示例就是基於web的系統,用於處理傳入的HTTP請求,但也需要進行其它活動,例如後臺的、周期性的作業或緩存管理。在這種系統中,許多活動在某個具體時間往往處於掛起狀態。而Erlang應對這種並發情況的途徑讓開發者的擔子輕了許多。如果每個獨立的活動都能夠分配至少一個獨立的Erlang進程(不要與OS的進程混淆了),我們就不僅能夠實現可伸縮性,還能夠改善容錯性:一個單一的進程故障不會對整個系統的絕大部分產生影響。並且還有許多方法能夠檢測到這種故障,然後從故障中恢復。
我發現這種處理方式非常直觀,並且任意一種類型的後端系統都能夠從中受益。我看到某些觀點說Erlang只適合於大規模系統,或某些特定的領域,例如電信領域。我不同意這種觀點。Erlang能夠幫助系統實現大規模化,而這種特質在任何類型的生產系統中都是必要的,無論其規模與領域如何。因為如果某個系統做不到高可用 ,那麽它就會頻繁地產生故障。即使你不需要實現傳說中的9個9的可用性,但你也應當希望讓你的系統停機時間盡量減短吧。實現這一點是一個艱難的挑戰,而Erlang則能夠幫助你實現這個目標。
InfoQ:你怎樣描述Elixir與Erlang兩者之間的關系呢?
Sa?a:我的看法是,Elixir是在Erlang與OTP所提供的強大基礎之上所擴展的能力,旨在提升開發者的生產力。我曾經進行過大量的全職Erlang開發工作,雖然我十分熱愛這門語言,但有許多任務也顯得過於繁瑣,我不得不無謂地處理一些低層次的機械性工作。而Elixir在這方面提供了大量實用的特性,包括語言(例如通過宏進行元編程,以及通過協議實現多態)與工具(例如構建項目的多種工具搭配,以及十六進制包管理器)兩方面,這讓我們能夠專註於處理一些更為實際的問題。
我個人的感受是使用Elixir進行開發比起使用純粹的Erlang進行開發要簡單許多。Elixir使可伸縮性與開發者的生產力之間這種刻意的權衡大大降低了,甚至是完全消除了。僅僅因為某個開發平臺允許我們創建高並發、可伸縮、高容錯的分布式系統,並不代表它就應當難以使用。同時,Elixir的運行時並沒有徹底遠離Erlang的哲學。作為一種函數式語言,它的語義與Erlang非常接近。Elixir能夠無縫地集成各種Erlang庫,因此開發者能夠完整地訪問整個Erlang生態系統。
InfoQ:在決定直接使用Erlang或Elixir時,這兩者有哪些缺陷是開發者需要考慮進去的?
Sa?a:我不認為它們有任何的缺陷。它們所具有的優勢主要來自於VM本身,以及已經過充分測試的OTP框架,而你可以從其中任何一門語言中收獲這些益處。因此主要的決定因素在於其它的一些附加價值。Elixir加入了一些額外的特性,因此實際上它是一門比起Erlang更加復雜的語言。而這門語言的優勢在於它的代碼更加簡潔,在樣板代碼方面的負擔較少。與之相比,Erlang是一門更為簡單的語言,因此所涉及的代碼更多,但它也因而顯得更為明確。
從我個人來看,Elixir在樣板代碼的減少與語言的明確性方面找到了一個很好的平衡點。它沒有Ruby等語言表面上那麽神奇,而仍然提供了各種實用的特性,其中最值得留意的就是元編程與多態性。
InfoQ:創建一個高可用、高容錯的並發系統是一項復雜的任務,尤其是從CAP定律的角度來看。要實現這一目標,選擇一門合適的語言與運行時環境的重要性有多高?
Sa?a:這實際上取決於個人的觀點。人們在各種語言上都實現過大規模的系統,因此不用Erlang也是完全可能的。不過對我來說,問題不僅僅在於是否可能,還在於某個工具能夠在多大程度上幫助我們完成這一過程。畢竟,工具的目的就是為我們提供服務。
而這也是為什麽我很看重Erlang的一個原因。在我看來,它為系統化地處理編寫高可用系統所面臨的挑戰提供了簡單而又非常強大的構建塊。它的主要工具是Erlang進程,它讓我們能夠將工作分解為幾千個,乃至上百萬個獨立的部分。通過使用多個進程,我們就獲得了可伸縮性與容錯性。它的崩潰傳遞機制能夠讓我們有機會處理這些故障:如果某個部分崩潰了,系統中的其它部分會收到它的通知,並進行相應的處理。最後,無共享並發機制能夠實現分布式系統,即使在一臺獨立的機器上也不例外。其實在本質上,通過將整個工作分解為大量隔離的、完全獨立的實體(進程),我們已經實現了分布式工作。當然,將系統在多臺機器上實現集群仍然不是一件簡單的事,畢竟分布式系統在本質上就存在著復雜性。但至少Erlang已經為我們解決了一些低層次的工作,我們可以始終利用相同的基元功能實現協作,即進程與消息傳遞。因此我們就能夠專註於業務上內在的挑戰,而不是將大量的精力消耗在低層次的細節上。
總的來說,我認為Erlang能夠簡化實現高可用性的挑戰。你也可以使用Erlang以外的技術應對這些挑戰,但很可能會因此付出更多的努力。
InfoQ:Erlang的一個核心思想是使用非常輕量級的進程模型,這使得上下文切換的開銷非常低。另一方面,在許多系統中仍然用線程處理可伸縮性,而線程的可伸縮性往往會成為系統的瓶頸。為了避免這種瓶頸,可以使用一個完整的異步模型配合一個小型的線程池,這種做法也有成功案例(這裏有一個參考示例)。你能否詳細地分析一下Erlang的處理方式與完整的異步方法相比所具有的優勢?
Sa?a:我認為Erlang為我們創建高並發的系統提供了一種優秀而整潔的抽象,而任何一種需要持續處理各種不同任務的系統在本質上都是並發的。Erlang的處理方式非常適合於這種類型的問題,你總是可以通過進程來應對各種類型的任務,無論是I/O密集型還是CPU密集型任務,並且你可以信任VM會高效地分派工作。在使用Erlang不太會出現許多頓悟的情況,也不太會搬起石頭砸了自己的腳。它能夠讓減輕我們的負擔,讓我們專註於實際的業務問題。
反之,如果你打算自行設計一種線程池,那麽就不得不自己處理許多問題。打個比方,如果你在某個線程中執行一個時間很長的計算,那麽你會阻塞在同一個線程上掛起的其它活動。如果某個線程因為一個bug而產生故障,該線程上運行的所有活動都會失敗。這一點當然是能夠解決的,但你或許要為投入大量的時間,以實現一種類似於Erlang VM的功能。既然如此,為什麽不依賴於某個已被證實的解決方案呢?如果單純的處理速度或是內存占用確實極端重要,那麽采取自定義的實現方案可能還有一些益處,但在我所遇到的這些情形中來看,基本都不屬於這種情況。
InfoQ:Erlang的另一個基本原則也被Elixir保留下來了,那就是“任其崩潰”。這種做法目前已經演變為將例行公事般地幹掉進程作為一種確保系統能夠容忍這一事件的手段了。這種策略對於打造一個具備容錯性的Erlang/Elixir系統有多大的重要性?
Sa?a:Erlang設計的一個前提就是在生產環境中的系統有可能產生錯誤,但系統作為一個整體不能夠中止:它應當盡力保留所有的服務,並盡快地從故障中進行自我修復。
任其崩潰在這種場合扮演了一個核心角色,它是一種簡單的技術,能夠讓我們以一種有條不紊的方式處理系統的錯誤。在這種情形下,我們會選擇讓進程崩潰,並依靠Supervisor修復問題。這種做法的好處是該進程的主體代碼可以不必操心錯誤處理的問題,例如編寫try-catch或“if err != nil”這樣的代碼塊,而只關註主路徑上的邏輯。我們甚至還可以通過模式匹配的方式優雅地對各種期望進行斷言。
在我看來,這種方式比try-catch-ignore的做法更優秀,因為一旦進程中止,它的狀態也就消失了。而問題的根源很可能來自於有問題的狀態。在進程重啟之後,新的進程會生成全新的、穩定的狀態,因此進程能夠再次運轉。至少它可以穩定地運行一段時間,直到狀態再次出現問題為止。這種做法能夠讓系統中有問題的地方浮現出來,多數服務在這種方式下都會表現出偶爾的故障現象,直至問題的根源解決為止。
與任其崩潰相輔相成的一點是通過Supervisor進行恢復。如果你建立了一個細粒度的supervision樹,那麽所需重啟的部分也相對較少。一旦產生故障,你可以試著重啟系統中的一小部分如果問題仍然沒有解決,你可以逐漸增加這部分的區域,直到系統中有問題的那部分被重啟為止。相反,如果你采用了try-catch-ignore方式,就有可能導致錯誤的狀態始終延續,最終產生了一個永無休止的故障循環。
InfoQ:“任其崩潰”是僅屬於Erlang的一種獨特功能嗎?可否將其移植至其它不使用Erlang VM的環境中呢?
Sa?a:問得好!首先我要強調一點,OTP是用純粹的Erlang構建的,它依賴於Erlang VM的基礎功能。理解這一點非常重要,因為我曾經看到過一些說法,認為OTP能夠以某種方式“移植”到其它運行時環境中。但我認為這是不太可能的,除非目標運行時平臺能夠提供一些嚴格的保障。
具體到任其崩潰和supervisor來說, Erlang的VM為它們提供了一些重要的保障。
- 每個進程的狀態都是私有的,一旦進程中止,它不會留下任何垃圾狀態,從而也不會幹擾其它的進程。
- 當一個單一的進程崩潰時,其它進程不會受到影響,它們的運行不會被打斷,除非你有意這麽做。
- 其它進程能夠收到某個進程崩潰的通知,並進行一些相應的處理。
- 可以無條件地中止一個進程(即使是進程正在進行一個密集的CPU運算)。
- 進程可以擁有外部資源(例如文件句柄或socket),一旦進程中止,它所擁有的資源會自動回收。
前兩點特性能夠幫助我們將故障的後果局限在一定範圍內:如果有部分出現問題,整個系統的大部分依然能夠繼續提供服務。第三點保障能夠讓我們對某個故障進行響應,當發生崩潰時,Supervisor能夠對其進行糾正。最後兩點保證了適當的系統清理,如果沒有這兩點保障,系統可能會產生孤兒進程或是資源無法釋放的問題。
如果缺乏這些保障,我認為是無法實現Erlang的容錯性能力的。即使你能夠盡力接近,但永遠也做不到100%的功能,總會有些隱秘的功能是你無法察覺的。這並不是說你必須要使用Erlang VM才能夠實現任其崩潰的做法,只是說你需要一種能夠提供這些保障的VM。
InfoQ:你是否能夠分享一下你對於Elixir目前在業界的使用情況的展望?
Sa?a:雖然Elixir還是一門新生的語言,但它的基礎(Erlang)已經非常穩定,其能力近20年來在各種不同的大型系統中都得到了證實。成功的案例包括WhatsApp、RabbitMQ、Riak、實時競價(AdRoll),以及財務系統(Klarna)等等。至於Elixir,我已經看到它越來越多地出現在各種解決方案的生產環境中,例如遊戲的後臺或物聯網(IoT)。可以在這裏找到在生產環境中使用Elixir的公司的一個列表,我很期待看到它今後的發展。
關於本書作者
Sa?a Juri?是一位軟件開發者,他在使用Elixir和Erlang打造高負載、高並發的服務端系統方面具有豐富的經驗。
《Elixir in Action》書評及作者問答錄(作者 Sergio De Simone ,譯者 邵思華 發布於 2015年9月29日)