Monix如何通过flatMap运算符使用反压?

时间:2019-04-25 07:07:27

标签: scala backpressure monix

Monix使用Ack同步发出的消息,但是如果我使用groupBy和flatMap,则内部Observable不会遵循source的背压。

请查看此测试代码:

import java.util.concurrent.TimeUnit

import monix.execution.Scheduler.Implicits.global
import monix.execution.Ack.Continue
import monix.reactive.{Observable, OverflowStrategy}
import org.junit.Test


class MonixBackpressureWithGroupByTest2 {
  @Test
  def test(): Unit = {
    val source = Observable.range(0,130)

    val backPressuredStream = source.map(x => {
        println("simple log first  map - " + x)
        x
      })
      .asyncBoundary(OverflowStrategy.BackPressure(5))
      .map { i =>

        println("after backpressure map, and Rim 3 operation of source - " + ((i % 3) toString) -> i)
        ((i % 3) toString) -> i
      }
      .groupBy{case (k, v) => k}
      .flatMap(x => {
        val mapWithSleep = x.map{case groupedMsg@(key, value) =>
          Thread.sleep(2000)
          println("inner Observable after group by rim 3. sleep 2 second for every message - " + groupedMsg)
          groupedMsg
        }

        mapWithSleep

      })

    backPressuredStream.share.subscribe(
      (keyAndValue: (String, Long)) => Continue
    )

    global.scheduleWithFixedDelay(0L, 1000L, TimeUnit.MILLISECONDS, () => {
      println("========sleep 1 second ============")
    })

    Thread.currentThread().join()

  }

}

输出:

...

========sleep 1 second ============
inner Observable after group by rim 3. sleep 2 second for every message - (0,72)
(after backpressure map, and Rim 3 operation of source - 1,73)
(after backpressure map, and Rim 3 operation of source - 2,74)
(after backpressure map, and Rim 3 operation of source - 0,75)
========sleep 1 second ============
========sleep 1 second ============
inner Observable after group by rim 3. sleep 2 second for every message - (0,75)
(after backpressure map, and Rim 3 operation of source - 1,76)
(after backpressure map, and Rim 3 operation of source - 2,77)
(after backpressure map, and Rim 3 operation of source - 0,78)
========sleep 1 second ============
========sleep 1 second ============
inner Observable after group by rim 3. sleep 2 second for every message - (0,78)
(after backpressure map, and Rim 3 operation of source - 1,79)
...

出现一些背压不匹配的情况:
之后:sleep 2 second for every message ...背压给项after backpressure map - ...

中的三个

sleep 2 second for every message ...在背压方面如何与after backpressure map - ...一对一?

另一个疑问:为什么sleep 2 second for every message的日志输出(0, 72), (0, 75), (0,78)却却(0, 72), (1, 73), (2,74)这样的东西?

谢谢。

Monix版本:   "io.monix" %% "monix" % "3.0.0-RC1"

1 个答案:

答案 0 :(得分:2)

您看到的行为正是您所期望的。

为了快速总结您的应用程序的功能,让我用我的话解释一下:


您有一个Observable生成数字,并对每个元素都有一些副作用。

接下来,您按_ % 3对元素进行分组。

接下来,您将在每个组的Observable内进行一些其他操作(休眠和写入控制台)。

然后,您flatMap每个组的Observable,得到一个单一的Observable


那么,为什么一开始您只看到第一组(其中_ % 3 == 0)向控制台打印内容? ***

答案在于flatMap:查看Observable的{​​{3}}时,会发现以下flatMap的描述:

final def flatMap[B](f: (A) ⇒ Observable[B]): Observable[B]

Alias for concatMap.

[...]

想起Observable秒钟,就像想想List秒钟一样:当连接List s时,最终会得到一个包含List的单个第一个List的元素,然后是第二个List的元素,依此类推。

在Monix中,通过等待Observable(读取:Observable)操作中产生的第一个flatMap发送“已完成”来完成concatMap的相同行为“-信号。只有这样,第二个Observable才会被消耗,依此类推。

或者,简而言之,flatMap关心产生的Observable的顺序。

但是您的 Observable操作中的flatMap何时“完成”?为此,我们必须了解groupBy的工作原理-因为那是它们的来源。

尽管groupBy被懒惰地评估,但为了使Observable工作,它必须将传入的元素存储在缓冲区中。我对此不是100%的确定,但是如果groupBy的工作方式像我认为的那样,它将对拉下一个元素的任何分组的Observable无限期地遍历原始Observable直到找到属于该组的元素为止,然后将属于其他组的所有先前(但不是必需)元素保存在该缓冲区中,以供以后使用。

所有这些都意味着groupBy在源Observable发信号完成之前,不知道是否找到了组中的所有元素,然后它将使用所有剩余的缓冲元素,然后将完成信号发给已分组的对象Observable

用简单的话来说:Observable产生的groupBy直到源Observable完成才完成。

将所有这些信息汇总在一起时,您将了解,只有当源Observable(您的Observable.range(0, 130))完成时,第一个分组的Observable也会完成,并且由于{{1 }},然后将使用所有其他分组的flatMap

因为我从上一个问题中知道您正在尝试构建Web套接字,所以使用Observable是个坏主意-传入请求的来源flatMap永远不会 >完整,仅有效地服务您将遇到的第一个IP地址。

您需要做的是使用ObservablemergeMap进行比较时,concatMap并不关心元素的顺序,而是“第一个先到先得”-适用规则。


***:当您到达我的解释的结尾并希望理解mergeMapgroupBy的工作原理时,您将理解为什么我写“一开始”!