Beautiful Concurrency & Pretty Erlang
前言
環(huán)信以“連接人與人,連接人與商業(yè)”為使命,旨在為廣大企業(yè)開發(fā)者提供最優(yōu)質(zhì)的全球即時通訊PaaS服務。如何實現(xiàn)高并發(fā)場景下,彈性化的保障服務質(zhì)量是我們一貫的業(yè)務要求和技術(shù)追求。
本文試圖通過漸進的技術(shù)分析,和大家分享我們在互聯(lián)網(wǎng)高并發(fā)技術(shù)方案選擇上的一些思考和決策;從技術(shù)選型的高度為架構(gòu)師在面對高并發(fā)業(yè)務設(shè)計時需要考慮的方方面面做一個參考。
接下來,enjoy:
Beautiful Concurrency–并發(fā)的必要性
- 世界是并發(fā)的,軟件也應該是并發(fā)的
我們生活在其中的世界,就是一個巨大的并發(fā)系統(tǒng)。每時每刻,在世界的每一個角落,人類的每一個個體都在和這個世界進行著頻繁的能量交互,信息交互也是其中的重要組成部分;當我們轉(zhuǎn)換身份,以造物主的視角俯瞰這個世界里所有的萬物(動物,植物,海洋,土壤,機器等等)時,也可以看到它們同樣在和這個世界進行著無休止的能量/信息交互。
為了與這個世界進行有效的交互,軟件也應該是并發(fā)的。
- 世界是分布的,軟件也應該是分布的
地球是圓的,世界是平的,不管怎樣,從宇宙大爆炸的那個奇點之后,就是分布的宇宙/世界。作為與世界萬物(包括人類自己)交互的軟件,自然也必須滿足分布式要求。而這種地理分布(Geo.Distribution)特性,也僅是并發(fā)在空間維度下的反映而已。
- 世界不可預測,軟件也應該是容錯的
沒有完美的世界:沖突,災難隨時在發(fā)生,不管在什么樣的維度上。作為軟件,bug,crash也是不可規(guī)避的現(xiàn)實挑戰(zhàn)。即使存在完美的沒有bug的程序,運行程序的硬件也可能出現(xiàn)故障。為了增強軟件的容錯性,代碼的獨立性(指一個故障不會影響到故障任務以外的其它任務)和故障檢測以及故障處理是關(guān)鍵:這一切都需要并發(fā),因為串行程序的容錯性遠遠不如并發(fā)程序。
并發(fā)方案概覽
“七個模型”來源于PaulButcher著的《Seven Concurrency Models in Seven Weeks》,中文譯名《七周七并發(fā)模型》,概覽的介紹了并發(fā)領(lǐng)域的常見方案,希望能給架構(gòu)師提供一個輪廓化的分類描述。本人在其基礎(chǔ)上添加了一些自己的拓展思考(見下文中斜體部分):
1.線程與鎖:線程與鎖模型有很多眾所周知的不足,但仍是其他模型的技術(shù)基礎(chǔ),也是很多并發(fā)軟件開發(fā)的首選。—這個方案其實是一個anti-pattern,在高并發(fā)場景下如履薄冰(bug,dead lock如影隨形),讓開發(fā)者和運維人員膽戰(zhàn)心驚。Ugly Locks to Ugly Concurrency (丑陋的鎖,丑陋的并發(fā)):locks and condition variables is fundamentally flawed!
2.函數(shù)式編程:函數(shù)式編程日漸重要的原因之一,是其對并發(fā)編程和并行編程提供了良好的支持。函數(shù)式編程消除了可變狀態(tài),所以從根本上是線程安全的,而且易于并行執(zhí)行。—函數(shù)之美,邏輯之美!相信很多從命令式語言(Imperative Programming)轉(zhuǎn)戰(zhàn)到函數(shù)式編程語言(Functional Programminmg)的時候都會發(fā)出這樣的感嘆。其實這一進步恰恰體現(xiàn)了人類在不斷的進化過程中,對這個世界認知不斷提煉,思維模式逐步由具象走向抽象的演進軌跡。而回到高并發(fā)的話題上,函數(shù)式編程以不可變狀態(tài)這一簡易的策略,贏得了完美贊譽,儼然已是君臨天下的明日霸主!
3.分離標識與狀態(tài):如果一個線程引用了持久數(shù)據(jù)結(jié)構(gòu),那么其他線程對數(shù)據(jù)結(jié)構(gòu)的修改對該線程就是不可見的。因此持久數(shù)據(jù)結(jié)構(gòu)對并發(fā)編程的意義非比尋常,其分離了標識(identity)與狀態(tài)(state)。—這又是一個很妙的策略,而大家熟知的version control system如git,包括比特幣/區(qū)塊鏈的機制都是這一思想指導下的具體實踐,限于篇幅,本文不做進一步展開。
4.ActorModel:一種適用性很廣的并發(fā)編程模型,適用于共享內(nèi)存模型和分布式內(nèi)存模型,也適合解決地理分布型問題,能提供強大的容錯性。—最開始接觸到Actor模型就是通過Erlang語言,后來又接觸到Akka(基于Scala)等基于各種語言實現(xiàn)的框架,也越來越體會到這一模型在高并發(fā)場景下的游刃有余。本文后續(xù)部分會展開介紹。另外,做個招聘小廣告,環(huán)信通訊云研發(fā)團隊正在廣納英才,歡迎懂Erlang,有相關(guān)高并發(fā)開發(fā)經(jīng)驗的小伙伴加盟,虛位以待!點這里直接聯(lián)系我們喲!
5.CSP(Communicating Sequential Processes,CSP):表面上看,CSP模型與Acto rModel很相似,兩者都基于消息傳遞。不過CSP模型側(cè)重于傳遞信息的通道,而Actor Model側(cè)重于通道兩端的實體,使用CSP模型的代碼會帶有明顯不同的風格。—這里就是channel可以大施拳腳的天地了,gogogo!限于篇幅,本文不做進一步展開。同樣的,歡迎懂Golang,有相關(guān)高并發(fā)開發(fā)經(jīng)驗的小伙伴加盟,虛位以待!
6.數(shù)據(jù)級并行:每個筆記本電腦里都藏著一臺超級計算機——GPU。GPU利用了數(shù)據(jù)級并行,不僅可以快速進行圖像處理,也可以用于更廣闊的領(lǐng)域。如果要進行有限元分析、流體力學計算或其他的大量數(shù)字計算,GPU的性能將是不二選擇。—在過去的兩年里,華人之光,黃仁勛(Jensen Huang,CEO of Nvidia),從自家的壁爐里一次又一次給大家?guī)砹苏鸷呈澜绲母镄庐a(chǎn)品,讓之前高不可攀的GPU飛入尋常百姓家,也帶來了一次又一次的算力之爭。相信在不久的將來,有了GPU算力加持和人工智能算法的普惠使用,會有無數(shù)的AI應用層出不窮的涌現(xiàn),它們會從云端落地到邊緣,人類可能會比自己想象的更早的進入前途未卜的人機爭霸時代。
7.Lambda架構(gòu):Lambda架構(gòu)綜合了Map Reduce和流式處理的特點,是一種可以處理多種大數(shù)據(jù)問題的架構(gòu)。—Lambda架構(gòu)也是采用了數(shù)據(jù)并行處理技術(shù),但是它把并行算力的微觀場景放大到了一個更大的尺度:將數(shù)據(jù)和計算分布到成千上萬臺機器組成的集群上進行,將并發(fā),分布特性整合到一套方案中,通過兩層(批處理層-Batch Layer,加速層-Speed Layer/Streaming Process)的組合,實現(xiàn)了高計算效率和低延遲的“魚與熊掌兼得”。
Pretty Erlang
環(huán)信的全球即時通訊云的核心網(wǎng)絡(luò)是基于Erlang/OTP開發(fā)的,截止目前共服務了幾十萬APP客戶,單集群日消息幾十億量級并還在不斷挑戰(zhàn)新高。多年的經(jīng)驗積累,我們?nèi)栽诓粩嗟膶ο到y(tǒng)進行優(yōu)化,榨取計算,網(wǎng)絡(luò)等資源的內(nèi)生價值,挑戰(zhàn)系統(tǒng)一個又一個不斷提升的指標要求。感謝Erlang/OTP,有了它,我們像站在了巨人肩膀上的一名揮舞利刃的勇士,能夠從容應對各種“黑云壓城城欲摧”的業(yè)務壓力和不斷變化,五花八門的業(yè)務需求。以下,我們簡單總結(jié)了Erlang/OTP幾點讓我們“迷醉的特質(zhì)”,給想入坑的小伙伴以參考:
Letit Crash!
再一次,讓我們理解了“思維高度決定人生高度”。當其它語言或者解決方案防御性的,想方設(shè)法捕獲異常/錯誤并盡力挽回“敗局”的時候,Erlang采納了”Letitcrash!”的策略,把重點放在了錯誤的檢測和錯誤通知體系上,通過嚴謹設(shè)計的單向,雙向link機制,OTP基于此設(shè)計的supervisor behaviour層級(Hierarchical)管理匯報機制,”任其崩潰“的同時又干凈漂亮的將所有Actor(進程)在發(fā)生異常時的行為進行了簡單但有效的管理,獲得了意想不到的好處:
代碼簡潔易懂,僅在需要關(guān)心崩潰處理的層級存在容錯代碼;
由于Actor Model的設(shè)計,Actor之間相互獨立,也不共享狀態(tài),因此任何一個Actor的崩潰并不會影響其它Actor,遑論它的管理者(Supervisor),因此管理者可以從容的處理被管理Actor的崩潰;
管理者也可以不處理崩潰,僅記錄相應崩潰,繼而通過查看崩潰通知來進行后續(xù)處理。這個策略在后續(xù)PatternMatching時候還會見到,我們可以在_Other(所有匹配均未觸發(fā))保護匹配中記錄相應信息,而不必為事先預估不到的場景絞盡腦汁!
Actor Model
Actor Model是PureOO的設(shè)計(而Java類與方法的設(shè)計并不是,意不意外?):每一個Actor封裝了狀態(tài),外界并沒有任何方法來操縱(manipulate)對象,它們唯有通過發(fā)送消息通知Actor,由Actor自己控制對消息的處理,這種簡化同樣為高并發(fā)處理提供了意料之外的強大支持。
Actor Model適用于共享內(nèi)存模型和分布式內(nèi)存模型,也適合解決地理分布型問題,能提供強大的容錯性,用了都說好!
我個人在曾經(jīng)的工作中做過一個網(wǎng)絡(luò)安全過濾的產(chǎn)品,是靠自己團隊設(shè)計編寫的一套消息隊列處理機制來解耦不同業(yè)務對相同數(shù)據(jù)的處理,后來回想起來才驀然發(fā)現(xiàn)其實這就是個簡陋的ActorModel設(shè)計,只是那個時候還不知道Erlang/OTP,要是早用上這把利器,能節(jié)省我生命中多少個日日夜夜。
Functional Programming
介紹函數(shù)式編程的書籍,資料已經(jīng)很多了,本文不做過多的展開。簡單列舉下個人的幾點體會:
程序最終還是要交給機器處理,因此要盡量按照機器的思維模式去編寫程序(雖然有時候會讓程序員頭大),最簡單的(a+b)與(+ab):前者對人類友好,而后者對機器友好,因而也能帶來更好的程序一致性,繼而使得系統(tǒng)可以設(shè)計的更簡單;
命令式編程的代碼由一系列改變?nèi)譅顟B(tài)的語句構(gòu)成,而函數(shù)式編程則是將計算過程抽象成表達式求值。這些表達式由純數(shù)學函數(shù)構(gòu)成,而這些數(shù)學函數(shù)是第一類對象(我們可以像操作數(shù)值一樣操作第一類對象)并且沒有副作用。由于沒有副作用,函數(shù)式編程可以更容易做到線程安全,因此特別適合于并發(fā)/并行編程。
函數(shù)之美帶來美之并發(fā)!原文摘抄:
For me, a beautiful program is one that is so simple and elegant that it obviously has no mistakes, rather than merely having no obvious mistakes. If we want to write parallel programs that work reliably, we must pay particular attention to beauty.
Pattern Matching
又一個讓你被美折服的設(shè)計:函數(shù)在這種Pattern Matching的語法結(jié)構(gòu)的描摹下,儼然變成了一道道證明題,你只需要描述(并不需要窮舉)你所關(guān)心的場景(Conditon)下自己的想法,剩下的就交給”模式匹配“這個自動化機器幫你完成。你可能想不到的是,”PatternMatching“發(fā)揮功效的地方并不只是case/if出現(xiàn)的地方,你的整個代碼都是在”Pattern Matching“的魔力下散發(fā)它優(yōu)雅的魅力。
Future Evolution
對高并發(fā)的不懈追求將一直是環(huán)信通訊云研發(fā)團隊的目標,我們也期待在這條崎嶇山路上不斷攀登,厚積薄發(fā),為環(huán)信的客戶帶來一貫的極致產(chǎn)品體驗!