Actor模型與Akka Actor體系基礎(chǔ)總結(jié)
點擊上方藍色字體,選擇“設(shè)為星標”

前言
最近用業(yè)余時間把Flink的RPC基礎(chǔ)設(shè)施翻了個底朝天,又與之前分析過的Spark RPC機制做了一些對比,越發(fā)覺得Actor模型甚為精妙,值得簡單記錄一下,順便也可作為日后解析Flink RPC機制的基礎(chǔ)入門。
Actor模型
Actor模型由Hewitt、Bishop和Steiger在1973年通過論文《A Universal Modular Actor Formalism for Artificial Intelligence》提出,是一個創(chuàng)新的并發(fā)、分布式計算和編程模型。該模型的理念是“萬物皆Actor”,即以Actor作為最基本的功能單元,且需要遵循以下幾個基本規(guī)則。
所有的計算都是在Actor中執(zhí)行的。
Actor之間只能通過消息進行通信,且消息是不可變的。
Actor串行處理并響應(yīng)消息。當(dāng)一個Actor響應(yīng)消息時,它可以進行下列操作:
* 更改狀態(tài)或行為;
* 發(fā)送有限數(shù)量的消息給其他Actor;
* 創(chuàng)建有限數(shù)量的子Actor。
Actor一詞在此語境下仍然沒有確定的中文譯名,有人把它翻譯為“角色”,大致貼切。
一個符合Actor模型的簡單系統(tǒng)如下圖所示。Actor本質(zhì)上是狀態(tài)、行為、郵箱三要素的集合。

狀態(tài)(State):Actor內(nèi)部維護的變量及數(shù)據(jù)。每個Actor都單獨維護自己的狀態(tài),與其他Actor隔離。
行為(Behavior):Actor內(nèi)部定義的一組計算邏輯(如函數(shù)),用于處理接收到的消息以及改變狀態(tài)數(shù)據(jù)。
郵箱(Mailbox):可以視為與接收方Actor關(guān)聯(lián)的FIFO消息隊列。由于Actor串行處理消息,發(fā)送方發(fā)來的來不及處理的消息會存入郵箱中,接收方再從郵箱逐條獲取pending的消息。(當(dāng)然,一個Actor既可以是發(fā)送方也可以是接收方)
可見,Actor模型另辟蹊徑解決了并發(fā)環(huán)境中最棘手的問題,即共享數(shù)據(jù)的問題。在傳統(tǒng)方案中,總需要通過同步機制(鎖、信號量、原子性內(nèi)存操作等)保證共享數(shù)據(jù)的一致性。但是同步操作的開銷都比較大,往往會拖累高并發(fā)情況下的性能表現(xiàn),并且容易引起死鎖等其他問題。而Actor模型純依賴消息傳遞,消息可以異步、非阻塞地處理,且狀態(tài)是隔離的,不需要再考慮同步,簡單而高效。
當(dāng)然,Actor的結(jié)構(gòu)也很簡潔,單個Actor只需利用單線程執(zhí)行,所以非常輕量級,1GB的內(nèi)存可以容納上百萬的Actor實例。
Actor模型有眾多成熟的實現(xiàn),例如Erlang語言的并發(fā)機制就是完全基于它來實現(xiàn)的。接下來簡要介紹Akka,它是目前最活躍的Actor模型開源項目之一,同時也是Flink RPC的基礎(chǔ)。而Spark的舊版本同樣使用Akka構(gòu)建其RPC體系,后來的新版本雖然換用了Netty,但其設(shè)計理念仍然可以近似視為簡化版的Akka。
Akka Actor體系
Akka官網(wǎng)首頁的介紹如下。
高并發(fā)、分布式、彈性、消息驅(qū)動、基于JVM,這就是Akka的五個關(guān)鍵詞,可見是深得Actor模型的精髓。
整個Akka生態(tài)分為很多庫(也叫模塊),如:Actor、Remoting、Cluster、Persistence、Streams、HTTP等。當(dāng)然,Actor庫是Akka核心中的核心,下面也僅簡要總結(jié)與Actor庫相關(guān)的基礎(chǔ)知識。
Akka Actor是按照樹形層次結(jié)構(gòu)來組織的,其關(guān)系示意圖如下所示。

Akka通過Actor系統(tǒng)(ActorSystem)來管理所有Actor,每個JVM實例內(nèi)只有一個ActorSystem。當(dāng)ActorSystem啟動時,默認有3個守護(guardian)Actor:
/:根守護Actor,如同文件系統(tǒng)中的根,最先被創(chuàng)建,最后被銷毀;
/system:系統(tǒng)守護Actor,Akka本身以及基于Akka構(gòu)建的某些模塊會在該路徑下創(chuàng)建子Actor;
/user:用戶守護Actor,我們在使用Akka過程中創(chuàng)建的Actor都會位于這個路徑下。當(dāng)調(diào)用ActorSystem.actorOf()方法時,會在/user下直接創(chuàng)建;而當(dāng)調(diào)用某Actor的ActorContext.actorOf()方法時,會在該Actor下創(chuàng)建子Actor。
創(chuàng)建或者根據(jù)路徑查找Actor,返回給用戶的都是ActorRef,可以視為Actor實例的不可變、可序列化的句柄(引用),用戶通過ActorRef來操作Actor,比如向其發(fā)送消息。
下圖示出Actor從低級到高級的三層抽象,即Actor、ActorContext和ActorRef,以及它們對應(yīng)的路徑ActorPath。

可見,Actor的實際層級關(guān)系維護在上下文實例ActorContext中(ActorContext也包含有向當(dāng)前Actor發(fā)送消息的那個ActorRef),而Actor的路徑則維護在ActorRef中。這樣就保證了從屬于不同ActorSystem的Actor之間可以正常通信。
Actor的層次結(jié)構(gòu)同時也是監(jiān)督(supervision)機制的基礎(chǔ)。當(dāng)一個Actor失敗時,它會通知其父Actor采取相應(yīng)的動作(如直接恢復(fù)、重啟、停止或者將失敗信息繼續(xù)向高層傳遞)。下圖示出一個Akka Actor的完整生命周期。

可見,Akka還提供了一些生命周期的觸發(fā)器方法(稱為hook/鉤子),用戶可以通過重寫這些方法來管理Actor的生命周期。特別需要注意的是,如果一個Actor停止,那么它的所有子Actor也會隨著停止。
最后有一個問題,整個ActorSystem是如何被驅(qū)動的呢?答案是依靠一個中心化組件——Dispatcher(調(diào)度器/分發(fā)器),負責(zé)將Actor和與其關(guān)聯(lián)的郵箱中的消息調(diào)度到線程中進行處理。它的原理并不難,形象的圖示如下,就不多廢話了。


版權(quán)聲明:
文章不錯?點個【在看】吧!??




