在Akka Streams中压缩两个流

时间:2019-07-03 20:22:09

标签: scala akka akka-stream

我有两个流程:

val a: Flow[Input, Data, NotUsed] =...
val b: Flow[Input, Unit, NotUsed] =...

第一个流程是我关心的数据事件流,第二个流程是“信号”流,也就是说,我真的只想在{{1}中收到元素时向下游发送Data }}。

我曾想过使用b之类的东西,但这似乎只在流和源之间起作用(尽管Akka文档暗示它也支持压缩流)。

我想念什么?

谢谢

3 个答案:

答案 0 :(得分:1)

如果您查看zipzipWith的签名:

def zip[U](that: Graph[SourceShape[U], _]): Repr[(Out, U)]

def zipWith[Out2, Out3](that: Graph[SourceShape[Out2], _])(combine: (Out, Out2) => Out3): Repr[Out3]

两种方法都期望Source

用另一个Flow压缩Flow并不像人们想象的那么琐碎(例如,第二个Flow可能每个输入元素使用mapConcat生成多个元素)

您可以考虑构建自定义GraphStage,如以下简单的示例所示:

case class DataIn(id: Int)
case class DataOut(content: String)
case class Signal(s: Int)

class ZipperFlow extends GraphStage[FlowShape[(DataIn, Signal), DataOut]] {

  val in = Inlet[(DataIn, Signal)]("ZipperFlow.in")
  val out = Outlet[DataOut]("ZipperFlow.out")

  override val shape = FlowShape.of(in, out)

  override def createLogic(attr: Attributes): GraphStageLogic =
    new GraphStageLogic(shape) {
      setHandler(in, new InHandler {
        override def onPush(): Unit = {
          push(out, DataOut("content-" + grab(in)._1.id))
        }
      })
      setHandler(out, new OutHandler {
        override def onPull(): Unit = {
          pull(in)
        }
      })
    }
}

测试ZipperFlow

implicit val system = ActorSystem("system")
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher

val dataSource = Source(1 to 5).map(DataIn(_))
val signalSource = Source(1 to 5).map(Signal(_))

val sink: Sink[DataOut, Future[Done]] = Sink.foreach(println)

dataSource.zip(signalSource).via(new ZipperFlow).runWith(sink)

// DataOut(content-1)
// DataOut(content-2)
// DataOut(content-3)
// DataOut(content-4)
// DataOut(content-5)

答案 1 :(得分:0)

这可以使用merge中的akka-streams graphs来实现

更新: 正确的是zip

示例:

import akka.NotUsed
import akka.actor.ActorSystem
import akka.stream.scaladsl._
import akka.stream.{ActorMaterializer, ClosedShape}


object Application extends App {

  implicit val sys: ActorSystem = ActorSystem()
  implicit val mat: ActorMaterializer = ActorMaterializer()

  val flowX: Flow[Int, String, NotUsed] = Flow[Int].map(i => (i + 10).toString)
  val flowY: Flow[Int, Long, NotUsed] = Flow[Int].map(i => (i * 2).toLong)

  RunnableGraph.fromGraph(GraphDSL.create(flowX, flowY)((_, _)) { implicit builder =>
    (flowX, flowY) =>
      import GraphDSL.Implicits._
      val broadcast = builder.add(Broadcast[Int](2))
      val zip = builder.add(Zip[String, Long])
      Source((1 to 10).toList) ~> broadcast.in

      broadcast ~> flowX ~> zip.in0
      broadcast ~> flowY ~> zip.in1
      zip.out ~> Sink.foreach(println)
      ClosedShape
  }).run()
}

flowXflowY是用于图形创建的参数。在constructing graph部分中,您可以找到用于拆分和合并流的不同情况(扇出+扇入)。使用图要比使用线性流更难。也许仅创建具有流动形状(1个输入,1个输出)的partial graph是有意义的-因此用户将其视为常规流动(但隐藏了复杂性)。我个人建议不要使用一般的图形,因为它很难测试(很难找到错误或性能下降),尽管在某些情况下这是一个很棒的功能

您可以找到许多methods来使用不同数量的参数来创建图形。此外,您可以为图形创建提供不同的输入参数-不同的源,流,汇。

答案 2 :(得分:0)

  implicit class FlowExt[In, Out, Mat](flow: Flow[In, Out, Mat]) {
    /**
     * 
     * @param f
     * @tparam Out2
     * @return
     */
    def zipFlow[Out2](f: Flow[In, Out2, Mat]): Flow[In, (Out, Out2), NotUsed] = {
      Flow.fromGraph(GraphDSL.create() { implicit b =>
        import GraphDSL.Implicits._

        val broadcast = b.add(Broadcast[In](2))
        val zip = b.add(Zip[Out, Out2])

        broadcast.out(0) -> zip.in0
        broadcast.out(1) -> zip.in1

        FlowShape(broadcast.in, zip.out)
      })
    }
  }

用法val c: Flow[Input, (Data, Unit), NotUsed] = a.zipFlow(b)