使用映射函数中的actor时超时和崩溃

时间:2017-06-30 07:16:36

标签: scala akka actor

我想做这样的事情:

对于集合中的每个项目,在等待某个超时间隔的响应时询问一个actor并阻塞,如果抛出超时异常,我想继续下一个项目。

这是代码模式:

createUser

演员本身正在召唤其他演员可能需要比我3秒钟更长的时间。

这并没有按预期工作:在第一个项目超时后,整个事情崩溃并停止。有一些死信通知,我想这是因为当我的演员调用的演员完成时,原始发件人无效(花了3秒多)。所以我的问题是如何告诉它忘记超时项目并继续其余的事情,好像什么也没发生?

2 个答案:

答案 0 :(得分:1)

@stefanobaghino是对的。如果未来包含异常,请参阅文档中的here,然后 Await.result 抛出以便可以正确处理。

这里你匹配未来的失败案例,但你没有从中恢复。更好的方法就像跟随 -

collection.foreach { item =>
    val future = (actor ? Request(msg = item)).mapTo[Response]
    future.recover {
        case ex: Exception =>
            // log ex
            Response(ex.message) // some other object of type Response
    }
    val response = Await.result(future, 3 seconds) 
    // use response here
}

在阅读@Dimitri的回答之后,我尝试以毫秒为单位记录时间戳,以查看它在整个过程中导致滞后的原因,并且我发现了相当奇怪的行为。我观察到,只要有死信,即使开始处理下一条消息给演员,也会有很大的滞后。不知道为什么会这样。下面是我试图检查它的代码 -

package com.lightbend.akka.sample

import akka.actor.{ Actor, ActorLogging, ActorRef, ActorSystem, Props }
import akka.pattern.{ ask, pipe, AskTimeoutException }

import scala.concurrent.Await
import scala.concurrent.duration._
import scala.io.StdIn
import scala.util.{ Try, Success, Failure }

import scala.concurrent.ExecutionContext.Implicits.global

import java.util.concurrent.TimeoutException

object AkkaQuickStart {
  class NumberActor extends Actor {

    override def receive: Receive = {
      case (num: Int, startAt: Long) =>
        println("B " +  num.toString + " : " + System.currentTimeMillis().toString + " : " + (System.currentTimeMillis() - startAt).toString)
        Thread.sleep(500 * num)
        sender() ! "OK"
    }
  }

  def main(args: Array[String]): Unit = {
    implicit val timeout: akka.util.Timeout = 1 seconds
    val numActor = ActorSystem("system").actorOf(Props(new NumberActor()))
    val range = (1 to 5) ++ (4 to 1 by -1)
    println(range)
    def lag(implicit startAt: Long): String = (System.currentTimeMillis() - startAt).toString

    range.map { r =>
      implicit val startAt = System.currentTimeMillis()
      println("A " +  r.toString + " : " + System.currentTimeMillis().toString + " : " + lag)
      val future = (numActor ? (r, startAt))
        .recover {
            case ex: AskTimeoutException =>
                println("E " +  r.toString + " : " + System.currentTimeMillis().toString + " : " + lag)
                "Ask timeout"
        }
        .mapTo[String]

      future.onComplete{
        case Success(reply) => 
            println("C " +  r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : success " + reply)
        case Failure(reply) => 
            println("C " +  r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : failure")
      }

      Try(Await.result(future, 1 seconds)) match {
        case Success(reply) => 
            println("D " +  r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : " + reply)
        case Failure(ex) => 
            println("D " +  r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : Await timeout ")
      }
    }
  }
}

我尝试了Ask超时和Await超时的不同组合,发现在迭代结束时发送的actor消息的开始处理方面存在滞后 -

Ask timeout = 1 Await Timeout = 1 => 3000 - 4500毫秒导致死信

Ask timeout = 1 Await Timeout = 3 => 3000 - 4500毫秒导致死信

询问超时= 3等待超时= 1 => 3000 - 4500毫秒导致死信

询问超时= 3等待超时= 3 => 0 - 500 ms不会导致死信

我不确定但猜测是调度员在处理死信时需要时间,因此无法开始处理我们的Actor的消息。可能是一些更有经验的人可以解释它。

答案 1 :(得分:0)

@stefanobaghino @Tarun感谢您的帮助,我想我现在明白了。

所以事情是有2次超时可能导致异常:

  • 如果我们必须等待演员响应的时间,那么Ask(?)超时会引发akka.pattern.AskTimeoutException

  • 如果我们不等待足够长的时间来完成,Await.result会引发java.util.concurrent.TimeoutException

这两个都可能导致整个事情崩溃。对于第一个,正如您所提到的,我们可以添加recover来返回一些默认值。对于第二个,我们也应该捕获并处理异常。

在以下示例中更改两个超时并删除recover / Try时,您可以看到不同的行为:

object Example {
  class NumberActor extends Actor {

    override def receive: Receive = {
      case num: Int =>
        Thread.sleep(250 * num)
        sender() ! "OK"
    }
  }

  def main(): Unit = {
    implicit val timeout: akka.util.Timeout = 1 seconds
    val numActor = ActorSystem("system").actorOf(Props(new NumberActor()))
    val range = (1 to 5) ++ (4 to 1 by -1)
    println(range)

    range.map { r =>
      val future = (numActor ? r)
        .recover { case ex: TimeoutException => "FAIL" }
        .mapTo[String]

      Try(Await.result(future, 1 seconds)) match {
        case Success(reply) => println(reply)
        case Failure(ex) => println(ex)
      }
    }
  }
}