package actor.faulttolerance
import akka.actor.SupervisorStrategy.Restart
import akka.actor._
import akka.event.LoggingReceive
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
/**
* Created by lc on 2015/11/28.
*/
object CounterService {
case class Increment(n: Int)
case object GetCurrentCount
case class CurrentCount(key: String, value: Long)
case class ServiceUnavailable(msg: String) extends RuntimeException
private case object Reconnect
}
class CounterService extends Actor {
import Counter._
import CounterService._
import Storage._
override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 5 seconds) {
case _: StorageException => Restart
}
val key = self.path.name
var storage: Option[ActorRef] = None
var counter: Option[ActorRef] = None
var backlog = IndexedSeq.empty[(ActorRef, Any)]
val MaxBacklog = 10000
override def preStart(): Unit = {
initStorage()
}
/**
* *
* The child storage is restarted in case of failure, but after 3 restarts,
* and still failing it will be stopped. Better to back-off than continuously
* failing. When it has been stopped we will schedule a Reconnect after a delay
* Watch the child so we receive Terminated message when it has been terminated
*/
def initStorage(): Unit = {
storage = Some(context.watch(context.actorOf(Props[Storage], name = "storage")))
counter foreach {
_ ! UseStorage(storage)
}
storage.get ! Get(key)
}
def receive = LoggingReceive {
case Entry(k, v) if k == key && counter == None =>
// Reply from Storage of the initial value, now we can create the Counter
val c = context.actorOf(Props(classOf[Counter], key, v))
counter = Some(c)
// Tell the counter to use current storage
c ! UseStorage(storage)
// and send the buffered backlog to the counter
for ((replyTo, msg) <- backlog) c.tell(msg, sender = replyTo)
backlog = IndexedSeq.empty
case msg@Increment(n) => forwardOrPlaceInBacklog(msg)
case msg@GetCurrentCount => forwardOrPlaceInBacklog(msg)
case Terminated(actorRef) if Some(actorRef) == storage =>
// After 3 restarts the storage child is stopped.
// We receive Terminated because we watch the child, see initStorage.
storage = None
// Tell the counter that there is no storage for the moment
counter foreach {
_ ! UseStorage(None)
}
// Try to re-establish storage after while
context.system.scheduler.scheduleOnce(10 seconds, self, Reconnect)
case Reconnect =>
// Re-establish storage after the scheduled delay
initStorage()
}
def forwardOrPlaceInBacklog(msg: Any) {
// We need the initial value from storage before we can start delegate to
// the counter. Before that we place the messages in a backlog, to be sent
// to the counter when it is initialized.
counter match {
case Some(c) => c forward msg
case None =>
if (backlog.size >= MaxBacklog)
throw new ServiceUnavailable(
"CounterService not available, lack of initial value")
backlog :+= (sender() -> msg)
}
}
}
代码来自akka doc,如果子actor存储失败3次,主管actor CounterService将发出Restart。在存储actor重启期间,当stoped时,CounterService actor将监视存储actor的死亡,获取Terminated消息,将其存储引用设置为None,并在10秒后重新连接。在重新连接时,CounterService actor将使用:storage = Some(context.watch(context.actorOf(Props[Storage], name = "storage")))
创建一个新的存储角色。
从akka doc我知道:
context.actorOf
将创建一个新的actor ref。因此,通过重新启动并由CounterService actor的context.actorOf
再次创建存储actor吗?通过重新启动创建的演员会被遗忘吗?
或者当contex.actorOf
看到存储角色已经重新启动并进行实例交换时,它只是使用新的并且并不总是创建一个新的?我看了医生,但没找到线索。
答案 0 :(得分:0)
通过仔细阅读akka doc找到答案。
重启频率设置了限制。在上面的代码中,它在5秒内最多重试3次。如果超出限制,则停止子actor。 因此前3次存储演员失败,演员立即重启。它是主管,CounterService什么都不做。重试3次或5秒后,存储actor被停止,CounterService在10秒内收到存储actor终止消息和rennect。
所以: 1.当contex.actorOf看到存储actor已经重新启动并进行实例交换时,它只是使用新的并不总是创建一个新的? 当存储actor重启时,CounterService actor将不知道。 2.重新启动创建的演员会被遗忘吗? 它将在重试限制后停止。