关于springboot:非阻塞-SpringBoot-之-Kotlin-协程实现

7次阅读

共计 2398 个字符,预计需要花费 6 分钟才能阅读完成。

非阻塞 SpringBoot 之 Kotlin 协程实现

Why?

Spring Boot 默认应用 Servlet Web 服务器,Tomcat,每个申请调配一个线程。如果服务不是计算密集型,而是存在大量 I/O 期待,那么会节约大量 CPU 工夫,导致 CPU 利用率不高。如果强行加大线程池,会消耗大量内存,且减少线程切换的损耗。

于是,咱们能够思考应用 Reactive Web 服务器,Netty,基于事件循环,对于 I / O 密集型服务,性能极高。

背景介绍

咱们有个服务,须要封装调用大量内部接口,而后做防腐转换和数据聚合。随着业务变得复杂,接口响应速度越来越慢,无奈满足业务的时延需要。于是咱们开始了第一轮优化,应用CompletableFuture + 线程池进行并发调用。一番操作之后,时延降下来了,然而资源利用率不高,单个节点能接受的并发量很小。如果遇到搞流动,并发需要上升时,须要申请大量资源进行扩容,十分节约。

此时要问:为何做了异步化革新,并发能力还是上不来?

起因在于整个服务的模型还是阻塞式 I /O,异步调用的时候,尽管用了一个新线程,但调用过程还是 阻塞式 的,这条线程就被阻塞了。当服务并发升高时,线程池里就会产生大量被阻塞的线程,而这些线程不是 绿色线程(用户态线程),而是抢占式的,会分走贵重的 CPU 工夫,那么后果就是资源利用率低下,并发能力差了。

How?

为了解决 I / O 密集型服务并发能力低下的问题,能够改用响应式 (Reactive) 模型。实际上 Spring 很早就有相应的解决方案:Reactor + WebFlux,可实现非阻塞式 IO。

尽管响应式编程非常弱小,但也有其难点:不是过程式的,写业务代码很难懂,而且难以调试和测试。响应式编程不是本文的探讨重点,感兴趣的同学能够钻研一下,从最早的 RxJava 到目前的 Project Reactor。

那有没有更简略的计划?无妨看看:协程。

Next:Coroutines

Java 也有协程计划,叫 Quasar(协程在外面叫 Fiber),然而 18 年之后就没有更新了,据说作者跑去写 Project Loom 了。Loom 是下一代 Java 协程库,但目前还没有成熟,上生产是不可能的了。

尽管 Java 没有协程,然而 JVM 语言 Kotlin 有。上面就用 Kotlin Coroutines 联合 WebFlux 实现非阻塞式 SpringBoot 服务。

假如有个 API,/slowInt,通过 1s 返回一个整数。咱们要调两次,而后计算 sum。

响应工夫 1s 极其一点,不过测试的时候更容易看出区别

咱们无妨应用非阻塞式(WebClient)和阻塞式(RestTemplate)的 web 客户端,别离做性能测试。

import kotlinx.coroutines.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType.APPLICATION_JSON
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
import org.springframework.web.client.getForObject
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.awaitBody

@Service
class ExampleService {

    @Autowired
    lateinit var webClient: WebClient

    @Autowired
    lateinit var restTemplate: RestTemplate

     /**
     * 应用协程
     */
    suspend fun sumTwo(): Int = coroutineScope {// 别离异步调用,换成 getInt2() 再测一遍
        val i1: Deferred<Int> = async {getInt() }
        val i2: Deferred<Int> = async {getInt() }
        // 聚合
        i1.await() + i2.await()
    }

    /**
     * None-Blocking web client
     * very fast
     */
    suspend fun getInt(): Int {return webClient.get()
            .uri("/slowInt")
            .accept(APPLICATION_JSON)
            .retrieve().awaitBody()
    }

    /**
     * Blocking web client
     * very slow
     */
    fun getInt2(): Int {val result = restTemplate.getForObject<Int>("/slowInt").toInt()
        println(result)
        return result
    }
}
@RestController
class ExampleController {

    @Autowired
    lateinit var exampleService: ExampleService

    @GetMapping("/sum")
    suspend fun sum(): String? = "Sum: ${exampleService.sumTwo()}"
}

性能测试

而后用 JMeter 压一压。

对于阻塞式 IO,应用 10 并发,循环 10 次。后果如下:

非阻塞式,应用 100 并发,循环 10 次。后果如下:

采纳非阻塞式 IO,在大并发的状况下,均匀时延根本就 1s,与接口耗时吻合。

可见,响应工夫大幅降落,吞吐量大幅回升,从此不再结结巴巴。

参考文献

https://www.baeldung.com/kotl…

正文完
 0