在Akka Streams中使用动态接收器目标

时间:2017-07-19 13:27:31

标签: akka akka-stream

我对Akka很新,我正在努力学习基础知识。我的用例是不断从JMS队列中读取消息并将每条消息输出到一个新文件。我有基本的设置:

Source<String, NotUsed> jmsSource =
  JmsSource
    .textSource(JmsSourceSettings
    .create(connectionFactory)
    .withQueue("myQueue")
    .withBufferSize(10));

Sink<ByteString, CompletionStage<IOResult>> fileSink =
  FileIO.toFile(new File("random.txt"));

final Flow<String, ByteString, NotUsed> flow = Flow.fromFunction((String n) -> ByteString.fromString(n));

final RunnableGraph<NotUsed> runnable = jmsSource.via(flow).to(fileSink);

runnable.run(materializer);

但是,我希望文件名是动态的(而不是硬编码为&#34; random.txt&#34;):它应该根据队列中每条消息的内容进行更改。当然,我可以在流程中选择文件名,但如何在fileSink中设置该名称?我该如何最好地设置它?

2 个答案:

答案 0 :(得分:3)

我基于<WrappedComponent {...this.props} {...newProps} {...this.context} />创建了一个简单的接收器。我只在成功的案例中使用单个元素对其进行了测试,因此可以在此处或GitHub Gist发表评论。

this.props.actions.doTheThing

这将为每个元素创建一个新的Sink,因此它避免了akka.stream.impl.LazySink所具有的大量复杂性,并且也没有合理的物化值返回。

答案 1 :(得分:0)

以下是三种等效方法:

  • 使用map和内部图表
  • 再简洁一次
  • 使用flatMapConcat和内部图表
  • 再简洁一次
  • 使用GraphDSL更加详细。

在所有情况下,输出为:

$ tail -n +1 -- *.txt
==> 1.txt <==
1

==> 2.txt <==
2

==> 3.txt <==
3

==> 4.txt <==
4

==> 5.txt <==
5
  1. 使用map

    import java.nio.file.Paths
    
    import akka.actor.ActorSystem
    import akka.stream._
    import akka.stream.scaladsl.{FileIO, Sink, Source}
    import akka.util.ByteString
    
    import scala.concurrent.Future
    
    object Example extends App {
      override def main(args: Array[String]): Unit = {
        implicit val system = ActorSystem("Example")
        implicit val materializer = ActorMaterializer()
    
        val result: Future[Seq[Future[IOResult]]] = Source(1 to 5)
          .map(
            elem => Source.single(ByteString(s"$elem\n"))
                .runWith(FileIO.toPath(Paths.get(s"$elem.txt")))
          )
          .runWith(Sink.seq)
    
        implicit val ec = system.dispatcher
        result.onComplete(_ => system.terminate())
      }
    }
    

    <强>解释: 我们map Int元素创建了一个内部图形Source.single(ByteString(...)).runWith(FileIO.toPath(...)的函数,该图形序列化并写入动态路径,并允许我们累积结果Future[IOResult] Sink.seq

    <强>文档

      

    map   通过使用它调用映射函数并将返回的值传递给下游来转换流中的每个元素。

         当映射函数返回元素

    时,

    发出      下游背压时

    背压

         上游完成时

    完成

    另见

    1. 使用flatMapConcat

      import java.nio.file.Paths
      
      import akka.actor.ActorSystem
      import akka.stream._
      import akka.stream.scaladsl.{FileIO, Sink, Source}
      import akka.util.ByteString
      
      import scala.concurrent.Future
      
      object Example extends App {
        override def main(args: Array[String]): Unit = {
          implicit val system = ActorSystem("Example")
          implicit val materializer = ActorMaterializer()
      
          val result: Future[Seq[Future[IOResult]]] = Source(1 to 5)
            .flatMapConcat(
              elem => Source.single(
                Source.single(ByteString(s"$elem\n"))
                  .runWith(FileIO.toPath(Paths.get(s"$elem.txt")))
              )
            )
            .runWith(Sink.seq)
      
          implicit val ec = system.dispatcher
          result.onComplete(_ => system.terminate())
        }
      }
      

      <强>解释flatMapConcat需要Source。因此,我们创建了一个发出内部图mat Source.single(ByteString(...)).runWith(FileIO.toPath(...)的图表,它允许我们通过Future[IOResult]累积结果Sink.seq。实际的序列化和调度由内部图形完成。

      <强>文档

        

      flatMapConcat   将每个输入元素转换为Source,然后通过连接将其元素展平为输出流。这意味着在消耗下一个源开始之前,每个源都被完全消耗。

           当前消耗的子流有可用元素时

      发出

           下游背压时

      背压

           当上游完成并且所有消耗的子流完成时

      完成

      另见

      1. 使用Sink自定义GraphDSL

        import java.nio.file.Path
        
        import akka.stream.scaladsl.{Broadcast, FileIO, Flow, GraphDSL, Sink, Source, ZipWith}
        import akka.stream.{IOResult, Materializer, SinkShape}
        import akka.util.ByteString
        
        import scala.concurrent.Future
        
        object FileSinks {
          def dispatch[T](
                           dispatcher: T => Path,
                           serializer: T => ByteString
                         )(
                           implicit materializer: Materializer
                         ): Sink[T, Future[Seq[Future[IOResult]]]] =
            Sink.fromGraph(
              GraphDSL.create(
                Sink.seq[Future[IOResult]]
              ) {
                implicit builder =>
                  sink =>
                    // prepare this sink's graph elements:
                    val broadcast = builder.add(Broadcast[T](2))
                    val serialize = builder.add(Flow[T].map(serializer))
                    val dispatch = builder.add(Flow[T].map(dispatcher))
                    val zipAndWrite = builder.add(ZipWith[ByteString, Path, Future[IOResult]](
                      (bytes, path) => Source.single(bytes).runWith(FileIO.toPath(path)))
                    )
        
                    // connect the graph:
                    import GraphDSL.Implicits._
                    broadcast.out(0) ~> serialize ~> zipAndWrite.in0
                    broadcast.out(1) ~> dispatch ~> zipAndWrite.in1
                    zipAndWrite.out ~> sink
        
                    // expose ports:
                    SinkShape(broadcast.in)
              }
            )
        }
        ----
        import java.nio.file.Paths
        
        import FileSinks
        import akka.actor.ActorSystem
        import akka.stream._
        import akka.stream.scaladsl.Source
        import akka.util.ByteString
        
        import scala.concurrent.Future
        
        object Example extends App {
          override def main(args: Array[String]): Unit = {
            implicit val system = ActorSystem("Example")
            implicit val materializer = ActorMaterializer()
        
            val result: Future[Seq[Future[IOResult]]] = Source(1 to 5)
              .runWith(FileSinks.dispatch[Int](
                elem => Paths.get(s"$elem.txt"),
                elem => ByteString(s"$elem\n"))
              )
        
            implicit val ec = system.dispatcher
            result.onComplete(_ => system.terminate())
          }
        }
        
      2. 免责声明:我本人仍在学习Akka Stream。