我对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
中设置该名称?我该如何最好地设置它?
答案 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
使用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
通过使用它调用映射函数并将返回的值传递给下游来转换流中的每个元素。发出 下游背压时
背压
上游完成时完成
另见:
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
,然后通过连接将其元素展平为输出流。这意味着在消耗下一个源开始之前,每个源都被完全消耗。发出
下游背压时背压
当上游完成并且所有消耗的子流完成时完成
另见:
使用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())
}
}
dispatcher
是一个将输入对象转换为路径的函数,您可以在此处动态决定路径。serializer
是一个序列化输入对象的函数。GraphDSL
,另请参阅:https://doc.akka.io/docs/akka/current/stream/stream-graphs.html#constructing-graphs 免责声明:我本人仍在学习Akka Stream。