1. 程式人生 > >Actor簡介(二)

Actor簡介(二)

       在上一部分,我們已經介紹了actor的基本用法,知道如何建立一個actor。我相信大家肯定不想侷限於如此,一定想把actor緊緊的握在手中,掌握它的生死。come on ,來吧!

     Actor生命週期

       執行緒在執行歷程中,會經歷建立、準備、等待、阻塞等階段,這一系列我們稱之為生命週期。當然,actor也有它自己的生命歷程,比如建立、執行、重啟和銷燬等。在理想的情況下,Actor會任勞任怨的不停工作,但在實際情況下,網路超時或者程式異常,我們希望能夠及時感知並處理,例如actor重啟或停止執行。關於Actor的生命週期,我們先來看一張圖(來自

Akka官網):

        從圖中,我們看出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在出現異常時,是使用重啟、恢復、還是停止,限於篇幅,我們下一節繼續。