重播实时收集的数据以模拟实际流量延迟和消息排序

时间:2019-06-03 11:45:08

标签: java scala akka akka-stream

有两个输入流,都产生定义为

的对象实例
case class ReplayData(timestamp:Long, payload:Any)

流1

1,有效载荷1

1000,有效载荷3

流2

400,有效负载2

1500,有效负载4

我想实现重播机制, 会按我在每个元素上按时间戳排序的顺序将元素推到下游

它将模拟生产中的实时场景。

此机制需要服从消息之间的延迟,例如第一个消息发送是有效负载1(它的起点),来自Stream2的有效负载2应该在400毫秒(下一个消息时间戳和初始消息时间戳之间的差)之后发送。

我可以使用DelayedQueue轻松做到这一点,SO thread

中对此用法进行了说明
  

Delayed元素的无限制阻塞队列,其中一个元素   只能在延迟时间到期后才能拍摄。

     

队列的头是延迟已过期的Delayed元素   过去最远的。如果没有延迟,就没有头和脚   poll将返回null。

     

当元素的getDelay(TimeUnit.NANOSECONDS)发生过期   方法返回的值小于或等于零。即使   未过期的元素无法使用take或poll删除,它们是   否则视为正常元素。

     

例如,size方法返回过期和过期的计数   未过期的元素。此队列不允许空元素。确实   不允许使用空元素。

我正在尝试弄清楚如何在Akka流中执行此操作,但是很难找到可以为我解决此问题的方法。

我当时看着mergeSorted是合并两个流并根据某些功能对其进行排序的一种方式。

似乎或多或少地适合于基于某些自定义功能进行排序的目的。

我不确定如何根据时间戳属性处理元素之间的延迟

使用普通的旧AKKA,我可以使用调度程序读取数据,对数据进行排序并安排时间流逝后要发送的每个元素。

1 个答案:

答案 0 :(得分:1)

我不记得akka流中的任何内容,这些内容可能会延迟开箱即用的消息,并且会为每条消息自定义延迟。毕竟,akka-streams背后的想法是反应式编程。我只知道2种方法通常可以解决您的问题(假设您已经合并了2个来源)

  1. Flow.mapAsync-在这种情况下,延迟一段时间后返回Future完全是您的事。例如:

    import java.time.LocalDateTime
    import java.util.concurrent.Executors
    
    import akka.NotUsed
    import akka.actor.ActorSystem
    import akka.pattern.after
    import akka.stream.ActorMaterializer
    import akka.stream.scaladsl.Source
    
    import scala.concurrent.ExecutionContext.Implicits.global
    import scala.concurrent.duration._
    import scala.concurrent.{ExecutionContext, Future}
    
    object Application extends App {
    
      implicit val sys: ActorSystem = ActorSystem()
      implicit val mat: ActorMaterializer = ActorMaterializer()
    
      case class SomeEntity(time: Int, value: Int)
      val source: Source[SomeEntity, NotUsed] = Source(List(100, 200, 400, 1000, 1100, 1400)).map(i => SomeEntity(i, i * i + 3))
    
      val ec: ExecutionContext = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(10))
      val scheduler = sys.scheduler
    
      val f = source
        .mapAsync(10)(se => after(se.time.milliseconds, scheduler)(Future.successful(se))(ec))
        .runForeach(se => println(s"${LocalDateTime.now()} -> $se"))
    
      f.onComplete(_ => sys.terminate())
    }
    
  2. 可能会发生您的用例(毕竟是模拟)实际上并不那么严格的情况,因此您可以使用Flow.throttle。它不是第一种解决方案那样简单,精确,但是它的性能要高得多,因为它使用了一些轻量级的存储桶模型来控制物品的输出速度。

    import java.time.LocalDateTime
    
    import akka.NotUsed
    import akka.actor.ActorSystem
    import akka.stream.ActorMaterializer
    import akka.stream.scaladsl.Source
    
    import scala.concurrent.ExecutionContext.Implicits.global
    import scala.concurrent.duration._
    
    object Application extends App {
    
      implicit val sys: ActorSystem = ActorSystem()
      implicit val mat: ActorMaterializer = ActorMaterializer()
    
      case class SomeEntity(time: Int, value: Int)
    
      val source: Source[SomeEntity, NotUsed] = Source(List(100, 200, 400, 1000, 1100, 1400, 1400, 1500, 1900, 2500, 2700)).map(i => SomeEntity(i, i * i + 3))
    
    
      val future = source.throttle(cost = 1, per = 1.millisecond, _.time).runForeach(se => {
        println(s"${LocalDateTime.now()} -> $se")
      })
    
      future.onComplete(_ => sys.terminate())
    }