如何在Akka actor中等待文件上传流完成

时间:2018-09-04 05:04:52

标签: scala akka akka-stream akka-http

最近,我开始使用Akka,并且正在使用Akka HTTP通过Akka HTTP创建REST API,以上传文件。该文件可以包含数百万条记录,对于每条记录,我需要执行一些验证和业务逻辑。我为actor建模的方式是,根actor接收文件流,将字节转换为String,然后按行分隔符拆分记录。完成此操作后,它将流(逐条记录)发送到另一个参与者以进行处理,后者再根据某些分组将记录分配给其他参与者。要将蒸汽从主要根演员发送到演员进行处理,我正在使用Sink.actorRefWithAck

这对于一个小文件来说很好用,但是对于一个大文件,我观察到的是,我得到了多个块,并且第一个块正在处理中。如果我根据负载添加Thread.sleep几秒钟,则它正在处理整个文件。我想知道是否有什么方法可以知道流是否已被处理角色完全消耗,因此我不必处理Thread.sleep。这是我使用的代码段:

val AckMessage = DefaultFileUploadProcessActor.Ack
val receiver = context.system.actorOf(
  Props(new DefaultFileUploadProcessActor(uuid, sourceId)(self, ackWith = AckMessage)))
// sent from stream to actor to indicate start, end or failure of stream:
val InitMessage = DefaultFileUploadProcessActor.StreamInitialized
val OnCompleteMessage = DefaultFileUploadProcessActor.StreamCompleted
val onErrorMessage = (ex: Throwable) => DefaultFileUploadProcessActor.StreamFailure(ex)

val actorSink = Sink.actorRefWithAck(
  receiver,
  onInitMessage = InitMessage,
  ackMessage = AckMessage,
  onCompleteMessage = OnCompleteMessage,
  onFailureMessage = onErrorMessage
)

val processStream =
  fileStream
    .map(byte => byte.utf8String.split(System.lineSeparator()))
    .runWith(actorSink)

Thread.sleep(9000)
log.info(s"completed distribution of data to the actors")
sender() ! ActionPerformed(uuid, "Done")

对于我采用的方法的任何专家建议,我们将不胜感激。

3 个答案:

答案 0 :(得分:0)

如果您的Source只有一个文件,则可以通过等待从runWith方法返回的Future来等待流完成。

如果您有多个文件的来源,则应编写如下内容:

filesSource
  .mapAsync(1)(data => (receiver ? data).mapTo[ProcessingResult])
  .mapAsync(1)(processingResult => (resultListener ? processingResult).mapTo[ListenerResponse])
  .runWith(Sink.ignore)

答案 1 :(得分:0)

当流成功完成或失败后,receiver操作者将收到OnCompleteMessageonErrorMessage,因此您应该在receive块中处理这些消息。接收者DefaultFileUploadProcessActor演员。

答案 2 :(得分:0)

假设fileStreamSource[ByteString, Future[IOResult],一个想法是保留源的物化值,然后在完成该物化值后触发对sender()的答复:

val processStream: Future[IOResult] =
  fileStream
    .map(_.utf8String.split(System.lineSeparator()))
    .to(actorSink)
    .run()

processStream.onComplete {
  case Success(_) =>
    log.info("completed distribution of data to the actors")
    sender() ! ActionPerformed(uuid, "Done")
  case Failure(t) =>
    // ...
}

以上方法可确保在通知发件人之前将整个文件消耗掉。

请注意,Akka Streams具有一个Framing对象,该对象可以解析ByteString流中的行:

val processStream: Future[IOResult] =
  fileStream
    .via(Framing.delimiter(
      ByteString(System.lineSeparator()),
      maximumFrameLenght = 256,
      allowTruncation = true))
    .map(_.ut8String)
    .to(actorSink) // the actor will have to expect String, not Array[String], messages
    .run()