想象一下一个数据服务器,其中的数据在40个节点之间随机分片,您要从中计算每200条记录的值。因此,加载200,计算,加载200,计算等。您的服务器每秒可以处理500条记录,但是您有足够的带宽从每台服务器每秒读取50条记录(最大吞吐量为2000条记录)。>
您可以按顺序执行此操作,这是最简单的选择:
var cache = mutableListOf()
for (serv in servers) {
for(record in serv.loadData()) {
cache += record
if (cache.count() == 500) {
process(cache)
cache.popFront(500)
}
}
}
这不会浪费任何内存空间,而只会加载50条记录/秒,并且不会并行处理结果。因此,另一种方法是先从所有服务器获取结果,然后对其进行迭代:
var queue = ConcurrentLinkedDeque()
coroutineScope {
for (serv in servers) {
launch(Dispatchers.IO) {
for (record in serv.loadData()) {
queue += record
}
}
}
}
for (batch in queue.chunked(500)) {
process(batch)
}
这将充分利用您的吞吐量,但会浪费并发队列中的空间,并且按原样还不允许并行进行处理和加载。
因此,这似乎是利用Flow
的好机会。我们希望保持从多个源并行加载的能力,因此我们将queue += record
替换为emit(record)
,然后在collect{}
中批量处理结果,但是Flow.emit
不是t多线程安全的(上下文因launch
而改变,但是即使不希望发生,也可以克服)。
假设serv.loadData()
以增量方式加载数据,那么仍然可以通过在队列太满时暂停数据加载来实现。但是,以这种方式编写它确实是手动且笨拙的。
因此-假设您不在乎数据的加载顺序-在当前版本的Kotlin中,惯用的方式是什么?
答案 0 :(得分:1)
这是flatMapMerge
的一种方法,它可以自动并行化您发出的内部流:
suspend fun main() {
servers.asFlow()
.flatMapMerge(servers.size) { server -> flow {
for (record in server.loadData()) {
emit(record)
}
} }
.chunked(500)
.flowOn(Dispatchers.IO) // optional
.collect { batch ->
process(batch)
}
}
fun <T> Flow<T>.chunked(size: Int) = flow {
var chunk = mutableListOf<T>()
collect {
chunk.add(it)
if (chunk.size == size) {
emit(chunk)
chunk = mutableListOf()
}
}
chunk.takeIf { it.isNotEmpty() }?.also { emit(it) }
}
Flow仍然没有chunked
的标准实现,因此我提供了一个简单的方法。