Actor簡介(二)
在上一部分,我們已經介紹了actor的基本用法,知道如何建立一個actor。我相信大家肯定不想侷限於如此,一定想把actor緊緊的握在手中,掌握它的生死。come on ,來吧!
Actor生命週期
執行緒在執行歷程中,會經歷建立、準備、等待、阻塞等階段,這一系列我們稱之為生命週期。當然,actor也有它自己的生命歷程,比如建立、執行、重啟和銷燬等。在理想的情況下,Actor會任勞任怨的不停工作,但在實際情況下,網路超時或者程式異常,我們希望能夠及時感知並處理,例如actor重啟或停止執行。關於Actor的生命週期,我們先來看一張圖(來自
從圖中,我們看出Actor的生命週期主要包含建立並啟動(Start)、恢復(Resume)、重啟(Restart)、停止(Stop)這幾個階段,針對自身狀態變化,各階段主要有如下行為:
階段 |
行為 |
建立並啟動 (Start) |
actorOf()建立並啟動Actor時,指定物件Path和UID(物件唯一標識,通過getSelf().path().uid()獲取),預設執行preStart()方法,我們可以在該方法中進行資源初始化。 |
恢復 (Resume) |
actor出現異常時,在容錯機制下,可以讓actor恢復並繼續執行,此時actor會繼續使用之前的物件例項,狀態也回保留。 |
重啟 (Restart) |
重啟會經歷兩個過程: 1.呼叫舊例項的preRestart()方法,該方法會預設停掉所有子級actor並呼叫postStop()方法。 2.建立新例項,在新例項上呼叫postRestart()方法, 該方法預設會呼叫preStart()方法。 重啟之後,path和UID不變,ActorRef不變,但是自身狀態已改變。 |
停止 (Stop) |
停止Actor,會呼叫postStop()方法,同時會發送一條Terminated資訊給自己的監控者。 |
建立並啟動
上一章節,我們已經知道如何建立Actor物件,現在不做過多介紹,上一章節的ActorDemo類上,我們重寫preStart:
@Override
public void preStart() throws Exception {
System.out.println("actorDemo開始啟動");
}
大家執行可以發現,當actor在啟動的時候,預設就會執行該方法,針對這個特性,我們可以在該方法做一些資源初始化或建立子類Actor。
停止
在我們的程式裡,出現某種異常或當我們不需要actor時,我們可以停止actor的執行。在Akka中,停止一個actor相當的簡單,有三種方式供我們選擇,如下:
第一種 |
呼叫ActorSystem或者ActorContext的stop方法 |
第二種 |
給Actor傳送PoisonPill(毒藥丸) |
第三種 |
傳送一條kill訊息給Actor,此時會丟擲ActorKilledException異常,另外會通知父級做相應處理。 |
建立一個Actor,我們演示這三種停止方式:
public class StopActor extends AbstractActor {
private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
@Override
public Receive createReceive() {
return receiveBuilder().match(Object.class,s->{
log.info(s.toString());
}).build();
}
@Override
public void postStop() throws Exception {
log.info("actor stop");
}
public static void main(String[] args) {
ActorSystem system = ActorSystem.create("system");
ActorRef stopActor = system.actorOf(Props.create(StopActor.class), "stopActor");
//第一種:ActorSystem停止actor
//system.stop(stopActor);
//第二種
stopActor.tell(PoisonPill.getInstance(),ActorRef.noSender());
//第三種
//stopActor.tell(Kill.getInstance(),ActorRef.noSender());
}
}
使用第一種或第二種方式,我們會收到如下結果:
[INFO] [10/03/2018 20:57:38.517] [system-akka.actor.default-dispatcher-3] [akka://system/user/stopActor] actor stop
當使用第三種方式時,我們會多收到一條ERROR級別的訊息:
[ERROR] [10/03/2018 20:56:06.261] [system-akka.actor.default-dispatcher-3] [akka://system/user/stopActor] Kill (akka.actor.ActorKilledException: Kill)
使用kill停止actor,會丟擲ActorKilledException異常,上報到父級,父級預設處理就是停止Actor。
我們發現,使用Akka提供的API,我們可以很簡單的停止actor,但是大家想過沒有,在實際專案中,我們的業務是複雜多樣的,我們需要考慮停止actor是否會帶來不可預知的後果。其實大家也不必太過擔心,Actor在停止時已經有一套相當可靠的流程:
1.停止actor時,會先將正常的訊息處理完畢後,再完全停止,後續訊息將不再處理,郵箱(保留Actor訊息)將被掛起。
2.給子級傳送停止訊息,需等待子級全部停止,再停掉自己,停止後會呼叫postStop方法,釋放資源。
3.向生命週期監控者(DeathWatch)傳送Terminated訊息,以便監控者做相應處理。
下面我們寫一個監控actor,來顯示這個流程:
public class WatchActor extends AbstractActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().getSystem(), this);
private ActorRef stopActor;
@Override
public Receive createReceive() {
return receiveBuilder().match(Object.class, o -> {
logger.info(o.toString());
}).build();
}
@Override
public void preStart() throws Exception {
//初始化子類stopActor
stopActor=getContext().actorOf(Props.create(StopActor.class),"stopActor");
}
@Override
public void postStop() throws Exception {
logger.info("watchActor stop");
}
public static void main(String[] args) {
ActorSystem system = ActorSystem.create("system");
ActorRef watchActor = system.actorOf(Props.create(WatchActor.class), "watchActor");
system.stop(watchActor);
}
}
此時,當我們執行main方法時,會輸出如下內容:
[INFO] [10/04/2018 08:52:24.549] [system-akka.actor.default-dispatcher-4] [akka://system/user/watchActor/stopActor] actor stop
[INFO] [10/04/2018 08:52:24.551] [system-akka.actor.default-dispatcher-2] [akka://system/user/watchActor] watchActor stop
結果表明,子級actor停止後,才會停止父類actor。
有時,我們需要監控子級actor,針對子級actor的生命週期,做出不同的處理。監控actor可以使用getContext().watch方法(相反,getContext().unwatch取消監控),對WatchActor稍作修改,如下:
public static void main(String[] args) {
ActorSystem system = ActorSystem.create("system");
ActorRef watchActor = system.actorOf(Props.create(WatchActor.class), "watchActor");
watchActor.tell("stopActor",ActorRef.noSender());
// system.stop(watchActor);
}
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, s -> {
if ("stopActor".equals(s)) {
getContext().stop(stopActor);
}
}).match(Terminated.class, t -> {
logger.info("監控到" + t.getActor() + "停止了");
}).build();
}
@Override
public void preStart() throws Exception {
//初始化子類stopActor
stopActor = getContext().actorOf(Props.create(StopActor.class), "stopActor");
//用於監控actor
getContext().watch(stopActor);
}
此時,執行main方法,我們會得到如下結果:
[INFO] [10/04/2018 09:00:20.805] [system-akka.actor.default-dispatcher-4] [akka://system/user/watchActor/stopActor] actor stop
[INFO] [10/04/2018 09:00:20.809] [system-akka.actor.default-dispatcher-3] [akka://system/user/watchActor] 監控到Actor[akka://system/user/watchActor/stopActor#-213431265]停止了
結果表明,子級actor停止,會給父類傳送一條Terminated訊息。
在實際專案中,由於各種原因,例如網路、程式碼、伺服器等都可能出現問題,為了程式的高可用,就涉及到監督和容錯處理。這一塊內容就會涉及到我們的actor在出現異常時,是使用重啟、恢復、還是停止,限於篇幅,我們下一節繼續。