在Akka等待多个结果

时间:2014-03-31 19:53:17

标签: akka actor nonblocking

等待Akka中多个演员的结果的正确方法是什么?

Principles of Reactive Programming Coursera课程练习了一个复制的键值存储。在不进入赋值细节的情况下,它需要等待多个actor的确认才能指示复制完成。

我使用包含未完成请求的可变地图实现了作业,但我觉得这个解决方案有一种难闻的气味'。我希望有更好的方法来实现看似常见的场景。

努力维护课程。通过隐瞒我对练习的解决方案来尊重代码,我有一个描述类似问题的抽象用例。

发票行项目需要计算其纳税义务。纳税义务是跨多个税务机关(例如,联邦,州,警区)应用于项目的所有税收的组合。如果每个征税机构都是能够确定订单项税务责任的行为者,则该订单项需要所有参与者报告才能继续报告整体纳税义务。在Akka中完成这种情况的最佳/正确方法是什么?

4 个答案:

答案 0 :(得分:3)

以下是我认为您正在寻找的简化示例。它显示了像演员这样的大师如何产生一些童工,然后等待他们的所有响应,处理等待结果可能发生超时的情况。该解决方案显示了如何等待初始请求,然后在等待响应时切换到新的接收函数。它还说明了如何将状态传播到等待接收函数中,以避免在实例级别具有显式可变状态。

object TaxCalculator {
  sealed trait TaxType
  case object StateTax extends TaxType
  case object FederalTax extends TaxType
  case object PoliceDistrictTax extends TaxType
  val AllTaxTypes:Set[TaxType] = Set(StateTax, FederalTax, PoliceDistrictTax)

  case class GetTaxAmount(grossEarnings:Double)
  case class TaxResult(taxType:TaxType, amount:Double)  

  case class TotalTaxResult(taxAmount:Double)
  case object TaxCalculationTimeout
}

class TaxCalculator extends Actor{
 import TaxCalculator._
 import context._
 import concurrent.duration._

  def receive =  waitingForRequest

  def waitingForRequest:Receive = {
    case gta:GetTaxAmount =>
      val children = AllTaxTypes map (tt => actorOf(propsFor(tt)))
      children foreach (_ ! gta)
      setReceiveTimeout(2 seconds)
      become(waitingForResponses(sender, AllTaxTypes))
  }

  def waitingForResponses(respondTo:ActorRef, expectedTypes:Set[TaxType], taxes:Map[TaxType, Double] = Map.empty):Receive = {
    case TaxResult(tt, amount) =>
      val newTaxes = taxes ++ Map(tt -> amount)
      if (newTaxes.keySet == expectedTypes){
        respondTo ! TotalTaxResult(newTaxes.values.foldLeft(0.0)(_+_))
        context stop self
      }
      else{
        become(waitingForResponses(respondTo, expectedTypes, newTaxes))
      }

    case ReceiveTimeout =>
      respondTo ! TaxCalculationTimeout
      context stop self
  }

  def propsFor(taxType:TaxType) = taxType match{
    case StateTax => Props[StateTaxCalculator]
    case FederalTax => Props[FederalTaxCalculator]
    case PoliceDistrictTax => Props[PoliceDistrictTaxCalculator]
  }  
}

trait TaxCalculatingActor extends Actor{  
  import TaxCalculator._
  val taxType:TaxType
  val percentage:Double

  def receive = {
    case GetTaxAmount(earnings) => 
      val tax = earnings * percentage
      sender ! TaxResult(taxType, tax)
  }
}

class FederalTaxCalculator extends TaxCalculatingActor{
  val taxType = TaxCalculator.FederalTax
  val percentage = 0.20
}

class StateTaxCalculator extends TaxCalculatingActor{
  val taxType = TaxCalculator.StateTax
  val percentage = 0.10
}

class PoliceDistrictTaxCalculator extends TaxCalculatingActor{
  val taxType = TaxCalculator.PoliceDistrictTax
  val percentage = 0.05
}

然后您可以使用以下代码对其进行测试:

import TaxCalculator._
import akka.pattern.ask
import concurrent.duration._
implicit val timeout = Timeout(5 seconds)

val system = ActorSystem("taxes")
import system._
val cal = system.actorOf(Props[TaxCalculator])
val fut = cal ? GetTaxAmount(1000.00)
fut onComplete{
  case util.Success(TotalTaxResult(amount)) =>
    println(s"Got tax total of $amount")
  case util.Success(TaxCalculationTimeout) =>
    println("Got timeout calculating tax")
  case util.Failure(ex) => 
    println(s"Got exception calculating tax: ${ex.getMessage}")
}

答案 1 :(得分:1)

这是Akka中一个非常常见的问题。你有多个演员可以为你完成这项工作,你需要将它们结合起来。

Jammie Allen在他的书#34; Effective Akka" (它是关于从各种类型的帐户获得银行帐户余额)是你产生一个演员,它将产生多个将完成这项工作的演员(例如计算你的税)。它将等待所有人回答。

您不应该使用ask,而是tell

当您生成多个actor(例如FederalTaxactor,StateTaxActor ...)时,您会向他们发送一条消息,其中包含他们需要处理的数据。然后你知道需要收集多少答案。每次回复都会检查是否所有响应都存在。如果不是你等。

问题是,如果任何演员失败,你可能会永远等待。因此,您需要为自己安排超时消息。如果不是所有答案都存在,则表示操作未成功完成。

Akka有一个特殊的工具,可以为你自己安排超时,作为一个好帮手。

答案 2 :(得分:1)

正如之前的回答所暗示的那样,你可能会发现在这种情况下撰写期货的能力很有用 - 我所知道的期货(和承诺,有些相关)的最佳描述在这里:http://docs.scala-lang.org/overviews/core/futures.html

这可能有助于解释可组合的未来可以满足需求的方式,可能比演员更干净,或者与演员结合。

答案 3 :(得分:0)

我在这种情况下使用溪流的经验很好。

我使用ActorRefs启动一个Source,然后使用mapAsync向refs发送ask并收集对Seq的响应。

val f = Source(workers).mapAsync(USED_THREAD_COUNT)(actorRef=>(actorRef ? QueryState).mapTo[StateResponse])).runWith(Sink.seq)
f onComplete { responses => 
    // validate and work with responses
}

我希望它会对你有所帮助。