摘要
也許您已經(jīng)聽說過Akka,一種用于建造可伸縮,彈性且高效應(yīng)用程序的工具包,支持Java和Scala編程語言。Akka包括了16個(gè)開源或商業(yè)的模塊,幫助開發(fā)者構(gòu)建從單JVM的Actor到網(wǎng)絡(luò)分區(qū)修復(fù),跨多個(gè)JVM的分布式集群等功能。如此多的特性,架構(gòu)師和開發(fā)者如何從高層的角度理解Akka?讀完本系列白皮書,您將更好的理解Akka “從A到Z”,這趟旅程從簡單的Actor開始,直到集群系統(tǒng)結(jié)束,您將會(huì)學(xué)到以下知識:
- Akka Actors如何運(yùn)作,從創(chuàng)建系統(tǒng)到管理監(jiān)督和路由
- Akka 使用Akka Streams 和 Alpakka實(shí)現(xiàn)Reactive Streams的方式
- 如何用Akka建造分布式集群系統(tǒng),甚至集群中的集群
- 對于分布式數(shù)據(jù),分布式持久化,發(fā)布-訂閱 和 ES/CQRS,Akka工具包如何通過多種組件提供開箱即用的解決方案
- 可用的Akka商業(yè)版技術(shù)有哪些,它們是如何工作的
Akka的歷史
Akka開始與1973(以IT世界的時(shí)間計(jì)算的話,相當(dāng)于8000年前),在MIT的實(shí)驗(yàn)室,數(shù)學(xué)博士Carl Hewitt 合作發(fā)表了論文《A Universal Modular Actor Formalism for Artificial Intelligence》,在這篇論文中,介紹了一種數(shù)據(jù)模型,將actors作為并發(fā)計(jì)算的通用原子類型。
“提出Actor 模型是基于高度并行計(jì)算的前景,計(jì)算系統(tǒng)可能由成百上千的獨(dú)立微處理器組成,每個(gè)處理器都擁有自己的本地內(nèi)存和通訊處理器,可以通過高性能通訊網(wǎng)絡(luò)在相互之間進(jìn)行通訊”
--Carl Hewitt
在那個(gè)時(shí)候,跨多種云基礎(chǔ)架構(gòu)的低延遲并發(fā)計(jì)算在商業(yè)上并不可行,一直到企業(yè)級云計(jì)算的到來。
2009年,灣光公司的合伙人兼CTO Jonas Bonér,創(chuàng)建了Akka項(xiàng)目,希望在JVM上構(gòu)造一種分布式,高并發(fā),事件驅(qū)動(dòng)的Actor模型實(shí)現(xiàn),就像 Erlang語言實(shí)現(xiàn)的Actor模型一樣。
現(xiàn)在到了2018年,離Akka十周年之剩一年之際,我們?nèi)栽诶^續(xù)我們的目標(biāo),將構(gòu)建多核,異步,自愈,并且高伸縮性系統(tǒng)的能力帶給Java和Scala的架構(gòu)師和開發(fā)者,而無需關(guān)注所有隱藏在后的底層細(xì)節(jié)。
值得注意的是,Akka是一個(gè)JVM工具包,而不是一個(gè)框架(例如Play 或者 Lagom).雖然面臨開源和商業(yè)組件的選擇(Lightbend Enterprise Suite的一部分),本系列文章將關(guān)注在actors, streams,HTTP,集群,持久化和部署概念上。
這趟旅程將從獨(dú)立的actors開始,介紹監(jiān)督和自愈的行為模式,然后講解通過Akka Streams, Akka HTTP 和 Alpakka實(shí)現(xiàn)Reactive Steams,再介紹事件Sroucing的分布式持久化和CQRS,微服務(wù)。最后到分布式集群以及如何管理,監(jiān)控,編排集群。
了解了藍(lán)圖,讓我們從Akka Actors開始吧。
第一部分 Akka Actors 及其工作方式
Actor 模型
讓我們從Akka系統(tǒng)的原子或分子級別來看看actors,相對于大多數(shù)開發(fā)者來說,這是一種完全不同的編程方式。許多開發(fā)者學(xué)習(xí)過面向?qū)ο缶幊?,甚至函?shù)式編程,大多數(shù)情況下,這是必要的,同步方式編程設(shè)計(jì)到了很多線程和相關(guān)元素。
然而Actor 模型有很大不同,一個(gè)Actor本身是一個(gè)class,可以用Java或者Scala實(shí)現(xiàn),actor具備方法,但是actor最大的不同在于actor的方法并不是在actor外部直接被別的類訪問調(diào)用的。
不直接調(diào)用actor的方法,這需要改變原來的習(xí)慣。和actor交互的唯一方式是通過Akka提供的 actor system向actor發(fā)送消息。
在actors中,actor之間消息是異步發(fā)送的,而不是同步的,如圖一所示,消息首先存入待處理消息收件箱,actor遍歷收件箱,一個(gè)一個(gè)地處理消息。這就是actor的基本機(jī)制,Akka系統(tǒng)就是由許多個(gè)actor構(gòu)成,actor之間互相通過發(fā)送消息進(jìn)行通訊。
其工作原理如圖2所示: actor A 發(fā)送消息給 actor B. 這是一個(gè)異步消息,就像兩個(gè)人互相發(fā)送短信。A 發(fā)送給 B一條消息, A可以繼續(xù)干別的事情,A并不會(huì)掛起等待B的回應(yīng),這和傳統(tǒng)的面向?qū)ο蟮姆椒ㄕ{(diào)用不一樣,傳統(tǒng)的方法調(diào)用必須等待方法返回。在這個(gè)例子中,A發(fā)了消息就繼續(xù)往下執(zhí)行了,或者執(zhí)行別的代碼,或者沒有代碼執(zhí)行,啥也不干。
在圖3中 B 收到A發(fā)送的消息,觸發(fā)了狀態(tài)的改變,例如B代表一個(gè)銀行賬戶,消息代表取款動(dòng)作的發(fā)生,因此狀態(tài)改變即更新賬戶余額,如果需要,B可以給A發(fā)送一條回執(zhí)消息:“已完成狀態(tài)更新”
現(xiàn)實(shí)中,actors A和B可能運(yùn)行在分布式環(huán)境,B可能在另一個(gè)機(jī)器上無法回應(yīng)。從一開始,這就提出了關(guān)于該做什么的問題;B從A獲得消息,完成任務(wù),也許會(huì)發(fā)送一條回執(zhí)消息。但這個(gè)流程也可能發(fā)生故障。
圖4 展示了A能夠直接向Akka的actor system報(bào)告一個(gè)請求: “請?jiān)谖磥砟硞€(gè)時(shí)間點(diǎn)給我發(fā)送一個(gè)消息“,可以是幾毫秒,幾秒或者幾分鐘之后。這個(gè)使用的場景是A向B發(fā)送消息,得不到回應(yīng)的情況下,一個(gè)超時(shí)的消息將會(huì)被發(fā)送。
在圖5中,A獲取到超時(shí)消息,A處理兩種類型的消息:從B得到的回應(yīng)消息或者超時(shí)消息。換句話說,A知道這兩種情形下分別該如何處理,這和異常處理不同,大多數(shù)異常只是被拋出,而在Akka中,彈性(resilience)是架構(gòu)中就考慮的特性,而不是事后的想法。
Actor監(jiān)督 (自修復(fù))
彈性是Akka設(shè)計(jì)的一部分,讓我們來看看Actor的監(jiān)督機(jī)制。Actor能夠創(chuàng)建子Actor,形成Actor的層級關(guān)系,就像我們在圖6中看到的一樣。在這個(gè)例子中,actor A是監(jiān)督者,創(chuàng)建了多個(gè)干活的actor,他們之間是一種"父-子"關(guān)系或者 “監(jiān)督者-工作者”的關(guān)系。
監(jiān)督者和工作者之間的關(guān)系是一種監(jiān)督策略,當(dāng)干活的actor處理消息遇到問題的時(shí)候,它自己并不處理這個(gè)異常,而是由監(jiān)督者來處理,這種監(jiān)督策略是定義好的,但也可以定制化??傊?,對于工作actor出現(xiàn)的異常,監(jiān)管actor始終有一個(gè)應(yīng)對計(jì)劃。
監(jiān)督者A的響應(yīng)包括:異常不嚴(yán)重的情況下恢復(fù)工作actor,繼續(xù)執(zhí)行;或者重啟工作actor;或者這個(gè)問題實(shí)在太嚴(yán)重,需要停止工作actor;最極端的情況是,監(jiān)督者也無法處理此問題,因此將問題向上報(bào)告給它的上級監(jiān)督者。
因此,根據(jù)嚴(yán)重程度,問題可以在層次結(jié)構(gòu)中逐級上升,這都處于開發(fā)者的控制之下。要記住的是,這是處理問題的明確方式。
對于actor系統(tǒng)的初學(xué)者,往往容易在actor中寫入很多的邏輯。隨著對actor模型越來越熟悉,會(huì)更容易地使用分而治之的策略。
分而治之的目的是為了降低風(fēng)險(xiǎn),通過構(gòu)建層次結(jié)構(gòu)的Actor來代理有風(fēng)險(xiǎn)的操作,從而降低影響。
在圖1-7中,我們有A1,B1和C3,C3將一些有風(fēng)險(xiǎn)的行為委托給actor D(D3),葉子節(jié)點(diǎn)用紅色標(biāo)記這些Actor做的是有風(fēng)險(xiǎn)的操作,例如通過網(wǎng)絡(luò)和數(shù)據(jù)庫通訊。有風(fēng)險(xiǎn)是因?yàn)榫W(wǎng)絡(luò)可能中斷,數(shù)據(jù)庫可能宕機(jī),或者其他的因素都可能導(dǎo)致錯(cuò)誤。
通過將風(fēng)險(xiǎn)的行為推給下層工作的Actor,上層的Actor得到了某種程度的保護(hù)。通過Actor的層次結(jié)構(gòu),工作被委托給更加細(xì)粒度的專門的Actor,就像圖中把風(fēng)險(xiǎn)行為推給樹的葉子節(jié)點(diǎn)。
路由和并發(fā)
Akka專注于多線程,多核并發(fā),使我們能輕松處理以前需要手工管理多線程的操作。多線程的事情很容易變復(fù)雜,所以Akka提供了開箱即用的并發(fā)特性,不會(huì)遇到Java和Scala中多線程編程的許多技術(shù)挑戰(zhàn)。
在圖1-8中,我們有Actor A和B,以及作為路由的actor R,R創(chuàng)建了一批工作的actor W,A發(fā)送消息給路由actor R,要求完成某項(xiàng)工作,R實(shí)際并不做這些工作,而是將工作代理給底下的actor W
在圖-9中,當(dāng)actor W在完成A的任務(wù)時(shí),這時(shí)B發(fā)了一個(gè)消息給路由actor R,要求完成一些工作。R會(huì)轉(zhuǎn)發(fā)消息給底下其他的某一個(gè)W,這樣我們就實(shí)現(xiàn)了多線程并發(fā)而不用寫任何代碼。從A和B的視角看,R在同一時(shí)刻做了很多工作,但是R在幕后實(shí)際上是通過代理工作到許多actor W來實(shí)現(xiàn)的。
在圖1-10中,我們再來看看這個(gè)例子中的Akka監(jiān)督策略。假設(shè)A發(fā)送了一個(gè)消息給路由R,路由R再轉(zhuǎn)發(fā)給實(shí)際工作的W,但是W在運(yùn)行過程中出錯(cuò)了。
在常用的“try-catch”方法編程中,捕獲的異常通常是發(fā)送給A。然而在actor系統(tǒng)中,是監(jiān)督者收到這個(gè)異常。因此在這個(gè)場景中是監(jiān)督者R收到了異常,而不是A
這就是超時(shí)機(jī)制起作用的地方了,R發(fā)送一個(gè)響應(yīng)給A說:“這里有一些問題,我無法完成你要求的任務(wù)”。無論如何,A沒有看到異常,作為調(diào)用者無需處理問題。而且作為服務(wù)的消費(fèi)者,最好也不是由它來處理,而是由服務(wù)的提供者來處理。
Actor模型作為Akka的核心,提供了許多強(qiáng)大的內(nèi)置特性來管理并發(fā)和彈性。它展現(xiàn)了一種不同的思維方式,是一種高效且安全的方法構(gòu)建分布式系統(tǒng)的方法。
總結(jié):
? Actors 是消息驅(qū)動(dòng)的,有狀態(tài)的構(gòu)建模塊,模塊間異步傳遞消息。
? 與傳統(tǒng)的同步和阻塞不同,Actors 是輕量的,不阻塞線程
? Actor能創(chuàng)建Actor,使用監(jiān)督層次結(jié)構(gòu)來支持從錯(cuò)誤自愈(彈性)。