1. 程式人生 > >AKKA官方文件閱讀筆記(1)JAVA版2.5.16

AKKA官方文件閱讀筆記(1)JAVA版2.5.16

準備工作:

Actor層級結構

在這裡插入圖片描述 其實在你用程式碼建立Actor之前,Akka自己就已經建立三個actor了,它們都是負責監管自己下面的actor的:

  • / 這個就是傳說中的跟監管者,是所有actor的祖先,當系統終止時,它一定是最後一個被停止的
  • /user 這個是所有咱們用程式碼建立的Actor的祖先
  • /system 不曉得這是做啥的,非使用者建立的actor的祖先吧 想搞清楚actor的層級結構,最好就是自己建立一些actor,並打印出它們的引用,你可以在包com.lightbend.akka.sample中增加下面這個類:
package com.lightbend.akka.sample;
import akka.actor.AbstractActor; import akka.actor.AbstractActor.Receive; import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.actor.Props; class PrintMyActorRefActor extends AbstractActor { @Override public Receive createReceive() { return receiveBuilder() .matchEquals
("printit", p -> { ActorRef secondRef = getContext().actorOf(Props.empty(), "second-actor"); System.out.println("Second: " + secondRef); }) .build(); } } public class ActorHierarchyExperiments { public static void main(String[] args) throws java.io.IOException {
ActorSystem system = ActorSystem.create("testSystem"); ActorRef firstRef = system.actorOf(Props.create(PrintMyActorRefActor.class), "first-actor"); System.out.println("First: " + firstRef); firstRef.tell("printit", ActorRef.noSender()); System.out.println(">>> Press ENTER to exit <<<"); try { System.in.read(); } finally { system.terminate(); } } }

執行main方法可以看到輸出的內容是:

First: Actor[akka://testSystem/user/first-actor#1053618476]
Second: Actor[akka://testSystem/user/first-actor/second-actor#-1544706041]

其中testSystem是ActorSystem的名稱,其後的user表示這是使用者(就是咱們用程式碼)建立的Actor,後面就是類似linux資料夾一樣的表示方式了,很容易理解。#後面的一串兒數值是Actor的唯一標識,大部分時候不用管

Actor的生命週期

actor的生命是從我們建立它開始,當一個Actor停止時,會先遞迴地停止它的孩子Actor,這種做法簡化了資源釋放並避免資源洩露。 推薦在actor內部通過getContext().stop(getSelf())寫法來中止它自己,一般是在actor完成它地任務之後收到一個約定好的停止訊息,作為對這個訊息地響應,停止它自己。也可以通過getContext().stop(actorRef)停止其他地actor,但是這種做法不推薦使用,最好改為給相應的actor傳送停止訊息 Akka的actor API有很多生命週期相關的鉤子你可以去重寫,最常用的就是preStart()preStop()了,看名字也很容易理解,preStart()方法一定會在開始處理訊息之前被執行。

Actor錯誤處理

在父Actor和子Actor的整個生命週期中,它們都是緊密相關的,任何時候一個Actor出錯了(就是receive方法中丟擲了錯誤),它就會被臨時掛起,然後這個錯誤會傳到父級那裡(具體怎麼傳這裡沒說,其實也是Actor類裡的一個方法),父級Actor會決定怎麼處理子Actor丟擲的這個錯誤,可以看出,每個Actor負責監管它的子級,預設的監管策略停止並重啟子Actor

給遠端Actor傳送訊息

發訊息給遠端actor和本地的actor是類似的,但是我們要記住:

  • 兩者的延遲會有較為明顯的差別,因為你要考慮頻寬和訊息的大小
  • 傳送遠端訊息包含更多的步驟也更容易出錯,要考慮可靠性
  • 傳送本地訊息不會對訊息做限制,反之,遠端訊息會限制message size

對於訊息傳送,Akka有以下兩個特性:

  • 訊息交付:最多傳送一次(訊息不會重複傳送,但不保證交付)
  • 訊息順序:對於一對傳送者和接收者,訊息是有序的

訊息交付 訊息傳遞的分類往往是以下三類:

  • 最多一次 At-most-once delivery: 每條訊息傳送接收方會收到0次或者1次。就是有可能收不到但絕不會重複。
  • 至少一次 At-least-once delivery: 每條訊息會被潛在地多次傳送,至少有一條傳送成功。有可能收到重複訊息但絕不會丟訊息。
  • 準確一次 Exactly-once delivery:每條訊息不多不少傳送一次

第一種就是Akka採用地,它是效能最高的,開銷最小,因為訊息即發即棄( fire-and-forget),傳送端接收端都不用維護訊息的狀態。相應的第二種,傳送端要維護訊息的狀態,接收端要有確認機制。第三種是最昂貴的,傳送端和接收端都要維護訊息狀態。

在一個actor系統中, 我們要確定交付的含義 — 在哪一個時間點,系統認為訊息傳送完成了:

    1. 當訊息傳送到網路中?
    1. 當訊息被目標Actor所在的主機接收時?
    1. 當訊息投放到目標Actor所在的郵箱?
    1. 當目標Actor開始處理這條訊息?
    1. 當目標Actor處理訊息完成?

大部分框架和協議宣稱在類似4或5的時候,聽起來挺合理的,但是這些框架和協議提供的保證真的能達到4或5的級別嗎?舉個簡單又實際的例子: 使用者提交一個訂單,我們希望返回此訂單被處理併成功寫入資料庫磁碟中了。

如果我們依賴於成功處理訊息,則只要訂單已提交給相應API,處理它並將其放入資料庫,actor就會報告成功。 不幸的是,在呼叫API之後,可能會發生以下任何一種情況:

  • 主機崩了
  • 反序列化失敗了
  • 資料校驗失敗了
  • 資料庫崩了
  • 程式碼出BUG了

這表明交付保證不會轉化為域級保證。 我們只想在訂單實際完全處理和保留後報告成功。 可以報​​告成功的唯一實體是應用程式本身(也就是消費者實際處理訊息的程式碼),因為只有它知道怎樣才算處理成功。 沒有通用框架可以確定特定域的細節以及在該域中被認為是成功的。

在這個特定的例子中,我們只希望在成功的資料庫寫入後發出成功訊號,資料庫確認訂單現在已安全儲存。 由於這些原因,Akka將保證的責任提升到應用程式本身,即您必須自己實現它們。 這使您可以完全控制要提供的保證。 現在,讓我們考慮Akka提供的訊息排序,以便於推理應用程式邏輯。

訊息排序 在Akka中,對於給定的一對Actor(傳送者和接收者),直接從第一個傳送到第二個的訊息不會亂序接收。 強調:這種保證僅適用於訊息直接傳送,而不是使用中間者(中介) 如果:

  • Actor A1 給A2傳送了訊息: M1, M2, M3
  • Actor A3 也給A2傳送了訊息: M4, M5, M6

這意味著: M1一定比M2, M3先到 M2一定比M3先到 M4一定比M5, M6先到 M5一定比M6先到 A2從A1和A3處收到的訊息可能會是夾雜著間隔著的 因為是不可靠交付,任何訊息都有可能丟失, 也就是到達不了A2