当一个actor包含异步方法时,这将导致Ask time out exception

时间:2017-11-21 03:52:50

标签: scala asynchronous akka actor

我使用ask模式在名为fun-F的函数中向一个名为actor-A的actor发送请求。 actor会以异步方式获取另一个系统生成的ID,当这个完成时,我会将包含此ID的消息转发给另一个名为actor-B的actor,actor-B将执行一些DB操作然后发送回数据库操作导致向发件人发送消息,因为在我的情况下我使用正向模式,所以actor-B将发件人识别为fun-F,akka会给fun-F一个临时的actor名称,所以返回的值应该是被送到临时演员。

我的问题是:

如果我使用sync-method从另一个系统获取ID,然后将此消息转发给actor-B,在actor-B的DB操作之后,结果可以传递给fun-F的值,并且有趣-F被akka框架运行时定义为临时actor Actor [akka:// ai-feedback-service / temp / $ b]。

如果我使用async-method从另一个系统获取ID,当它完成时,我将在另一个回调线程中的oncompleted {}代码块中转发消息,成功处理actor-B中的DB操作,但是返回的值不能传递给fun-F中定义的值,在这种情况下,fun-F被akka框架运行时定义为Actor [akka:// ai-feedback-service / deadLetters]。所以actor-B迷失了方向,不知道如何返回或者这个消息应该传递到哪里,这将导致我的日志中出现一个Ask time out exception抛出。

我该如何处理这个问题?或者我怎样才能避免这封死信要求超时?

以下是我的代码:

// this is the so-called fun-F [createFeedback]
def createFeedback(query: String, 
                   response: String, 
                   userId: Long, 
                   userAgent: String, 
                   requestId: Long, 
                   errType: Short, 
                   memo: String): Future[java.lang.Long] = {
    val ticket = Ticket(userId,
                        requestId,
                        query,
                        response,
                        errType,
                        userAgent,
                        memo)
    val issueId = (jiraActor ? CreateJiraTicketSignal(ticket))
                  .mapTo[CreateFeedbackResponseSignal].map{ r =>
        r.issueId.asInstanceOf[java.lang.Long]
    }
    issueId
}


//this is the so-called actor-A [jiraActor]
//receive method are run in its parent actor for some authorization
//in this actor only override the handleActorMsg method to deal msg
override def handleActorMsg(msg: ActorMsgSignal): Unit = {
    msg match {
        case s:CreateJiraTicketSignal =>
            val issueId = createIssue(cookieCache.cookieContext.flag,
                                     cookieCache.cookieContext.cookie,
                                     s.ticket)
            println(s">> ${sender()} before map $issueId")
            issueId.map{
                case(id:Long) =>
                    println(s">> again++issueId = $id ${id.getClass}")
                    println(s">>> $self / ${sender()}")
                    println("again ++ jira action finished")
                    dbActor.forward(CreateFeedbackSignal(id,s.ticket))
                case(message:String) if(!s.retry) =>
                    self ! CreateJiraTicketSignal(s.ticket,true)
                case(message:String) if(s.retry) =>
                    log.error("cannot create ticket :" + message)
            }
            println(s">> after map $issueId")
}


//this is the so-called actor-B [dbActor]
override def receive: Receive = {
    case CreateFeedbackSignal(issueId:Long, ticket:Ticket) =>
        val timestampTicks = System.currentTimeMillis()
        val description: String = Json.obj("question" -> ticket.query, 
                                          "answer" -> ticket.response)
                                          .toString()
        dao.createFeedback(issueId,
                           ticket.usrId.toString,
                           description,
                           FeedbackStatus.Open.getValue
                                .asInstanceOf[Byte],
                           new Timestamp(timestampTicks),
                           new Timestamp(timestampTicks),
                           ticket.usrAgent,
                           ticket.errType,
                           ticket.memo)

        println(s">> sender = ${sender()}")
        sender() ! (CreateFeedbackResponseSignal(issueId))
        println("db issue id is " + issueId)
        println("db action finished")
}

1 个答案:

答案 0 :(得分:0)

要避免死信问题,请执行以下操作:

  1. 对于每个请求,请使用您可以与请求的最终目标关联的标识符(可能是requestId)。也就是说,将您传递给requestId方法的createFeedback绑定到该方法的调用方(ActorRef),然后将此ID传递到您的消息链中。您可以使用地图来保存这些关联。

    • 更改CreateFeedbackResponseSignal(issueId)以包含requestId课程中的TicketCreateFeedbackResponseSignal(requestId, issueId)
  2. 在处理演员内部Future的异步结果时,pipe Futureself的结果,而不是使用回调。

    • 使用此方法,createIssue的结果将在结果可用时发送至jiraActorjiraActor然后将结果发送到dbActor
    • jiraActor将是sender中的dbActor。当jiraActor收到dbActor的结果时,jiraActor可以在其内部地图中查找对目标的引用。
  3. 下面是一个模仿您的用例并可在ScalaFiddle中运行的简单示例:

    import akka.actor._
    import akka.pattern.{ask, pipe}
    import akka.util.Timeout
    
    import language.postfixOps
    
    import scala.concurrent._
    import scala.concurrent.duration._
    
    case class Signal(requestId: Long)
    case class ResponseSignal(requestId: Long, issueId: Long)
    
    object ActorA {
      def props(actorB: ActorRef) = Props(new ActorA(actorB))
    }
    
    class ActorA(dbActor: ActorRef) extends Actor {
      import context.dispatcher
    
      var targets: Map[Long, ActorRef] = Map.empty
    
      def receive = {
        case Signal(requestId) =>
          val s = sender
          targets = targets + (requestId -> s)
          createIssue(requestId).mapTo[Tuple2[Long, Long]].pipeTo(self) // <-- use pipeTo
        case ids: Tuple2[Long, Long] =>
          println(s"Sending $ids to dbActor")
          dbActor ! ids
        case r: ResponseSignal =>
          println(s"Received from dbActor: $r")
          val target = targets.get(r.requestId)
          println(s"In actorA, sending to: $target")
          target.foreach(_ ! r)
          targets = targets - r.requestId
      }
    }
    
    class DbActor extends Actor {
      def receive = {
        case (requestId: Long, issueId: Long) =>
          val response = ResponseSignal(requestId, issueId)
          println(s"In dbActor, sending $response to $sender")
          sender ! response
      }
    }
    
    val system = ActorSystem("jiratest")
    implicit val ec = system.dispatcher
    
    val dbActor = system.actorOf(Props[DbActor])
    val jiraActor = system.actorOf(Props(new ActorA(dbActor)))
    
    val requestId = 2L
    
    def createIssue(requestId: Long): Future[(Long, Long)] = {
      println(s"Creating an issue ID for requestId[$requestId]")
      Future((requestId, 99L))
    }
    
    def createFeedback(): Future[Long] = {
      implicit val timeout = Timeout(5.seconds)
      val res = (jiraActor ? Signal(requestId)).mapTo[ResponseSignal]
      res.map(_.issueId)
    }
    
    createFeedback().onComplete { x =>
      println(s"Done: $x")
    }
    

    在ScalaFiddle中运行上面的代码会产生以下输出:

    Creating an issue ID for requestId[2]
    Sending (2,99) to dbActor
    In dbActor, sending ResponseSignal(2,99) to Actor[akka://jiratest/user/$b#-710097339]
    Received from dbActor: ResponseSignal(2,99)
    In actorA, sending to: Some(Actor[akka://jiratest/temp/$a])
    Done: Success(99)