kotlin flow 背压策略实现方案

368 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

前言

相信大家在开发过程中都遇到过这样一种情况,当订阅的某个事件连续发送只需要间隔10ms,而处理单个事件又需要3s,这种被观察者发送事件速度与观察者处理事件速度不匹配的情况。

在这种情况之下会出现观察者无法及时响应被观察者发送的事件,往往伴随着缓存区溢出、事件丢失、OOM这类程序异常的现象。

示例场景

假设我们存在以下逻辑,emit 耗时 10毫秒,collect 耗时 3 秒,这样我们就会出现背压现象。如果按照这个逻辑执行就会出现 发出 -> 收集 -> 发出 -> 收集 -> ...,这样往复5次。

fun emitter(): Flow = (1..5)
	.asFlow()
	.onEach{
		delay(10)
		print("发出")
	}

fun main() = runBlocking{
    emitter().collect{
                delay(3000)
                print("收集")
            }
}

方案一

解决背压的一种方法是在线程中处理发射。

emitter()
	.flowOn(Dispatchers.Default)
	.collect{
            delay(3000)
            print("收集")
	}

这样,发射线程就使用了默认的dispatcher(调用flowOn之前的逻辑在dispatcher中运行)。由于收集线程使用 ,因此减少了发出所需的时间。但是,由于 collect 是按顺序执行的,因此仍然需要很长时间。此外还有一个缺点是发出的线程和收集的线程之间存在差异。

方案二

由于collect中使用了delay(挂起函数),所以还有另一种使用buffer的方法。

emitter()
	.buffer()
	.collect{
             delay(3000)
             print("收集")
	}

这样一来,在你延迟 collect 的那一刻,发射器就可以继续发射并在缓冲区中累积值。这样做的好处是发射线程和收集线程之间的线程是相同的。但是,如果 collect 本身需要很长时间而不使用挂起功能,则无法解决。

方案三

如果你不需要处理你发出的所有值,只需要关注最新的处理值,你也可以使用 conlation 。

emitter()
	.conflation()
	.collect{
             delay(3000)
             print("收集")
	}

这样,当collect中的挂起函数被调用时,发射器继续发射值,但如果collect处于不可接受状态,则丢弃除最新值之外的所有值。也就是说,collect 只处理它可以处理的最新值。