当我们谈论Actor的生命周期时 ,我们看到可以通过各种方式(使用ActorSystem.stop或ActorContext.stop或发送PoisonPill
来停止PoisonPill
还有Kill
和gracefulStop
)。
无论Actor死于什么原因,在某些情况下,系统中的其他Actor都想知道它。 让我们举一个与数据库对话的Actor的简单例子-我们将其称为RepositoryActor
。 出于明显的原因,系统中几乎没有其他参与者会向此RepositoryActor
发送消息。 这些“感兴趣的”演员希望继续eye
或watch
该演员,如果它出现故障。 现在,用Actor的术语称为DeathWatch
。 watch
和unwatch
watch
的方法直观地是ActorContext.watch
和ActorContext.unwatch
。 如果进行监视,则观察者将从停止的Actor收到Terminated
消息,他们可以轻松地将其添加到其receive
部分功能中。
与Supervision(下一个文章将在完成后将插入链接)不同,Supervision严格执行父子层次结构,任何Actor都可以watch
ActorSystem中的任何其他Actor。
让我们看一下代码。
码
QuoteRepositoryActor
- 我们的
QueryRepositoryActor
持有一堆quotes
作为List,并在收到QuoteRepositoryRequest
时提供随机QuoteRepositoryRequest
。 - 它跟踪接收到的消息数,如果接收到多于3条消息,则会使用
PoisonPill
杀死自己
这里没什么好看的。
package me.rerun.akkanotes.deathwatch
import akka.actor.{PoisonPill, Actor, ActorLogging, actorRef2Scala}
import me.rerun.akkanotes.protocols.QuoteRepositoryProtocol._
import scala.util.Random
class QuoteRepositoryActor() extends Actor with ActorLogging {
val quotes = List(
"Moderation is for cowards",
"Anything worth doing is worth overdoing",
"The trouble is you think you have time",
"You never gonna know if you never even try")
var repoRequestCount:Int=1
def receive = {
case QuoteRepositoryRequest => {
if (repoRequestCount>3){
self!PoisonPill
}
else {
//Get a random Quote from the list and construct a response
val quoteResponse = QuoteRepositoryResponse(quotes(Random.nextInt(quotes.size)))
log.info(s"QuoteRequest received in QuoteRepositoryActor. Sending response to Teacher Actor $quoteResponse")
repoRequestCount=repoRequestCount+1
sender ! quoteResponse
}
}
}
}
教师演员观察员
同样,对TeacherActorWatcher
没什么QuoteRepositoryActor
,只是它创建了QuoteRepositoryActor
并使用context.watch
对其进行QuoteRepositoryActor
。
package me.rerun.akkanotes.deathwatch
import akka.actor.{Terminated, Props, Actor, ActorLogging}
import me.rerun.akkanotes.protocols.TeacherProtocol.QuoteRequest
import me.rerun.akkanotes.protocols.QuoteRepositoryProtocol.QuoteRepositoryRequest
class TeacherActorWatcher extends Actor with ActorLogging {
val quoteRepositoryActor=context.actorOf(Props[QuoteRepositoryActor], "quoteRepositoryActor")
context.watch(quoteRepositoryActor)
def receive = {
case QuoteRequest => {
quoteRepositoryActor ! QuoteRepositoryRequest
}
case Terminated(terminatedActorRef)=>{
log.error(s"Child Actor {$terminatedActorRef} Terminated")
}
}
}
测试用例
这是有趣的一点。 坦白说,我从没想过可以对它们进行测试。 akka-testkit FTW。 我们将在这里分析三个测试用例:
1.确认收到Terminated
消息的声明
QuoteRepositoryActor
应该在收到第四条消息后向测试用例发送一条Terminated
消息。 前三个消息应该没问题。
"A QuoteRepositoryActor" must {
...
...
...
"send back a termination message to the watcher on 4th message" in {
val quoteRepository=TestActorRef[QuoteRepositoryActor]
val testProbe=TestProbe()
testProbe.watch(quoteRepository) //Let's watch the Actor
within (1000 millis) {
var receivedQuotes = List[String]()
(1 to 3).foreach(_ => quoteRepository ! QuoteRepositoryRequest)
receiveWhile() {
case QuoteRepositoryResponse(quoteString) => {
receivedQuotes = receivedQuotes :+ quoteString
}
}
receivedQuotes.size must be (3)
println(s"receiveCount ${receivedQuotes.size}")
//4th message
quoteRepository!QuoteRepositoryRequest
testProbe.expectTerminated(quoteRepository) //Expect a Terminated Message
}
}
2.如果未关注/未关注,则声明未接收到Terminated
消息
实际上,我们为了展示context.unwatch
只是在做过多的事情。 如果我们删除testProbe.watch
和testProbe.unwatch
行,那么测试用例就可以正常工作。
"not send back a termination message on 4th message if not watched" in {
val quoteRepository=TestActorRef[QuoteRepositoryActor]
val testProbe=TestProbe()
testProbe.watch(quoteRepository) //watching
within (1000 millis) {
var receivedQuotes = List[String]()
(1 to 3).foreach(_ => quoteRepository ! QuoteRepositoryRequest)
receiveWhile() {
case QuoteRepositoryResponse(quoteString) => {
receivedQuotes = receivedQuotes :+ quoteString
}
}
testProbe.unwatch(quoteRepository) //not watching anymore
receivedQuotes.size must be (3)
println(s"receiveCount ${receivedQuotes.size}")
//4th message
quoteRepository!QuoteRepositoryRequest
testProbe.expectNoMsg() //Not Watching. No Terminated Message
}
}
3.在TeacherActorWatcher
确认收到Terminated
消息
我们订阅EventStream并检查特定的日志消息以断言终止。
"end back a termination message to the watcher on 4th message to the TeacherActor" in {
//This just subscribes to the EventFilter for messages. We have asserted all that we need against the QuoteRepositoryActor in the previous testcase
val teacherActor=TestActorRef[TeacherActorWatcher]
within (1000 millis) {
(1 to 3).foreach (_=>teacherActor!QuoteRequest) //this sends a message to the QuoteRepositoryActor
EventFilter.error (pattern="""Child Actor .* Terminated""", occurrences = 1).intercept{
teacherActor!QuoteRequest //Send the dangerous 4th message
}
}
}
毫不奇怪, EventFilter
的pattern
属性期望使用正则表达式模式。 pattern="""Child Actor .* Terminated"""
应该与格式为Child Actor {Actor[akka://TestUniversityMessageSystem/user/$$d/quoteRepositoryActor#-1905987636]} Terminated
的日志消息Child Actor {Actor[akka://TestUniversityMessageSystem/user/$$d/quoteRepositoryActor#-1905987636]} Terminated
Github
与往常一样,该代码可从github获得 。 注意deathwatch
包。
翻译自: https://www.javacodegeeks.com/2014/11/akka-notes-deathwatch-7.html