Scala Akka超时异常,但消息实际已发送

Scala Akka超时异常,但消息实际已发送,scala,playframework,akka,akka-stream,alpakka,Scala,Playframework,Akka,Akka Stream,Alpakka,我正在使用Scala 2.13堆栈,使用以下技术: 玩!框架2.8 akka类型2.6.3 卡夫卡2.0.3 Akka流作业从Kafka读取事件,要求参与者计算某些内容,并根据给定的响应生成新事件返回到Kafka 问题在于,只有当邮箱至少收集了两封邮件且收到的每封邮件只有一封时,QuestionActor(以下)才会使用使用ask模式发送的邮件 奇怪的行为是: t1 t2 t3 然后,我试图理解为什么我会观察到这种行为,以及我做错了什么 阿克卡河-卡夫卡管道为: Consumer .pl

我正在使用Scala 2.13堆栈,使用以下技术:

  • 玩!框架2.8
  • akka类型2.6.3
  • 卡夫卡2.0.3
Akka流作业从Kafka读取事件,要求参与者计算某些内容,并根据给定的响应生成新事件返回到Kafka

问题在于,只有当邮箱至少收集了两封邮件且收到的每封邮件只有一封时,
QuestionActor
(以下)才会使用使用ask模式发送的邮件

奇怪的行为是:

t1

t2

t3

然后,我试图理解为什么我会观察到这种行为,以及我做错了什么

阿克卡河-卡夫卡管道为:

Consumer
  .plainSource(consumerSettings, subscription)
  .map(DeserializeEvents.fromService)
  .filter(_.eventType == classOf[Item].getName)
  .via(askFlowExplicit)
  .withAttributes(ActorAttributes.supervisionStrategy(decider()))
  .map(
    response =>
      new ProducerRecord[String, OutputItem](
        topics,
        OutputItem(response.getClass.getName, response)
      )
  )
  .log("Kafka Pipeline")
  .runWith(Producer.plainSink(producerSettings))
决策者是一种监督策略,在
序列化
超时
异常时恢复工作
askFlowExplicit
向外部参与者声明了一个ask请求,因此,我遇到了问题

val askFlowExplicit =
  ActorFlow.ask[OutputItem, Question, Answer](askTarget) {
    case (envelope, replyTo) =>
      val item = Serdes.deserialize[Item](envelope.payload)
      Question(item.trID, item.id, item.user, replyTo)
  }
管道开始运作了!应用程序引导

@Singleton
class ApplicationStart @Inject()(
    configuration: Configuration,
    questionActor: ActorRef[QuestionActor.Question]
) {
  private implicit val logger = Logger.apply(getClass)
  implicit val mat            = context
  AlpakkaPipeline.run(configuration, questionActor)
}
actor是属于同一actor系统的简单类型的actor,现在它只是将来自流的请求转发给另一个服务

class QuestionActor(
    configuration: Configuration,
    context: ActorContext[Question],
    itemService: ItemService
) extends AbstractBehavior[Question](context) {
  import QuestionActor._

  implicit val ec: ExecutionContextExecutor = context.executionContext
  private implicit val timeout: Timeout = ...

  override def onMessage(msg: Question): Behavior[Question] = Behaviors.receive[Question] {
    case (context, Question(trID, id, user, sender)) =>
      log.info(s"Question request for ${msg.trID}-${msg.id}. Processing.")
        itemService
          .action(id, user)
          .onComplete {
            case Success(result) if result.isEmpty =>
              log.info("Action executed")
              msg.replyTo ! NothingHappened(trID, id)
            case Failure(e) =>
              log.error("Action failed.", e)
              msg.replyTo ! FailedAction(trID, id, user, e.getMessage)
          }
      Behaviors.same
  }
}

object QuestionActor {
  final case class Question(
      trID: String,
      id: Int,
      user: Option[UUID],
      replyTo: ActorRef[Answer]
  )

  def apply(itemService: ItemService, configuration: Configuration): Behavior[Question] =
    Behaviors.setup { context =>
      context.setLoggerName(classOf[QuestionActor])
      implicit val log: Logger = context.log
      new QuestionActor(configuration, context)
    }
}
它是使用运行时DI和Play构建的

class BootstrapModule(environment: Environment, configuration: Configuration)
    extends AbstractModule
    with AkkaGuiceSupport {

  override def configure(): Unit = {
    bind(new TypeLiteral[ActorRef[CloneWithSender]]() {})
      .toProvider(classOf[QuestionActorProvider])
      .asEagerSingleton()
    bind(classOf[ApplicationStart]).asEagerSingleton()
  }
}

private class Question @Inject()(
    actorSystem: ActorSystem,
    itemService: ItemService,
    configuration: Configuration
) extends Provider[ActorRef[Question]] {
  def get(): ActorRef[Question] = {
    val behavior = QuestionActor(itemService, configuration)
    actorSystem.spawn(behavior, "question-actor")
  }
}
我试过的

  • 将dispatcher更改为
    QuestionActor
  • 将邮箱更改为
    QuestionActor
  • QuestionActor
  • 使用actor构造函数(向self)发送相同的消息,观察到相同的行为:另一条消息将触发actor使用前者,请求后者超时
我所没有的

  • 将调度程序更改为Akka流管道
在我看来,这是一个线程问题,但我不知道从这里去哪里。
非常感谢您的帮助。提前谢谢。

问题是您正在组合提供
onMessage
AbstractBehavior
,在那里您定义了一个新的
行为。接收[问题]
行为。你必须使用其中之一

删除
行为。按如下方式接收

覆盖def onMessage(msg:Question):行为[Question]={
log.info(s“对${msg.trID}-${msg.id}.Processing的问题请求”)
项目服务
.操作(id、用户)
.未完成{
如果result.isEmpty=>
log.info(“已执行的操作”)
msg.replyTo!未显示任何内容(trID,id)
案例失败(e)=>
log.error(“操作失败”,e)
msg.replyTo!FailedAction(trID,id,user,e.getMessage)
}
行为是一样的
}
}
AbstractBehavior.onMessage
是行为的实现。因此,您通过方法参数接收消息,您应该对其进行处理并返回一个新的
行为
行为。在您的情况下也是如此

但是,您不需要处理消息,而是使用
行为创建一个新的
行为
。接收
,并将未来的回调注册到原始的第一条消息。因此,当第二条消息到达时,您将看到log语句,这将触发新的行为


如果要使用FP样式定义,则必须仅使用
Behaviors.xxx
helper方法。如果选择OOP样式,则扩展
AbstractBehavior
。但你不应该两者兼而有之

问题在于,您正在组合提供
onMessage
AbstractBehavior
,在这里您定义了一个新的
行为。接收[问题]
行为。你必须使用其中之一

删除
行为。按如下方式接收

覆盖def onMessage(msg:Question):行为[Question]={
log.info(s“对${msg.trID}-${msg.id}.Processing的问题请求”)
项目服务
.操作(id、用户)
.未完成{
如果result.isEmpty=>
log.info(“已执行的操作”)
msg.replyTo!未显示任何内容(trID,id)
案例失败(e)=>
log.error(“操作失败”,e)
msg.replyTo!FailedAction(trID,id,user,e.getMessage)
}
行为是一样的
}
}
AbstractBehavior.onMessage
是行为的实现。因此,您通过方法参数接收消息,您应该对其进行处理并返回一个新的
行为
行为。在您的情况下也是如此

但是,您不需要处理消息,而是使用
行为创建一个新的
行为
。接收
,并将未来的回调注册到原始的第一条消息。因此,当第二条消息到达时,您将看到log语句,这将触发新的行为


如果要使用FP样式定义,则必须仅使用
Behaviors.xxx
helper方法。如果选择OOP样式,则扩展
AbstractBehavior
。但你不应该两者兼而有之

你能通过只向参与者发送消息而不将其集成到流中来重现问题吗?很难看出问题在哪里。但是您定义的上下文太多了。尽量不要
扩展AbstractBehavior[Question](context)
,避免将
context
作为构造函数参数传递,只需使用
behavies.setup+receiveMessage
。嗨,伊万,是的,我尝试从actor构造函数中向self发送相同的消息,我可以观察到相同的行为。(即使我不使用ask)。我更新了问题。将尝试避免
AbstractBehavior
尝试替换
msg.replyTo!没有出现(trID,id)
发送者!没有任何可能(trID,id)
只需向参与者发送消息而无需输入,就可以重现问题
@Singleton
class ApplicationStart @Inject()(
    configuration: Configuration,
    questionActor: ActorRef[QuestionActor.Question]
) {
  private implicit val logger = Logger.apply(getClass)
  implicit val mat            = context
  AlpakkaPipeline.run(configuration, questionActor)
}
class QuestionActor(
    configuration: Configuration,
    context: ActorContext[Question],
    itemService: ItemService
) extends AbstractBehavior[Question](context) {
  import QuestionActor._

  implicit val ec: ExecutionContextExecutor = context.executionContext
  private implicit val timeout: Timeout = ...

  override def onMessage(msg: Question): Behavior[Question] = Behaviors.receive[Question] {
    case (context, Question(trID, id, user, sender)) =>
      log.info(s"Question request for ${msg.trID}-${msg.id}. Processing.")
        itemService
          .action(id, user)
          .onComplete {
            case Success(result) if result.isEmpty =>
              log.info("Action executed")
              msg.replyTo ! NothingHappened(trID, id)
            case Failure(e) =>
              log.error("Action failed.", e)
              msg.replyTo ! FailedAction(trID, id, user, e.getMessage)
          }
      Behaviors.same
  }
}

object QuestionActor {
  final case class Question(
      trID: String,
      id: Int,
      user: Option[UUID],
      replyTo: ActorRef[Answer]
  )

  def apply(itemService: ItemService, configuration: Configuration): Behavior[Question] =
    Behaviors.setup { context =>
      context.setLoggerName(classOf[QuestionActor])
      implicit val log: Logger = context.log
      new QuestionActor(configuration, context)
    }
}
class BootstrapModule(environment: Environment, configuration: Configuration)
    extends AbstractModule
    with AkkaGuiceSupport {

  override def configure(): Unit = {
    bind(new TypeLiteral[ActorRef[CloneWithSender]]() {})
      .toProvider(classOf[QuestionActorProvider])
      .asEagerSingleton()
    bind(classOf[ApplicationStart]).asEagerSingleton()
  }
}

private class Question @Inject()(
    actorSystem: ActorSystem,
    itemService: ItemService,
    configuration: Configuration
) extends Provider[ActorRef[Question]] {
  def get(): ActorRef[Question] = {
    val behavior = QuestionActor(itemService, configuration)
    actorSystem.spawn(behavior, "question-actor")
  }
}