动态合并Akka流

时间:2019-07-05 12:43:56

标签: scala merge stream akka

我正尝试通过以下方式使用Akka流构建pub sub总线:

发布者为主题添加源流,订阅者指定主题并获取该主题的所有内容。但是,主题可以由多个发布者发布,发布者和订阅者都可以随时加入。

我想到的是合并所有源,然后将过滤后的源返回给订户。

但是,由于发布者可以随时加入,因此可以在订阅后添加来源,订阅者需要从中获取数据,就像该主题的任何其他已发布数据一样。

有没有一种方法可以动态地管理流与源的合并,从而使以下内容成立:

发布(主题:字符串,消息:来源[T]) subscription(topic:字符串):来源[T]

这样,无论何时添加发布者,主题订阅者都将在订阅后将所有消息发布到与该主题相关的任何来源。

也很高兴听到其他方法。

谢谢, Z

2 个答案:

答案 0 :(得分:1)

您可能想看看以下Akka文档:使用dynamic pub-sub serviceMergeHub构建BroadcastHub

这里是将MergeHubBroadcastHub分别用作动态扇入和扇出结的示例代码。

想法是将MergeHubBroadcastHub连接以通过Flow.fromSinkAndSource以Flow的形式形成一个发布订阅频道:

val (bfSink, bfSource) = MergeHub.source[String](perProducerBufferSize).
  toMat(BroadcastHub.sink[String](bufferSize))(Keep.both).
  run

val busFlow: Flow[String, String, NotUsed] = Flow.fromSinkAndSource(bfSink, bfSource)

请注意,以上代码段中的Keep.both(Sink[T, NotUsed], Source[T, NotUsed])MergeHub.source[T]生成具有以下方法签名的元化值BroadcastHub.sink[T]的元组:

object MergeHub {
  def source[T](perProducerBufferSize: Int): Source[T, Sink[T, NotUsed]] = // ...
  // ...
}

object BroadcastHub {
  def sink[T](bufferSize: Int): Sink[T, Source[T, NotUsed]] = // ...
  // ...
}

下面是一个简单的发布订阅频道busFlow的示例代码(类似于Akka文档中的示例):

import akka.actor.ActorSystem
import akka.stream._
import akka.stream.scaladsl._
import akka.NotUsed

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

val (bfSink, bfSource) = MergeHub.source[String](perProducerBufferSize = 32).
  toMat(BroadcastHub.sink[String](bufferSize = 256))(Keep.both).
  run

// Optional: avoid building up backpressure when there is no subscribers
bfSource.runWith(Sink.ignore)

val busFlow: Flow[String, String, NotUsed] = Flow.fromSinkAndSource(bfSink, bfSource)

测试busFlow

Source(101 to 103).map(i => s"Batch(A)-$i").
  delay(2.seconds, DelayOverflowStrategy.backpressure).
  viaMat(busFlow)(Keep.right).
  to(Sink.foreach{ case s: String => println("Consumer(1)-" + s) }).
  run

Source(104 to 105).map(i => s"Batch(B)-$i").
  viaMat(busFlow)(Keep.right).
  to(Sink.foreach{ case s: String => println("Consumer(2)-" + s) }).
  run

// Consumer(2)-Batch(B)-104
// Consumer(2)-Batch(B)-105
// Consumer(1)-Batch(B)-104
// Consumer(1)-Batch(B)-105
// Consumer(1)-Batch(A)-101
// Consumer(1)-Batch(A)-102
// Consumer(2)-Batch(A)-101
// Consumer(1)-Batch(A)-103
// Consumer(2)-Batch(A)-102
// Consumer(2)-Batch(A)-103

用作发布订阅通道,busFlow的输入通过bfSink发布给所有订阅者,而其输出则通过bfSource流所有发布的元素。例如:

val p1 = Source.tick[Int](0.seconds, 5.seconds, 5).map(_.toString)
p1.runWith(bfSink)

val p2 = Source.tick[Int](2.seconds, 10.seconds, 10).map(_.toString)
p2.runWith(bfSink)

val s1 = bfSource
s1.runForeach(x => println(s"s1 --> $x"))

val s2 = bfSource
s2.runForeach(x => println(s"s2 --> $x"))

// s1 --> 5
// s2 --> 5
// s1 --> 10
// s2 --> 10
// s2 --> 5
// s1 --> 5
// s2 --> 5
// s1 --> 5
// s1 --> 10
// s2 --> 10
// s2 --> 5
// s1 --> 5
// ...

其他可能感兴趣的相关主题包括KillSwitch(用于流完成控制)和PartitionHub(用于将Stream元素从给定生产者路由到一组动态消费者)。

答案 1 :(得分:0)

这就是我最终要做的。发布者和订阅者都可以消失,并且无论订阅者何时加入以及发布者何时加入,订阅者都应能够(按主题)查看其订阅的所有已发布消息,而与订阅时处于活动状态的发布者无关。制作。欢迎发表评论。

def main(args: Array[String]): Unit = {
   val actorSystem = ActorSystem("test")
   val materializerSettings = ActorMaterializerSettings(actorSystem)
   implicit val materializer = ActorMaterializer(materializerSettings)(actorSystem)
   implicit val ec: ExecutionContext = actorSystem.dispatcher

   val (queue, pub) = Source.queue[Int](100, akka.stream.OverflowStrategy.dropHead).toMat(Sink.asPublisher(true))(Keep.both).run()

   val p1 = Source.tick[Int](0.seconds, 5.seconds, 5)

   p1.runForeach(x=> {queue.offer(x)})

   val p2= Source.tick[Int](2.seconds,10.seconds, 10)
   p2.runForeach(x=> queue.offer(x))

   val s1 = Source.fromPublisher(pub)
   s1.runForeach(x=> println(s"s1 =======> ${x}"))

   val s2 = Source.fromPublisher(pub)
   s2.runForeach(x=> println(s"s2 =======> ${x}"))
}