Kotlin 从多个来源收集数据的惯用方法是什么?
设想一个数据服务器,数据在40个节点之间随机分片,您希望从中计算每200条记录的值。因此,加载200、计算、加载200、计算等。您的服务器每秒可以处理500条记录,但您有足够的带宽每秒从每台服务器读取50条记录(最大吞吐量为2000条记录) 您可以按顺序执行此操作,这是最简单的选项:Kotlin 从多个来源收集数据的惯用方法是什么?,kotlin,kotlin-coroutines,Kotlin,Kotlin Coroutines,设想一个数据服务器,数据在40个节点之间随机分片,您希望从中计算每200条记录的值。因此,加载200、计算、加载200、计算等。您的服务器每秒可以处理500条记录,但您有足够的带宽每秒从每台服务器读取50条记录(最大吞吐量为2000条记录) 您可以按顺序执行此操作,这是最简单的选项: var cache = mutableListOf() for (serv in servers) { for(record in serv.loadData()) { cache += r
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)
}
这将最大限度地利用吞吐量,但会浪费并发队列中的空间,而且as也不允许并行处理和加载
因此,这似乎是一个利用流的好机会。我们希望保持从多个源并行加载的能力,因此我们将用emit(record)
替换queue+=record
,然后在collect{}
中批处理结果,但是Flow.emit
不是多线程安全的(由于启动
,上下文会发生变化,但这是可以克服的,即使这是不可取的)
假设serv.loadData()
以增量方式加载数据,这仍然可以通过在队列太满时暂停数据加载来实现。但是这样编写会让人感觉非常手动和笨拙
那么-假设您不关心数据的加载顺序-在当前版本的Kotlin中实现这一点的惯用方法是什么?下面是一种使用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) }
}
suspend fun main(){
servers.asFlow()
.flatMapMerge(servers.size){server->flow{
for(记录在server.loadData()中){
发射(记录)
}
} }
.分块(500)
.flow on(Dispatchers.IO)//可选
.收集{批处理->
过程(批次)
}
}
乐趣流。分块(大小:Int)=流{
var chunk=mutableListOf()
收集{
chunk.add(它)
if(chunk.size==大小){
发射(块)
chunk=mutableListOf()
}
}
chunk.takeIf{it.isNotEmpty()}?.还有{emit(it)}
}
Flow仍然没有一个标准的chunked实现,所以我提供了一个快速而肮脏的实现。很好;我想我不是第一个遇到这种情况的人:)我的想法是,这也需要一个调度器吗?从外部看,我们知道loadData()
是一个IO绑定的操作,但我们似乎没有办法指定使用Dispatchers.IO
。或者此并发收集是以其他方式完成的?这取决于loadData()
是阻塞函数还是挂起函数。我写这篇文章时假设suspend-fun
。您可以使用上下文(IO)
将整个流程包装成,也可以在flatMapMerge
下面添加.flowOn(IO)
。