Java 参与者模型与外部系统的异步通信

Java 参与者模型与外部系统的异步通信,java,asynchronous,akka,actor,Java,Asynchronous,Akka,Actor,在我的Java应用程序中使用AKKA,我希望通过TCP-IP使用JSON与外部系统进行异步交互。请求和响应通过发出请求的应用程序提供的ID进行关联。外部系统来自第三方,因此我们将其视为黑盒(只有接口定义良好) 例如,假设我调用外部系统来检查某个人的帐户余额。请求将类似于: {id=1234, account_name: "John Doe", question: "accountbalance"} 相应的响应将在几秒钟后(异步)到达,如下所示: {id=1234, answer: "$42.8

在我的Java应用程序中使用AKKA,我希望通过TCP-IP使用JSON与外部系统进行异步交互。请求和响应通过发出请求的应用程序提供的ID进行关联。外部系统来自第三方,因此我们将其视为黑盒(只有接口定义良好)

例如,假设我调用外部系统来检查某个人的帐户余额。请求将类似于:

{id=1234, account_name: "John Doe", question: "accountbalance"}
相应的响应将在几秒钟后(异步)到达,如下所示:

{id=1234, answer: "$42.87"}
每秒将有数千个此类请求。我的问题是:

  • 是否可以在AKKA中以真正的异步方式执行此操作。我对AKKA的了解表明,不存在共享变量等。因此,在这种情况下,我们如何跟踪请求并将响应与正确的请求关联起来?并以此方式维持10-5万TPS

  • 谢谢。

    我认为Akka可以被设置来处理您在这里试图做的事情。我不能说要达到您所期望的吞吐量,因为这将取决于其他因素(如内核等),但我可以为您提供一种非常高级别的方法来关联请求和响应。我不会涉及TCP部分,因为这与正确的Akka设计无关(我将把这部分留给您)

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

    然后,
    QuestionAsker
    实例可以做它需要做的任何事情来准备TCP调用,然后调用另一个处理低级TCP的参与者(可能使用Netty,其中它有一个通道作为它的内部状态)。然后,
    QuestionAsker
    应该存储
    发送者
    ,这样它就可以响应正确的呼叫者,然后调用
    setReceiveTimeout
    来处理答案没有及时返回的情况。如果达到超时,我会将错误消息发送回先前存储的
    发送者
    ,然后停止此
    提问者
    实例

    当TCP参与者从远程系统获得响应时,它可以向
    QuestionMaster
    发回一条消息,表明它得到了响应。该消息将包含响应的id。然后,
    QuestionMaster
    将使用
    context.child(requestId)
    查找等待该响应的
    QuestionAsker
    实例。如果它解析为一个actor实例,它将把该消息转发给该actor。从那里,
    QuestionAsker
    可以做任何它需要做的事情来准备回复,然后回复原始的
    发送者
    ,然后停止自己

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

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

    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
      }
    }