使用流的内容更改源中的物化值

时间:2019-01-21 13:10:06

标签: scala akka-stream alpakka

Alpakka提供了一种访问数十种不同数据源的好方法。面向文件的源(例如HDFS和FTP源)以Source[ByteString, Future[IOResult]的形式提供。但是,通过Akka HTTP发出的HTTP请求将作为Source[ByteString, NotUsed]的实体流传递。在我的用例中,我想以Source[ByteString, Future[IOResult]的形式从HTTP源中检索内容,因此我可以构建一个可以在多种方案(在这种情况下为hdfs,file,ftp和S3)下工作的统一资源提取程序。

尤其是,我想将Source[ByteString, NotUsed]源转换为 Source[ByteString, Future[IOResult],在这里我可以根据传入的字节流计算IOResult。有很多方法,例如flatMapConcatviaMat,但似乎没有一个方法能够从输入流中提取细节(例如读取的字节数)或正确初始化IOResult结构。理想情况下,我正在寻找一种具有以下签名的方法,该签名将在流进入时更新IOResult。

  def matCalc(src: Source[ByteString, Any]) = Source[ByteString, Future[IOResult]] = {
    src.someMatFoldMagic[ByteString, IOResult](IOResult.createSuccessful(0))(m, b) => m.withCount(m.count + b.length))
  }

2 个答案:

答案 0 :(得分:1)

我无法回忆起任何现成的功能,可以立即使用,但是您可以使用alsoToMat(令人惊讶的是,尽管您可以在源代码中找到它,但在akka流文档中找不到它)文档和Java api)流函数与Sink.fold一起累积一些值并最终给出它。例如:

def magic(source: Source[Int, Any]): Source[Int, Future[Int]] =
    source.alsoToMat(Sink.fold(0)((acc, _) => acc + 1))((_, f) => f)

问题是alsoToMat将输入Mat值与alsoToMat中提供的值相结合。同时,源产生的值不受alsoToMat中接收器的影响:

def alsoToMat[Mat2, Mat3](that: Graph[SinkShape[Out], Mat2])(matF: (Mat, Mat2) ⇒ Mat3): ReprMat[Out, Mat3] =
  viaMat(alsoToGraph(that))(matF)

根据源代码,改编此函数以返回IOResult并不难:

final case class IOResult(count: Long, status: Try[Done]) { ... }

您还需要注意的最后一件事-您希望您的消息来源像这样:

Source[ByteString, Future[IOResult]]

但是,如果您要直到流定义的最后才携带这些值,然后根据将来的完成情况进行处理,那可能是容易出错的方法。例如,在此示例中,我根据该将来完成工作,因此将不处理最后一个值:

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Keep, Sink, Source}

import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContext, Future}

object App extends App {

  private implicit val sys: ActorSystem = ActorSystem()
  private implicit val mat: ActorMaterializer = ActorMaterializer()
  private implicit val ec: ExecutionContext = sys.dispatcher

  val source: Source[Int, Any] = Source((1 to 5).toList)

  def magic(source: Source[Int, Any]): Source[Int, Future[Int]] =
    source.alsoToMat(Sink.fold(0)((acc, _) => acc + 1))((_, f) => f)

  val f = magic(source).throttle(1, 1.second).toMat(Sink.foreach(println))(Keep.left).run()
  f.onComplete(t => println(s"f1 completed - $t"))
  Await.ready(f, 5.minutes)


  mat.shutdown()
  sys.terminate()
}

答案 1 :(得分:0)

这可以通过使用Promise来实现值的传播来实现。

val completion = Promise[IoResult]
val httpWithIoResult = http.mapMaterializedValue(_ => completion.future)

现在剩下的是在相关数据可用时完成completion承诺。

另一种方法是使用GraphStage API,您可以在其中获得对物化值传播的较低级别控制。但是,即使在其中使用Promises也是实现值传播的选择实现。看一下Ignore之类的内置运算符实现。