前文介绍角色的生命周期时提到过,当异常发生时,顶层的用户角色默认会重启。这里将详细讨论其背后的原理。
在Akka中,每个父角色都是其子角色的监管者。
当一个子角色失败了,它会中断消息处理,并向父角色发送一个消息来决定如何处理这个失败。
子角色失败之后用于确定父角色和该子角色的行为的策略称为监管策略(supervision-strategy)。父角色有可能采纳下列行为中的一种。
● 用Restart消息重启子角色。
● 用Resume消息让子角色恢复而不重启。
● 用Stop消息永久性地停止子角色。
● 用Escalate消息抛出同样的异常来停止自己。
在默认情况下,守护角色user的监管策略是重启失败的子角色。而用户角色在默认情况下会停止子角色。两种监管策略都可以被重载。
为了重载用户角色中的默认监管策略,需要重载Actor类的supervisorStrategy字段。
下载管理器的实现
object DownloadManager {
case class Download(url: String, dest: String)
case class Finished(dest: String)
}
class Downloader extends Actor {
val log = Logging(context.system, this)
def receive = {
case DownloadManager.Download(url, dest) =>
val content = Source.fromURL(url)
FileUtils.write(new java.io.File(dest), content.mkString)
sender ! DownloadManager.Finished(dest)
}
override def preRestart(reason: Throwable, message: Option[Any]): Unit = log.warning("Downloader Restart!")
override def postStop(): Unit = log.warning("Downloader Stop!")
}
class DownloadManager(val downloadSlots: Int) extends Actor {
import DownloadManager._
val log = Logging(context.system, this)
val downloaders = mutable.Queue[ActorRef]()
val pendingWork = mutable.Queue[Download]()
val workItems = mutable.Map[ActorRef, Download]()
private def checkDownloads(): Unit = {
if (pendingWork.nonEmpty && downloaders.nonEmpty) {
val dl = downloaders.dequeue()
val item = pendingWork.dequeue()
log.info(
s"$item starts, ${downloaders.size} download slots left")
dl ! item
workItems(dl) = item
}
}
def receive = {
case msg @ DownloadManager.Download(url, dest) =>//这里 @后绑定一个模式进行匹配
pendingWork.enqueue(msg)
checkDownloads()
case DownloadManager.Finished(dest) =>
workItems.remove(sender)
downloaders.enqueue(sender)
log.info(
s"'$dest' done, ${downloaders.size} download slots left")
checkDownloads()
}
override def preStart(): Unit = {
for (i <- 0 until downloadSlots) {
val dl = context.actorOf(Props[Downloader], s"dl$i")
downloaders.enqueue(dl)
}
}
override val supervisorStrategy =
OneForOneStrategy(
maxNrOfRetries = 20, withinTimeRange = 2 seconds
) {
case fnf: java.io.FileNotFoundException =>
log.info(s"Resource could not be found: $fnf")
workItems.remove(sender)
downloaders.enqueue(sender)
Resume // 忽略异常并恢复角色
case _ =>
Escalate //消息抛出同样的异常来停止自己。
}
override def postStop(): Unit = log.warning("DownloadManager Stop!")
}
object start extends App{
val downloadManager = ourSystem.actorOf(Props(classOf[DownloadManager], 4), "man")
downloadManager ! Download("http://www.w3.org/Addressing/URL/url-spec.txt", "url-spec.html")
downloadManager ! Download("https://www.baidu.com/", "baidu.html")
}
这个基于角色的下载管理器的简单实现既展现了如何通过向子角色代理任务来实现并发性,也展示了如何处理子角色的错误。
这种任务代理是非常重要的,既可用于将程序分解为更小的独立的组件,也可以实现高吞吐和可扩展性。
对用不同角色实现的独立组件而言,角色监管是一种基本的异常处理机制。