重试长期生活的akka​​演员的轻微例外情况

时间:2018-01-25 11:51:03

标签: scala akka actor akka-supervision akka-actor

我有一个在应用程序启动时创建的actor作为另一个actor的子节点,并且每天从父节点接收一条消息,以执行从某些SFTP服务器获取某些文件的操作。

现在,可能存在一些导致操作失败的次要临时连接异常。在这种情况下,需要重试。

但是可能存在抛出异常并且不会在重试时解决的情况(例如:未找到文件,某些配置不正确等)。

因此,在这种情况下,考虑到演员将在很长一段时间(每天一次)之后接收消息,这可能是一个适当的重试机制和监督策略。

在这种情况下,发送给actor的消息输入不错 - 它只是一个触发器。示例:

case object FileFetch

如果我在这样的父母中有一个监督策略,它将在没有重试的情况下重新启动每个次要/主要异常的失败子项。

override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = -1, withinTimeRange = Duration.inf) {
    case _: Exception                => Restart
}

我想拥有的是这样的:

override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = -1, withinTimeRange = Duration.inf) {
    case _: MinorException           => Retry same message 2, 3 times and then Restart
    case _: Exception                => Restart
}

1 个答案:

答案 0 :(得分:3)

如果发生异常,“重试”或重新发送消息是您必须自己实现的。来自documentation

  

如果在处理邮件时抛出异常(即从邮箱中取出并移交给当前行为),则此邮件将丢失。重要的是要了解它没有放回邮箱。因此,如果要重试处理消息,则需要通过捕获异常并重试流程来自行处理。确保您对重试次数设置了限制,因为您不希望系统活锁(因此消耗了大量的cpu周期而没有取得进展)。

如果您想在FileFetch的情况下向孩子重新发送MinorException消息而不重新启动孩子,那么您可以在孩子中捕获异常以避免触发监督策略。在try-catch块中,您可以向父节点发送消息,并让父节点跟踪重试次数(例如,如果您希望父节点制定某种退避策略,则可能在此消息中包含时间戳) 。在孩子身上:

def receive = {
  case FileFetch =>
    try {
      ...
    } catch {
      case m: MinorException =>
        val now = System.nanoTime
        context.parent ! MinorIncident(self, now)
    }
  case ...
} 

在父母:

override val supervisorStrategy =
  OneForOneStrategy(maxNrOfRetries = -1, withinTimeRange = Duration.Inf) {
    case _: Exception => Restart
  }

var numFetchRetries = 0

def receive = {
  case MinorIncident(fetcherRef, time) =>
    log.error(s"${fetcherRef} threw a MinorException at ${time}")
    if (numFetchRetries < 3) { // possibly use the time in the retry logic; e.g., a backoff
      numFetchRetries = numFetchRetries + 1
      fetcherRef ! FileFetch
    } else {
      numFetchRetries = 0
      context.stop(fetcherRef)
      ... // recreate the child
    }
  case SomeMsgFromChildThatFetchSucceeded =>
    numFetchRetries = 0
  case ...
}

或者,不是在子代中捕获异常,而是在Resume的情况下将主管策略设置为MinorException子进程,同时仍然让父进程处理消息重试逻辑:

override val supervisorStrategy =
  OneForOneStrategy(maxNrOfRetries = -1, withinTimeRange = Duration.Inf) {
    case m: MinorException =>
      val child = sender()
      val now = System.nanoTime
      self ! MinorIncident(child, now)
      Resume
    case _: Exception => Restart
  }