我們首先來看一下官方給出的Actor的聲明周期的圖:
在上圖中,Actor系統中的路徑代表一個地方,其可能會被活着的Actor占據。最初路徑都是空的。在調用actorOf()時,將會為指定的路徑分配根據傳入Props創建的一個Actor引用。該Actor引用是由路徑和一個Uid標識的。重啟時只會替換有Props定義的Actor示例,但不會替換引用,因此Uid保持不變。
當Actor停止時,其引用的生命周期結束。在這一時間點上相關的生命周期事件被調用,監視該Actor的Actor都會獲得終止通知。當引用停止后,路徑可以重復使用,通過actorOf()
創建一個Actor。在這種情況下,除了UID不同外,新引用與老引用是相同的。
ActorRef
始終表示引用(路徑和UID)而不只是一個給定的路徑。因此如果Actor停止,並且創建一個新的具有相同名稱的Actor,則指向老化身的ActorRef
將不會指向新的化身。
相對地,ActorSelection
指向路徑(或多個路徑,如果使用了通配符),且完全不關注有沒有引用占據它。因此ActorSelection
不能被監視。獲取某路徑下的當前化身ActorRef
是可能的,只要向該ActorSelection
發送Identify
,如果收到ActorIdentity
回應,則正確的引用就包含其中。也可以使用ActorSelection
的resolveOne
方法,它會返回一個包含匹配ActorRef
的Future
。
從上圖我們可以發現Actor的生命周期主要包含三個狀態:開始、終止和重啟。下面分別就 這三個狀態進行說明。
一、開始
其實Actor的生命周期是使用Hooks體現和控制的,我們可以重新相關的hooks,從而實現對Actor生命周期各環節的細粒度控制。而當Akka通過Props構建一個Actor后,這個Actor可以立即開始處理消息,進入開始(started)狀態。Akka提供了針對開始狀態的事件接口(event hooks)preStart方法,因此,我們可以重寫該方法進行一些操作,例如:
override def preStart={ log.info ("Starting storage actor...") initDB }
二、終止
一個Actor可能因為完成運算、發生異常又或者人為通過發送Kill,PoisonPill強行終止等而進入停止(stopping)狀態。而這個終止過程分為兩步:
第一步:Actor將掛起對郵箱的處理,並向所有子Actor發送終止命令,然后處理來自子Actor的終止消息直到所有的子Actor都完成終止。
第二步:終止自己,調用postStop方法,清空郵箱,向DeathWatch發布Terminated
,通知其監管者。
整個人過程保證Actor系統中的子樹以一種有序的方式終止,將終止命令傳播到葉子結點並收集它們回送的確認消息給被終止的監管者。如果其中某個Actor沒有響應(即由於處理消息用了太長時間以至於沒有收到終止命令),整個過程將會被阻塞。
因此,我們可以再最后調用postStop方法,來進行一些資源清理等工作,例如:
override def postStop={ log.info ("Stopping storage actor...") db.release }
三、重啟
重啟是Actor生命周期里一個最重要的環節。在一個Actor的生命周期里可能因為多種原因發生重啟(Restart)。造成一個Actor需要重啟的原因可能有下面幾個:
(1)在處理某特定消息時造成了系統性的異常,必須通過重啟來清理系統錯誤
(2)內部狀態毀壞,必須通過重啟來重新構建狀態
(3)在處理消息時無法使用到一些依賴資源,需要重啟來重新配置資源
其實,Actor的重啟過程也是一個遞歸的過程,由於其比較復雜,先上個圖:
在默認情況下 ,重啟過程主要分為以下幾步:
(1)該Actor將被掛起
(2)調用舊實例的 supervisionStrategy.handleSupervisorFailing 方法 (缺省實現為掛起所有的子Actor)
(3)調用preRestart方法,preRestart方法將所有的children Stop掉了!(Stop動作,大家注意!),並調用postStop回收資源
(4)調用舊實例的 supervisionStrategy.handleSupervisorRestarted 方法 (缺省實現為向所有剩下的子Actor發送重啟請求)
(5)等待所有子Actor終止直到 preRestart 最終結束
(6)再次調用之前提供的actor工廠創建新的actor實例
(7)對新實例調用 postRestart(默認postRestart是調用preStart方法)
(8)恢復運行新的actor