我尝试在键中对Monix中的单个Observable
进行拆分,然后将每个n
中的最后GrouppedObservable
个事件分组,并将其发送以进行进一步处理。问题在于,要分组的键的数量可能是无限的,并且会导致内存泄漏。
应用环境:
我有来自很多对话的消息的kafka流。每个对话都有roomId
,我想将此ID分组以获得Observable的集合,每个Observables仅包含来自单个对话的消息。
对话室通常是短暂的,即使用唯一的roomId
创建新的对话,在短时间内交换几十条消息,然后关闭对话。
为了避免内存泄漏,我只想保留100-1000个最近会话的缓冲区,并删除较旧的会话。因此,如果某个事件来自长时间未见的对话,则该事件将被视为新的对话,因为会忘记带有其先前消息的缓冲区。
groupBy方法具有参数keysBuffer
,该参数指定如何处理键缓冲区。
我认为将keyBuffer
设置为DropOld策略将使我能够实现想要的行为。
下面是所描述用例的简化版本。
import monix.execution.Scheduler.Implicits.global
import monix.reactive._
import scala.concurrent.duration._
import scala.util.Random
case class Event(key: Key, value: String, seqNr: Int) {
override def toString: String = s"(k:$key;s:$seqNr)"
}
case class Key(conversationId: Int, messageNr: Int)
object Main {
def main(args: Array[String]): Unit = {
val fakeConsumer = Consumer.foreach(println)
val kafkaSimulator = Observable.interval(1.millisecond)
.map(n => generateHeavyEvent(n.toInt))
val groupedMessages = kafkaSimulator.groupBy(_.key)(OverflowStrategy.DropOld(50))
.mergeMap(slidingWindow)
groupedMessages.consumeWith(fakeConsumer).runSyncUnsafe()
}
def slidingWindow[T](source: Observable[T]): Observable[Seq[T]] =
source.scan(List.empty[T])(fixedSizeList)
def fixedSizeList[T](list: List[T], elem: T): List[T] =
(list :+ elem).takeRight(5)
def generateHeavyEvent(n: Int): Event = {
val conversationId: Int = n / 500
val messageNr: Int = n % 5
val key = Key(conversationId, messageNr)
val value = (1 to 1000).map(_ => Random.nextPrintableChar()).toString()
Event(key, value, n)
}
}
但是,在VisualVM上观察应用程序堆表明内存泄漏。经过大约30分钟的跑步,我得到了java.lang.OutOfMemoryError: GC overhead limit exceeded
以下是堆使用情况图的屏幕快照,描述了运行我的应用程序约30分钟。 (末尾的平整部分位于OutOfMemoryError
之后)
VisualVM Heap plot of application
我的问题是:如何在不泄漏内存的情况下按可能无限数量的键在monix中对事件进行分组?允许删除旧密钥
背景信息:
3.0.0-RC2
2.12.8
答案 0 :(得分:1)
我有与您类似的用例,读取kafka流并按ID分组。
您想要做的是在没有需求时使GrouppedObservable
超时/清理。否则,它将永远存在于内存中。因此,您可以执行以下操作:
val eventsStream: Observable[Int] = ???
eventsStream
.groupBy(_ % 2 == 0)
.mergeMap {
_.mapEval(s => Task.delay(println(s)))
.timeoutOnSlowUpstreamTo(5.minutes, Observable.empty)
}
.completedL