Actor模型与外部系统的异步通信

时间:2014-01-05 07:43:13

标签: java asynchronous akka actor

在使用 AKKA Java 应用程序中,我想使用<与外部系统 异步进行交互< strong>基于TCP IP的JSON 。要求&amp;响应与发出请求的应用程序提供的ID 相关。外部系统来自第三方,因此我们将其视为黑盒子(只有界面定义明确)。

例如,假设我调用外部系统来检查某个人的帐户余额。请求看起来像:

{id=1234, account_name: "John Doe", question: "accountbalance"}

相应的响应将在几秒钟后(异步)到达,并且看起来像:

{id=1234, answer: "$42.87"}

一秒钟内会有数千个这样的请求。我的问题:

  1. 是否可以以真正的异步方式在AKKA中执行此操作。我对AKKA的了解是说没有共享变量等等。所以在这种情况下,我们如何跟踪请求并将响应与正确的请求相关联?并且这样做可以维持10-50K TPS。
  2. 感谢。

1 个答案:

答案 0 :(得分:2)

我认为可以设置Akka来处理你在这里要做的事情。我不能说达到你想要的吞吐量,因为它将依赖于其他东西(比如核心等......),但我可以为你提供一个非常高级的方法来关联请求和响应。我不会涵盖TCP部分,因为它与正确的Akka设计无关(我会将该部分留给您)

我从单个实例actor开始,可能称为QuestionMaster,其中所有对此外部系统的请求都被路由。在这里,我将启动另一个actor的新实例,可能称为QuestionAsker,并将该actor上的name设置为请求的id。这将允许您查找正确的actor以在稍后返回时处理答案。然后,我会将消息从QuestionMaster转发到新的QuestionAsker实例。

然后QuestionAsker实例可以执行准备TCP调用所需的任何操作,然后调用另一个处理低级TCP的Actor(可能使用Netty,其中有一个Channel作为其内部状态)。然后,QuestionAsker应存储sender,以便它可以响应正确的调用者,然后调用setReceiveTimeout来处理答案未及时返回的情况。如果达到超时,我会将错误消息发送回先前存储的sender,然后停止此QuestionAsker实例。

当TCP actor从远程系统获得响应时,它可以将消息发送回QuestionMaster,表明它得到了响应。该消息将包含响应的id。然后,QuestionMaster将使用context.child(requestId)来查找等待该响应的QuestionAsker实例。如果它解析为actor实例,它会将该消息转发给该actor。从那里开始,QuestionAsker可以做任何准备响应所需的操作,然后回复原始sender,然后自行停止。

同样,这是非常高的水平,但这是使用Akka处理外部系统的请求/响应范例的一种可能方法,其中响应将异步进入并且需要与原始请求相关联。

该流的代码(不包括tcp actor)如下所示:

case class AskQuestion(id:Long, accountName:String, question:String)
case class QuestionAnswer(id:Long, answer:String)
case class QuestionTimeout(id:Long)

class QuestionMaster(tcpHandler:ActorRef) extends Actor{
  def receive = {
    case ask:AskQuestion => 
      val asker = context.actorOf(Props(classOf[QuestionAsker], tcpHandler), ask.id.toString)
      asker.forward(ask)

    case answer:QuestionAnswer =>
      val asker = context.child(answer.id.toString)
      asker match{
        case Some(ref) => ref.forward(answer)                    
        case None => 
          //handle situation where there is no actor to handle the answer
      }
  }
}

class QuestionAsker(tcpHandler:ActorRef) extends Actor{
  import context._
  import concurrent.duration._

  def receive = {
    case ask:AskQuestion =>
      //Do whatever other prep work here if any then send to tcp actor
      tcpHandler ! ask
      setReceiveTimeout(5 seconds)
      become(waitingForAnswer(ask, sender))
  }

  def waitingForAnswer(ask:AskQuestion, caller:ActorRef):Receive = {
    case ReceiveTimeout =>
      caller ! QuestionTimeout(ask.id)
      context stop self
    case answer:QuestionAnswer =>
      //do any additional work to prep response and then respond
      caller ! answer
      context stop self
  }
}