Akka幫助您構(gòu)建可靠的應(yīng)用程序在一臺機(jī)器上使用多個(gè)處理器核心(“擴(kuò)大”)或分布在計(jì)算機(jī)網(wǎng)絡(luò)(“擴(kuò)張”)哮幢。關(guān)鍵的抽象使所有交互者在代碼單元——Actor——發(fā)生在消息傳遞過程中罐呼,這就是為什么在參與者之間傳遞消息時(shí)以精確的詞義得到它們的內(nèi)容。
以下的討論涉及到考慮到應(yīng)用拆分到多個(gè)網(wǎng)絡(luò)主機(jī)上睁本。不論發(fā)送到在本地JVM的Actor或遠(yuǎn)程的Actor基本的通信機(jī)制是一致的,當(dāng)然可以觀察到交付信息的延遲不同(可能取決于網(wǎng)絡(luò)鏈路的帶寬和消息大小))和可靠性灾而。對于遠(yuǎn)程消息發(fā)送顯然有更多的步驟,這意味著更多的出錯(cuò)可能性饭寺。另一個(gè)方面是阻课,本地傳遞只會在同一個(gè)JVM中通過引用信息,沒有任何限制底層對象發(fā)送艰匙,而遠(yuǎn)程運(yùn)輸將受消息大小限制限煞。
編寫Actor時(shí)采用悲觀策略以使每個(gè)交互者遠(yuǎn)程是安全的。這只有依靠屬性來保證员凝,并在下面詳細(xì)討論這些內(nèi)容署驻。有一些開銷發(fā)生在Actor實(shí)現(xiàn)時(shí)。如果樂意放棄的透明傳輸比如用一組緊密的Actor做樣例,把它們在同一個(gè)JVM只使消息傳遞完全可靠旺上。下面將進(jìn)一步權(quán)衡這方面細(xì)節(jié)瓶蚂。
作為一種補(bǔ)充部分,我們將涉及到一些頂級內(nèi)置部分建立更高可靠性宣吱。這章結(jié)尾將討論一下“死信機(jī)制”窃这。
一般的規(guī)則
這些是消息發(fā)送的規(guī)則(即tell
或!方法,也構(gòu)成詢問模式):
- 至多一次交付,例如非擔(dān)保的交付征候;
- 發(fā)送者-接收者每個(gè)消息有序化成對杭攻;
第一條規(guī)則通常發(fā)現(xiàn)在其它Actor實(shí)現(xiàn),當(dāng)?shù)诙lAkka特定于它們疤坝。
討論:至多一次交付是含義
描述交付機(jī)制的語義有三類:
- 至多一次交付指每個(gè)消息的機(jī)制是1次或0次交付兆解,更直白的說消息可能會丟失。
- 至少一次交付指每個(gè)消息的機(jī)制是嘗試多次交付跑揉,并有一次成功痪宰。更直白的說消息會重復(fù)但不會丟失。
- 只一次交付指每個(gè)消息只會交付給接收者一次畔裕,消息可能即不會被復(fù)制也不重復(fù)衣撬。
第一個(gè)代價(jià)最低——高性能,低開銷——因?yàn)樗鼘?shí)現(xiàn)即發(fā)即棄的方式不在發(fā)送端或傳輸狀態(tài)上保持狀態(tài)扮饶。第二個(gè)需要累計(jì)傳輸失敗量具练,這意味著發(fā)送端保持狀態(tài)以及接收端實(shí)現(xiàn)確認(rèn)機(jī)制。第三個(gè)代價(jià)最高甜无,之所以性能最差扛点,因?yàn)橐3纸邮斩说臓顟B(tài)還要過慮掉重復(fù)數(shù)據(jù)。
討論:為什么用非擔(dān)保交付
以上這個(gè)問題的核心是擔(dān)保表示:
- 網(wǎng)絡(luò)上的消息發(fā)送出去嗎?
2.消息在其他主機(jī)接收嗎? - 消息放到目標(biāo)Actor的郵箱了嗎岂丘?
- 目標(biāo)Actor開始處理消息了嗎陵究?
- 目標(biāo)Actor的消息處理成功了嗎?
這些的每一個(gè)都有各自的挑戰(zhàn)和開銷奥帘,很明顯在消息處理庫中有條件的將無法完成铜邮。考慮例子的配置郵箱類型以及一個(gè)郵箱的邊界如何與第三點(diǎn)相互寨蹋,甚至決定以上五個(gè)點(diǎn)的“成功”是什么松蒜。
沿這幾點(diǎn)的推理得到“Nobody Needs Reliable Messaging.”發(fā)送方知道的接收業(yè)務(wù)級別的確認(rèn)消息成功的唯一意義在于,Akka不是可以自己組成的已旧。(這不是一個(gè)想怎么做就能怎么做的框架)秸苗。
Akka依賴分布式并使用不可靠的通信傳遞消息,因此它不保存可以想像成漏水一樣运褪。這個(gè)模型已經(jīng)在Erlang中的取得了巨大的成功惊楼,用戶圍繞它來設(shè)計(jì)他們的應(yīng)用程序玖瘸。可以閱讀更多關(guān)于這種方法在Erlang文檔(10.9和10.10節(jié)),Akka密切關(guān)注它檀咙。
另一個(gè)角度看這個(gè)問題店读,它只提供基本保障的用例不需要更強(qiáng)的可靠性不支付的成本實(shí)施;它總是可以添加更強(qiáng)的可靠性的基本內(nèi)容上,但這是不可能的倒行逆施的刪除為了獲得更多的性能可靠性攀芯。
討論:消息排序
更具體的說屯断,對于給定的兩個(gè)Actor,消息收到時(shí)不會無序侣诺。這強(qiáng)調(diào)了只能保證應(yīng)用在發(fā)送時(shí)明確發(fā)送源和目標(biāo)時(shí)殖演,而不是使用介質(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保證適用于消息隊(duì)列的順序進(jìn)入收件人的郵箱座硕。如果郵箱遵循FIFO實(shí)現(xiàn)順序(例如PriorityMailbox),然后處理順序的Actor將偏離入隊(duì)秩序。
注意這些規(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收到后再收到(盡管其中任何一個(gè)可能丟失)涕蜂。這個(gè)順充可能不確定因不同消息延遲华匾,在A,B机隙,C在不同的網(wǎng)絡(luò)主機(jī)上蜘拉。請參閱下文。
注意
Actor創(chuàng)建被視為一個(gè)消息從父級發(fā)送到子級有鹿,如同上面所討論的旭旭。在發(fā)送消息到Actor時(shí)能重新在初始化時(shí)重新排序就意味著消息沒有到達(dá)因?yàn)锳ctor還沒生成。舉個(gè)例子發(fā)送一個(gè)消息從R2引用發(fā)送過來時(shí)葱跋,消息可能太早到而不能創(chuàng)建遠(yuǎn)程布署的Actor R1時(shí)持寄。更好的定義順序是創(chuàng)建Actor后立即發(fā)送一個(gè)消息給它。
通訊失敗
請注意以上兩個(gè)Actor間保證順序的討論僅限于用戶消息年局。Actor的子級通信是特別的系統(tǒng)消息际看,與用戶消息的順序無關(guān)。特別是:
子Actor C發(fā)送消息M到它的父級P
子Actor F處理失敗
父Actor P可能收到兩個(gè)事件順序M矢否,F(xiàn)或F,M
原因是內(nèi)部系統(tǒng)消息有自己的郵箱調(diào)用用戶排隊(duì)的順序和系統(tǒng)的消息不能保證出列的訂購時(shí)間脑溢。
JVM內(nèi)(本地)消息發(fā)送規(guī)則
留意正下這節(jié)所要做的
在這一節(jié)中依賴較強(qiáng)的可適應(yīng)性是不可取的僵朗,從應(yīng)用程序綁定到本地布署上赖欣。應(yīng)用程序被設(shè)計(jì)的不同(不僅僅是一些消息交換模式和一些Actor)以適應(yīng)一個(gè)集群上運(yùn)行的機(jī)器。我們的信條是一次設(shè)計(jì)验庙,任意布署顶吮。而要實(shí)現(xiàn)這一點(diǎn),人應(yīng)該只依賴于一般規(guī)則粪薛。
本地發(fā)送的可靠性
Akka測試套件的依賴在本地的上下文中沒有丟失的消息(及遠(yuǎn)程開發(fā)沒有錯(cuò)誤測試)悴了,這意味著我們努力保持測試穩(wěn)定。本地tell
操作可能因?yàn)橐恍┰虻腻e(cuò)誤就如同通常在JVM中調(diào)用的方法违寿。
- StackOverflowError
- OutOfMemoryError
- other VirtualMachineError
此外湃交,本地發(fā)送有特定的Akka方法使發(fā)送時(shí)出錯(cuò): - 比如郵箱沒有接收到消息(如,BoundedMaibox滿了)
- 接收的actor處理失敗或已經(jīng)終止
當(dāng)排除每一個(gè)錯(cuò)誤的配置引起第二個(gè)時(shí),第二個(gè)消息得不到反饋在處理異常藤巢。通知會被它的主客代替搞莺。這是一般不區(qū)分外部觀察者的失去了消息
本地消息發(fā)送順序
上述警告的不及物的消息假定在嚴(yán)格的FIFO郵箱中在特定條件下被消除。會注意到掂咒,這些很細(xì)微甚至涉及到未來優(yōu)化整個(gè)段落才沧。這可能是下列不完整的幾點(diǎn):
- 在收到頂級的Actor回復(fù)前,有一個(gè)內(nèi)部鎖保護(hù)內(nèi)部監(jiān)時(shí)隊(duì)列绍刮,這把鎖不是直接的温圆。這意味著排隊(duì)請求期間從不同的發(fā)送者到acotr的結(jié)構(gòu)(比如,細(xì)節(jié)更復(fù)雜)可能被重新排序根據(jù)底層線程調(diào)度孩革。由于不存在完全公平鎖在JVM上捌木,這是認(rèn)識上的誤區(qū)。
- 使用相同的機(jī)制在建設(shè)一個(gè)路由器嫉戚,更精確地ActorRef路由刨裆,因此Actor與路由器部署存在同樣的問題。
- 如前所述,發(fā)生任何鎖的問題是涉及在入隊(duì),這可能也適用于自定義郵箱彬檀。
這個(gè)列表已經(jīng)仔細(xì)編制,但其他問題場景可能逃脫了我們的分析帆啃。
本地排除和網(wǎng)絡(luò)排序如何實(shí)施
對于一個(gè)給定的規(guī)則對Actor、消息發(fā)送直接從第一個(gè)到第二個(gè)不會收到無序適用于通過網(wǎng)絡(luò)發(fā)送的消息與基于TCP的Akka遠(yuǎn)程傳輸協(xié)議窍帝。
在前一節(jié)中解釋當(dāng)?shù)叵l(fā)送服從傳遞因果順序在特定條件下努潘。這個(gè)命令可以違反了由于不同的消息傳遞延遲。例如:
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可能花很長時(shí)間旅行到node-3 比M2旅行經(jīng)過node-3經(jīng)過node-2
高級抽象
Akka提供強(qiáng)大的坤学、更高層次的抽象基于一個(gè)微小而持續(xù)的Akka核心工具集疯坤。
消息傳遞模式
正如上面所討論的可靠傳遞的要求是一個(gè)顯式的ACK-RETRY協(xié)議。在其最簡單的形式要求
- 一種識別個(gè)人信息和確認(rèn)相關(guān)信息
- 重試機(jī)制深浮,如果不確認(rèn)將重新發(fā)送消息
- 為接收器檢測和丟棄重復(fù)
第三種成為必要借助確認(rèn)不必要到達(dá)压怠。ACK-RETRY協(xié)議與業(yè)務(wù)級別的確認(rèn)支持“至少一次”的Akka持久模塊交付。副本可以檢測到跟蹤消息的標(biāo)識符通過“至少一次”交付飞苇。另一種實(shí)現(xiàn)第三部分將使處理消息冪等層面的業(yè)務(wù)邏輯菌瘫。
實(shí)現(xiàn)這三個(gè)需求的另一個(gè)例子是在可靠的代理模式(這是現(xiàn)在取代“至少一次”交付)蜗顽。
事件源
事件源(和共享)用于制造大型網(wǎng)站規(guī)模數(shù)十億的用戶,這個(gè)想法非常簡單:當(dāng)一個(gè)組件(Actor)過程命令將生成一個(gè)事件列表代表命令的效果雨让。這些事件存儲除了應(yīng)用于組件的狀態(tài)雇盖。這個(gè)方案的優(yōu)點(diǎn)是,事件只添加到存儲栖忠,沒有什么是永遠(yuǎn)的突變崔挖;這使得完美的復(fù)制和擴(kuò)展消費(fèi)者的事件流(即其他組件可以使用事件流來復(fù)制組件的狀態(tài)在一個(gè)不同的容器或反應(yīng)的變化)。如果組件的狀態(tài)因?yàn)闄C(jī)器故障或被排擠出緩存它可以很容易地重建重演了事件流(通常采用快照來加快這一進(jìn)程)庵寞。事件源支持Akka持久性狸相。
郵箱的明確確認(rèn)
通過實(shí)現(xiàn)一個(gè)自定義郵箱類型可以重試的消息處理接收Actor的一端為處理臨時(shí)失敗。此模式主要是有用的本地通信上下文交付擔(dān)保否則足以滿足應(yīng)用程序的需求皇帮。
請注意,規(guī)則的警告在jvm(本地)消息發(fā)送申請卷哩。
實(shí)現(xiàn)這種模式的一個(gè)例子是顯示在郵箱與明確的確認(rèn)。
死信
消息不能交付(這可以確定)將交付給一個(gè)叫做 /deadLetters
合成的Actor属拾。這交付發(fā)生在力所能及将谊;它可能會失敗甚至在本地JVM(例如在Actor終止)。通過發(fā)送的消息不可靠的網(wǎng)絡(luò)傳輸將丟失沒有出現(xiàn)死亡的信件渐白。
死信用于哪些方面
這個(gè)設(shè)備的主要用途是為調(diào)試,特別是如果一個(gè)Actor發(fā)送與到達(dá)不一致(通常檢查死者字母會告訴你,發(fā)送方或接收方設(shè)置錯(cuò)了沿途某處)尊浓。為了有效使用它盡可能避免發(fā)送deadLetters,即運(yùn)行您的應(yīng)用程序與一個(gè)合適的死信記錄器(參見下面的更多)不時(shí)和清理日志輸出纯衍。這種需要明智的應(yīng)用常識:很可能是避免發(fā)送Actor終止栋齿,使發(fā)送方的代碼更清晰。
死信服務(wù)遵循相同的規(guī)則對交付擔(dān)保和其他消息發(fā)送,因此它不能用于實(shí)現(xiàn)保證交付襟诸。
如何接收死信
Actor可以訂閱akka.actor.DeadLetter
在事件流瓦堵,Event Stream顯示如何使用它。訂閱的Actor將會收到所有死信件發(fā)表在這一點(diǎn)的本地系統(tǒng)歌亲。死信不是通過網(wǎng)絡(luò)傳播,如果想收集在一個(gè)地方要訂閱一個(gè)Actor/手動網(wǎng)絡(luò)節(jié)點(diǎn)和轉(zhuǎn)發(fā)菇用。死信在這個(gè)節(jié)點(diǎn)生成可以確定發(fā)送操作失敗,可以本地系統(tǒng)為遠(yuǎn)程發(fā)送(如果沒有可以建立網(wǎng)絡(luò)連接)或遠(yuǎn)程(如果你發(fā)送的Actor不存在在這個(gè)時(shí)間點(diǎn))。
死信不需要擔(dān)心
每一次Actor不會由自已決定停止陷揪,因?yàn)橛幸豢赡芩砸寻l(fā)送丟失惋鸥。一些復(fù)雜的自動關(guān)閉場景是良性的:看到akka.dispatch.Terminate
。終止消息意味著兩個(gè)終止請求,當(dāng)然只有一個(gè)能成功悍缠。同樣,你可能會看到akka.actor.Terminated
卦绣。但父級看孩子時(shí)終止消息Actor的層次結(jié)構(gòu)在死信從孩子開始。