Akka幫助您構(gòu)建可靠的應(yīng)用程序在一臺機器上使用多個處理器核心(“擴大”)或分布在計算機網(wǎng)絡(luò)(“擴張”)。關(guān)鍵的抽象使所有交互者在代碼單元——Actor——發(fā)生在消息傳遞過程中,這就是為什么在參與者之間傳遞消息時以精確的詞義得到它們的內(nèi)容。
以下的討論涉及到考慮到應(yīng)用拆分到多個網(wǎng)絡(luò)主機上。不論發(fā)送到在本地JVM的Actor或遠(yuǎn)程的Actor基本的通信機制是一致的,當(dāng)然可以觀察到交付信息的延遲不同(可能取決于網(wǎng)絡(luò)鏈路的帶寬和消息大?。?和可靠性。對于遠(yuǎn)程消息發(fā)送顯然有更多的步驟,這意味著更多的出錯可能性。另一個方面是,本地傳遞只會在同一個JVM中通過引用信息,沒有任何限制底層對象發(fā)送,而遠(yuǎn)程運輸將受消息大小限制。
編寫Actor時采用悲觀策略以使每個交互者遠(yuǎn)程是安全的。這只有依靠屬性來保證,并在下面詳細(xì)討論這些內(nèi)容。有一些開銷發(fā)生在Actor實現(xiàn)時。如果樂意放棄的透明傳輸比如用一組緊密的Actor做樣例,把它們在同一個JVM只使消息傳遞完全可靠。下面將進(jìn)一步權(quán)衡這方面細(xì)節(jié)。
作為一種補充部分,我們將涉及到一些頂級內(nèi)置部分建立更高可靠性。這章結(jié)尾將討論一下“死信機制”。
一般的規(guī)則
這些是消息發(fā)送的規(guī)則(即tell
或!方法,也構(gòu)成詢問模式):
- 至多一次交付,例如非擔(dān)保的交付;
- 發(fā)送者-接收者每個消息有序化成對;
第一條規(guī)則通常發(fā)現(xiàn)在其它Actor實現(xiàn),當(dāng)?shù)诙lAkka特定于它們。
討論:至多一次交付是含義
描述交付機制的語義有三類:
- 至多一次交付指每個消息的機制是1次或0次交付,更直白的說消息可能會丟失。
- 至少一次交付指每個消息的機制是嘗試多次交付,并有一次成功。更直白的說消息會重復(fù)但不會丟失。
- 只一次交付指每個消息只會交付給接收者一次,消息可能即不會被復(fù)制也不重復(fù)。
第一個代價最低——高性能,低開銷——因為它實現(xiàn)即發(fā)即棄的方式不在發(fā)送端或傳輸狀態(tài)上保持狀態(tài)。第二個需要累計傳輸失敗量,這意味著發(fā)送端保持狀態(tài)以及接收端實現(xiàn)確認(rèn)機制。第三個代價最高,之所以性能最差,因為要保持接收端的狀態(tài)還要過慮掉重復(fù)數(shù)據(jù)。
討論:為什么用非擔(dān)保交付
以上這個問題的核心是擔(dān)保表示:
- 網(wǎng)絡(luò)上的消息發(fā)送出去嗎?
2.消息在其他主機接收嗎? - 消息放到目標(biāo)Actor的郵箱了嗎?
- 目標(biāo)Actor開始處理消息了嗎?
- 目標(biāo)Actor的消息處理成功了嗎?
這些的每一個都有各自的挑戰(zhàn)和開銷,很明顯在消息處理庫中有條件的將無法完成??紤]例子的配置郵箱類型以及一個郵箱的邊界如何與第三點相互,甚至決定以上五個點的“成功”是什么。
沿這幾點的推理得到“Nobody Needs Reliable Messaging.”發(fā)送方知道的接收業(yè)務(wù)級別的確認(rèn)消息成功的唯一意義在于,Akka不是可以自己組成的。(這不是一個想怎么做就能怎么做的框架)。
Akka依賴分布式并使用不可靠的通信傳遞消息,因此它不保存可以想像成漏水一樣。這個模型已經(jīng)在Erlang中的取得了巨大的成功,用戶圍繞它來設(shè)計他們的應(yīng)用程序。可以閱讀更多關(guān)于這種方法在Erlang文檔(10.9和10.10節(jié)),Akka密切關(guān)注它。
另一個角度看這個問題,它只提供基本保障的用例不需要更強的可靠性不支付的成本實施;它總是可以添加更強的可靠性的基本內(nèi)容上,但這是不可能的倒行逆施的刪除為了獲得更多的性能可靠性。
討論:消息排序
更具體的說,對于給定的兩個Actor,消息收到時不會無序。這強調(diào)了只能保證應(yīng)用在發(fā)送時明確發(fā)送源和目標(biāo)時,而不是使用介質(zhì)或其他信息傳遞特性(除非另有說明)。
如下例這樣保障:
Actor A1發(fā)送消息 M1,M2,M3 到 A2
Actor A3發(fā)送消息 M4,M5,M6 到A2
這意味著:
- M1必須在M2和M3之前交付;
- M2必須在M3之前交付;
- M4必須在M5和M6之前交付;
- M5必須在M6之前交付;
- A2能從A1和A3交叉看到消息;
- 由于不是保證交付,任何消息都可能拋下,即沒有到達(dá)A2
注意
Akka保證適用于消息隊列的順序進(jìn)入收件人的郵箱。如果郵箱遵循FIFO實現(xiàn)順序(例如PriorityMailbox),然后處理順序的Actor將偏離入隊秩序。
注意這些規(guī)則是不可達(dá)的
Actor A發(fā)送消息M1 到Actor C
Actor A 然后發(fā)送消息M2 到Actor B
Actor B轉(zhuǎn)遞消息M2到Actor C
Actor C可能以任何順序收到M1和M2
由于可達(dá)順序是M2在M1在Acotr C收到后再收到(盡管其中任何一個可能丟失)。這個順充可能不確定因不同消息延遲,在A,B,C在不同的網(wǎng)絡(luò)主機上。請參閱下文。
注意
Actor創(chuàng)建被視為一個消息從父級發(fā)送到子級,如同上面所討論的。在發(fā)送消息到Actor時能重新在初始化時重新排序就意味著消息沒有到達(dá)因為Actor還沒生成。舉個例子發(fā)送一個消息從R2引用發(fā)送過來時,消息可能太早到而不能創(chuàng)建遠(yuǎn)程布署的Actor R1時。更好的定義順序是創(chuàng)建Actor后立即發(fā)送一個消息給它。
通訊失敗
請注意以上兩個Actor間保證順序的討論僅限于用戶消息。Actor的子級通信是特別的系統(tǒng)消息,與用戶消息的順序無關(guān)。特別是:
子Actor C發(fā)送消息M到它的父級P
子Actor F處理失敗
父Actor P可能收到兩個事件順序M,F(xiàn)或F,M
原因是內(nèi)部系統(tǒng)消息有自己的郵箱調(diào)用用戶排隊的順序和系統(tǒng)的消息不能保證出列的訂購時間。
JVM內(nèi)(本地)消息發(fā)送規(guī)則
留意正下這節(jié)所要做的
在這一節(jié)中依賴較強的可適應(yīng)性是不可取的,從應(yīng)用程序綁定到本地布署上。應(yīng)用程序被設(shè)計的不同(不僅僅是一些消息交換模式和一些Actor)以適應(yīng)一個集群上運行的機器。我們的信條是一次設(shè)計,任意布署。而要實現(xiàn)這一點,人應(yīng)該只依賴于一般規(guī)則。
本地發(fā)送的可靠性
Akka測試套件的依賴在本地的上下文中沒有丟失的消息(及遠(yuǎn)程開發(fā)沒有錯誤測試),這意味著我們努力保持測試穩(wěn)定。本地tell
操作可能因為一些原因的錯誤就如同通常在JVM中調(diào)用的方法。
- StackOverflowError
- OutOfMemoryError
- other VirtualMachineError
此外,本地發(fā)送有特定的Akka方法使發(fā)送時出錯: - 比如郵箱沒有接收到消息(如,BoundedMaibox滿了)
- 接收的actor處理失敗或已經(jīng)終止
當(dāng)排除每一個錯誤的配置引起第二個時,第二個消息得不到反饋在處理異常。通知會被它的主客代替。這是一般不區(qū)分外部觀察者的失去了消息
本地消息發(fā)送順序
上述警告的不及物的消息假定在嚴(yán)格的FIFO郵箱中在特定條件下被消除。會注意到,這些很細(xì)微甚至涉及到未來優(yōu)化整個段落。這可能是下列不完整的幾點:
- 在收到頂級的Actor回復(fù)前,有一個內(nèi)部鎖保護(hù)內(nèi)部監(jiān)時隊列,這把鎖不是直接的。這意味著排隊請求期間從不同的發(fā)送者到acotr的結(jié)構(gòu)(比如,細(xì)節(jié)更復(fù)雜)可能被重新排序根據(jù)底層線程調(diào)度。由于不存在完全公平鎖在JVM上,這是認(rèn)識上的誤區(qū)。
- 使用相同的機制在建設(shè)一個路由器,更精確地ActorRef路由,因此Actor與路由器部署存在同樣的問題。
- 如前所述,發(fā)生任何鎖的問題是涉及在入隊,這可能也適用于自定義郵箱。
這個列表已經(jīng)仔細(xì)編制,但其他問題場景可能逃脫了我們的分析。
本地排除和網(wǎng)絡(luò)排序如何實施
對于一個給定的規(guī)則對Actor、消息發(fā)送直接從第一個到第二個不會收到無序適用于通過網(wǎng)絡(luò)發(fā)送的消息與基于TCP的Akka遠(yuǎn)程傳輸協(xié)議。
在前一節(jié)中解釋當(dāng)?shù)叵l(fā)送服從傳遞因果順序在特定條件下。這個命令可以違反了由于不同的消息傳遞延遲。例如:
node-1上Actor A發(fā)送消息M1到node-3上的actor C
然后node-1的Actor A上發(fā)送消息M2到node-2的Actor B
node-2上ActorB轉(zhuǎn)發(fā)消息M2到node-3的 Actor C
Actor C 可能以任何順序收到M1 和 M2
M1可能花很長時間旅行到node-3 比M2旅行經(jīng)過node-3經(jīng)過node-2
高級抽象
Akka提供強大的、更高層次的抽象基于一個微小而持續(xù)的Akka核心工具集。
消息傳遞模式
正如上面所討論的可靠傳遞的要求是一個顯式的ACK-RETRY協(xié)議。在其最簡單的形式要求
- 一種識別個人信息和確認(rèn)相關(guān)信息
- 重試機制,如果不確認(rèn)將重新發(fā)送消息
- 為接收器檢測和丟棄重復(fù)
第三種成為必要借助確認(rèn)不必要到達(dá)。ACK-RETRY協(xié)議與業(yè)務(wù)級別的確認(rèn)支持“至少一次”的Akka持久模塊交付。副本可以檢測到跟蹤消息的標(biāo)識符通過“至少一次”交付。另一種實現(xiàn)第三部分將使處理消息冪等層面的業(yè)務(wù)邏輯。
實現(xiàn)這三個需求的另一個例子是在可靠的代理模式(這是現(xiàn)在取代“至少一次”交付)。
事件源
事件源(和共享)用于制造大型網(wǎng)站規(guī)模數(shù)十億的用戶,這個想法非常簡單:當(dāng)一個組件(Actor)過程命令將生成一個事件列表代表命令的效果。這些事件存儲除了應(yīng)用于組件的狀態(tài)。這個方案的優(yōu)點是,事件只添加到存儲,沒有什么是永遠(yuǎn)的突變;這使得完美的復(fù)制和擴展消費者的事件流(即其他組件可以使用事件流來復(fù)制組件的狀態(tài)在一個不同的容器或反應(yīng)的變化)。如果組件的狀態(tài)因為機器故障或被排擠出緩存它可以很容易地重建重演了事件流(通常采用快照來加快這一進(jìn)程)。事件源支持Akka持久性。
郵箱的明確確認(rèn)
通過實現(xiàn)一個自定義郵箱類型可以重試的消息處理接收Actor的一端為處理臨時失敗。此模式主要是有用的本地通信上下文交付擔(dān)保否則足以滿足應(yīng)用程序的需求。
請注意,規(guī)則的警告在jvm(本地)消息發(fā)送申請。
實現(xiàn)這種模式的一個例子是顯示在郵箱與明確的確認(rèn)。
死信
消息不能交付(這可以確定)將交付給一個叫做 /deadLetters
合成的Actor。這交付發(fā)生在力所能及;它可能會失敗甚至在本地JVM(例如在Actor終止)。通過發(fā)送的消息不可靠的網(wǎng)絡(luò)傳輸將丟失沒有出現(xiàn)死亡的信件。
死信用于哪些方面
這個設(shè)備的主要用途是為調(diào)試,特別是如果一個Actor發(fā)送與到達(dá)不一致(通常檢查死者字母會告訴你,發(fā)送方或接收方設(shè)置錯了沿途某處)。為了有效使用它盡可能避免發(fā)送deadLetters,即運行您的應(yīng)用程序與一個合適的死信記錄器(參見下面的更多)不時和清理日志輸出。這種需要明智的應(yīng)用常識:很可能是避免發(fā)送Actor終止,使發(fā)送方的代碼更清晰。
死信服務(wù)遵循相同的規(guī)則對交付擔(dān)保和其他消息發(fā)送,因此它不能用于實現(xiàn)保證交付。
如何接收死信
Actor可以訂閱akka.actor.DeadLetter
在事件流,Event Stream顯示如何使用它。訂閱的Actor將會收到所有死信件發(fā)表在這一點的本地系統(tǒng)。死信不是通過網(wǎng)絡(luò)傳播,如果想收集在一個地方要訂閱一個Actor/手動網(wǎng)絡(luò)節(jié)點和轉(zhuǎn)發(fā)。死信在這個節(jié)點生成可以確定發(fā)送操作失敗,可以本地系統(tǒng)為遠(yuǎn)程發(fā)送(如果沒有可以建立網(wǎng)絡(luò)連接)或遠(yuǎn)程(如果你發(fā)送的Actor不存在在這個時間點)。
死信不需要擔(dān)心
每一次Actor不會由自已決定停止,因為有一可能它自已發(fā)送丟失。一些復(fù)雜的自動關(guān)閉場景是良性的:看到akka.dispatch.Terminate
。終止消息意味著兩個終止請求,當(dāng)然只有一個能成功。同樣,你可能會看到akka.actor.Terminated
。但父級看孩子時終止消息Actor的層次結(jié)構(gòu)在死信從孩子開始。