Consultas paralelas em Kotlin para automatizar a coleta de dados

Olá pessoal! No meu trabalho, costumo usar Kotlin para automação. Minha atividade não está diretamente relacionada à programação, mas Kotlin simplifica muito as tarefas de trabalho.





Recentemente foi necessário coletar dados de um tamanho bastante grande para fazer a análise, então decidi escrever um pequeno script para obter os dados e salvá-los no Excel. Não houve problemas com o último ponto - li sobre o Apache POI, peguei alguns exemplos da documentação oficial, modificando-o por mim mesmo. O mesmo não pode ser dito sobre os pedidos da Internet.





A fonte retornou em lotes de json e foi necessário coletar esses "lotes" de alguma forma rapidamente, convertendo-os em texto e escrevendo uma tabela em um arquivo.





Método assíncrono

Decidi começar com uma simples assincronização. Depois de vasculhar um pouco o HttpUrlConnection, enviei-o para onde ele pertence, substituindo-o por HttpClient do Java.





Para testes, usei o serviço https://jsonplaceholder.typicode.com/ , que me foi sugerido por um desenvolvedor conhecido. Salvei um link que emite comentários ao Json para uma variável, de modo a não duplicar e iniciar os testes.





const val URL = "https://jsonplaceholder.typicode.com/comments"
      
      



A função estava pronta e até funcionando. Os dados chegaram.





fun getDataAsync(url: String): String? {
    val httpClient = HttpClient.newBuilder()
        .build()
    val httpRequest = HttpRequest.newBuilder()
        .uri(URI.create(link)).build()

    return httpClient.sendAsync(httpRequest, BodyHandlers.ofString())
        .join().body()
}
      
      



Agora era preciso verificar a velocidade de trabalho. Armado com o measureTimeMillis, executei o código.





val asyncTime = measureTimeMillis { 
    val res = (1..10)
        .toList()
        .map {getDataAsync("$URL/$it")}
    res.forEach { println(it) }
}
println("   $asyncTime ")
      
      



Tudo funcionou como deveria, mas eu queria mais rápido. Depois de vasculhar um pouco a Internet, encontrei uma solução em que as tarefas são realizadas em paralelo.





Mapa Paralelo

, . , , .





suspend fun <A, B> Iterable<A>.pmap(f: suspend (A) -> B): List<B> =
    coroutineScope {
        map { async { f(it) } }.awaitAll()
    }
      
      



, ( Iterable) pmap, . A. async , .awaitAll() . suspend, .





, , - .





val parmapTime = measureTimeMillis {
    runBlocking {
        val res = (1..10)
            .toList()
            .pmap { getDataAsync("$URL/$it") }
        println(mapResult)
    }
}
println(" pmap $parmapTime ")
      
      



- 1523, . map async, .





Parallel Map v 2.0

, , .





suspend fun <T, V> Iterable<T>.parMap(func: suspend (T) -> V): Iterable<V> =
    coroutineScope {
        map { element -> 
            async(Dispatchers.IO) { func(element) } 
        }.awaitAll() 
    }

val parMapTime = measureTimeMillis {
    runBlocking {
        val res = (1..10)
            .toList()
            .parMap { getDataAsync("$URL/$it") }
    }
    println(res)
}
println(" map  $parMapTime ")
      
      



Dispatchers.IO 2 ~ 610 . ! ( , excel ..) . , - .





Java ParallelStream

, stackowerflow parallelStream. , IDEA.





val javaParallelTime = measureTimeMillis { 
    val res = (1..10).toList()
        .parallelStream()
        .map { getDataAsync("$URL/$it") }
    res.forEach { println(it) }
}
println("Java parallelSrtream  $javaParallelTime ")
      
      



, . , . stream . , , , "" , Json.





, - , async . .





Os resultados podem ser vistos na tabela a seguir. Para mim, definitivamente decidi deixar o modo assíncrono . Principalmente por causa do tratamento de erros mais simples, é claro. E não há necessidade de ir além das corrotinas aqui.





Método





Tempo (ms)





Método assíncrono





1487





Implementação de Pmap da web





1523





Minha opção é parallelMap





610





Java.parallelStream





578





No futuro, há ideias para organizar isso em uma pequena biblioteca e usá-lo para fins pessoais e, claro, reescrever tudo do "código hindu" para o humano, desde que haja possibilidades suficientes. E, em seguida, carregue tudo para o vds.





Espero que minha experiência seja útil para alguém. Eu ficaria feliz em receber críticas e conselhos construtivos! Obrigado a todos








All Articles