【问题标题】:Resolving Akka futures from ask in the event of a failure在失败的情况下从询问中解决 Akka 期货
【发布时间】:2015-06-29 22:15:23
【问题描述】:

我正在使用 Spray 应用程序中的 ask 模式调用 Actor,并将结果作为 HTTP 响应返回。我将参与者的失败映射到自定义错误代码。

val authActor = context.actorOf(Props[AuthenticationActor])

callService((authActor ? TokenAuthenticationRequest(token)).mapTo[LoggedInUser]) { user =>
  complete(StatusCodes.OK, user)
}

def callService[T](f: => Future[T])(cb: T => RequestContext => Unit) = {
 onComplete(f) {
  case Success(value: T) => cb(value)
  case Failure(ex: ServiceException) => complete(ex.statusCode, ex.errorMessage)
  case e => complete(StatusCodes.InternalServerError, "Unable to complete the request. Please try again later.")
  //In reality this returns a custom error object.
 }
}

当 authActor 发送失败时,这可以正常工作,但如果 authActor 抛出异常,则在请求超时完成之前不会发生任何事情。例如:

override def receive: Receive = {
  case _ => throw new ServiceException(ErrorCodes.AuthenticationFailed, "No valid session was found for that token")
}

我知道 Akka 文档是这么说的

要完成带有异常的未来,您需要向发件人发送失败消息。 当参与者在处理消息时抛出异常时,这不会自动完成

但是考虑到我在 Spray 路由 Actor 和服务 Actor 之间使用了很多接口,我宁愿不使用 try/catch 包装每个子 Actor 的接收部分。有没有更好的方法来实现子actor中异常的自动处理,并在发生异常时立即解决future?

编辑:这是我目前的解决方案。但是,对每个儿童演员都这样做是相当麻烦的。

override def receive: Receive = {
case default =>
  try {
    default match {
      case _ => throw new ServiceException("")//Actual code would go here
    }
  }
  catch {
    case se: ServiceException =>
      logger.error("Service error raised:", se)
      sender ! Failure(se)
    case ex: Exception =>
      sender ! Failure(ex)
      throw ex
  }
}

这样,如果它是一个预期的错误(即 ServiceException),它会通过创建一个失败来处理。如果它是意外的,它会立即返回一个失败,以便解决未来,但随后抛出异常,以便仍然可以由 SupervisorStrategy 处理。

【问题讨论】:

  • 嗯...在抛出异常之前发送失败消息。
  • 那是我不想做的 - 我说我宁愿不使用 try/catch 包装每个子 actor 的接收部分。这是一个玩具示例,我完全有可能无法控制抛出异常的位置。
  • 嗯......你知道......弹性分布式系统的基本途径之一是“明确错误”。想想可能发生的各种错误……让它们明确。如果你能有“TypeSafe”错误……那就更好了。
  • 库代码中总是存在潜在的未捕获异常。除非您知道异常是什么,或者对异常进行全面捕获,否则您无法使用失败使它们显式,这很糟糕。当然,我可以明确地因预期错误而失败,但这对我没有多大帮助。
  • 嗯...编写弹性系统就是为异常做好准备。是的,总会有一些“意外”的例外。但是你知道......既然你“必须”决定系统在这些异常情况下的行为......你“需要”为它们仔细考虑和计划。这是可靠地预测系统行为的唯一方法。任何“意外”异常都是错误,因此是提高系统可靠性的机会。

标签: scala akka spray


【解决方案1】:

如果您想要一种在发生意外异常时自动将响应发送回发送者的方法,那么这样的方法可能对您有用:

trait FailurePropatingActor extends Actor{
  override def preRestart(reason:Throwable, message:Option[Any]){
    super.preRestart(reason, message)
    sender() ! Status.Failure(reason)
  }
}

我们覆盖preRestart 并将失败作为Status.Failure 传播回发送者,这将导致上游Future 失败。此外,在这里打电话给super.preRestart 也很重要,因为这就是孩子停止的地方。在演员中使用它看起来像这样:

case class GetElement(list:List[Int], index:Int)
class MySimpleActor extends FailurePropatingActor {  
  def receive = {
    case GetElement(list, i) =>
      val result = list(i)
      sender() ! result
  }  
}

如果我要这样调用这个演员的一个实例:

import akka.pattern.ask
import concurrent.duration._

val system = ActorSystem("test")
import system.dispatcher
implicit val timeout = Timeout(2 seconds)
val ref = system.actorOf(Props[MySimpleActor])
val fut = ref ? GetElement(List(1,2,3), 6)

fut onComplete{
  case util.Success(result) => 
    println(s"success: $result")

  case util.Failure(ex) => 
    println(s"FAIL: ${ex.getMessage}")
    ex.printStackTrace()    
}     

然后它会正确地击中我的Failure 块。现在,当Futures 不涉及扩展该特征的参与者时,该基本特征中的代码运行良好,就像这里的简单参与者一样。但是如果您使用Futures,那么您需要小心,因为Future 中发生的异常不会导致actor 重新启动,而且在preRestart 中,对sender() 的调用不会返回正确的ref 因为演员已经进入下一条消息。像这样的演员表明了这个问题:

class MyBadFutureUsingActor extends FailurePropatingActor{
  import context.dispatcher

  def receive = {
    case GetElement(list, i) => 
      val orig = sender()
      val fut = Future{
        val result = list(i)
        orig ! result
      }      
  } 
}

如果我们在之前的测试代码中使用这个actor,我们总是会在失败的情况下得到一个超时。为了缓解这种情况,您需要将期货的结果通过管道传回发送者,如下所示:

class MyGoodFutureUsingActor extends FailurePropatingActor{
  import context.dispatcher
  import akka.pattern.pipe

  def receive = {
    case GetElement(list, i) => 
      val fut = Future{
        list(i)
      }

      fut pipeTo sender()
  } 
}

在这种特殊情况下,actor 本身不会重新启动,因为它没有遇到未捕获的异常。现在,如果您的 actor 需要在未来进行一些额外的处理,您可以通过管道返回 self 并在收到 Status.Failure 时显式失败:

class MyGoodFutureUsingActor extends FailurePropatingActor{
  import context.dispatcher
  import akka.pattern.pipe

  def receive = {
    case GetElement(list, i) => 
      val fut = Future{
        list(i)
      }

      fut.to(self, sender())

    case d:Double =>
      sender() ! d * 2

    case Status.Failure(ex) =>
      throw ex
  } 
}

如果这种行为变得普遍,您可以将其提供给任何需要它的参与者,如下所示:

trait StatusFailureHandling{ me:Actor =>
  def failureHandling:Receive = {
    case Status.Failure(ex) =>
      throw ex      
  }
}

class MyGoodFutureUsingActor extends FailurePropatingActor with StatusFailureHandling{
  import context.dispatcher
  import akka.pattern.pipe

  def receive = myReceive orElse failureHandling

  def myReceive:Receive = {
    case GetElement(list, i) => 
      val fut = Future{
        list(i)
      }

      fut.to(self, sender())

    case d:Double =>
      sender() ! d * 2        
  } 
}  

【讨论】:

    猜你喜欢
    • 2015-07-07
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-07
    • 2020-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多