4 min to read
Servlet vs Reactive (WebFlux) - Wydajność
Wydajność reaktywnego stacku (hands-on)
Reaktywne programowanie od jakiegoś czasu jest często poruszanym tematem tak też, aby namacalnie poczuć różnicę w wydajności reaktywnego/blokującego stacku zrobiłem dla Ciebie ten mały projekt. Na początek powiemy sobie co jest zrobione oraz pokażę Ci drobne różnice implementacyjne w trzech aplikacjach jakie tu znajdziesz.
Co mamy?
product-store - reaktywna aplikacja (WebFlux), która zwraca nam produkty. Z ustawionym opóźnieniem 100ms.
spring-boot-web - blokująca aplikacja wraz z RestTemplate gdzie tworzymy zapytanie do product-store.
spring-boot-webflux - tak samo tylko reaktywnie. Korzystamy z WebClienta (czyli reaktywnego zamiennika na RestTemplate).
Kotlin + Gradle + Gatling (testy wydajnościowe - Scala)
Projekt znajdziesz na Githubie.
Różne implementacje
product-store
- zaimplementowane po staremu w nowym wydaniu. Znajdziemy tutaj stare i dobre adnotacje @RestController
@RequestMapping oraz inne. Słowem - wszystko co znamy z blokującego stacku. Jedyna różnica jest taka, że obiekty są
opakowane w Mono, albo Flux. Flux to trochę jak taka lista, która nie ma końca. Można by to nazwać strumieniem danych. Z
drugiej strony Mono to po prostu zero lub jeden element, w tym przypadku jest to produkt.
/** RestController **/
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun createProduct(@RequestBody newProduct: Mono<NewProduct>): Mono<Product> =
productService.createProduct(newProduct)
/** ProductService **/
fun createProduct(product: Mono<NewProduct>): Mono<Product> =
product.delayElement(Duration.ofMillis(100)).map {
Product(
name = it.name,
unitPrice = it.unitPrice
)
}
spring-boot-web
- zwykła blokująca aplikacja wraz z RestTemplate. Tworzymy tutaj request w postaci Product(name,
unitPrice), a w zwrotce (od product-store) dostajemy dodatkowo randomowy uuid Product(id, name, unitPrice).
/** RestController **/
@PostMapping
@ResponseStatus(HttpStatus.OK)
fun createProduct(@RequestBody newProduct: NewProduct): Product? =
productService.createProduct(newProduct)
/** RestClient **/
private val restTemplate = restTemplateBuilder.build()
fun createProduct(newProduct: NewProduct): Product? = restTemplate.postForEntity(
"$productStoreBaseUrl/products",
HttpEntity(newProduct),
Product::class.java
).body
spring-boot-webflux
- to samo co powyżej z tą różnicą, że reaktywnie. Jest tu najwięcej nowych zabawek. Po pierwsze
używamy tutaj DSLa (RouterFunctionDsl) od springa do tworzenia RouterFunctions, czyli to router { }. Druga rzecz to
użycie Scope Functions od Kotlina. Daje nam to tyle, że przekazujemy sobie obiekty w łańcuchu wywołań i nie musimy robić
tymczasowych zmiennych. Do tego oddzielamy dwie minimalnie różniące się logiki jedno to zapytanie, a drugie to odpowiedź
jaką zwracamy z naszego API. To czy warto to rozdzielić to oceń sam.
/** Router (albo RouterFunction) - czyli to samo co RestController **/
@Bean
fun router() = router {
accept(APPLICATION_JSON).nest {
POST("/products", productHandler::createProduct)
}
}
/** ReactiveRestClient **/
val webClient: WebClient = WebClient.builder()
.baseUrl(productStoreBaseUrl)
.build()
fun createProduct(req: ServerRequest): Mono<ServerResponse> = run {
webClient.post().uri("/products")
.body(req.bodyToMono(NewProduct::class.java))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(NewProduct::class.java)
}.let {
ServerResponse.ok().body(it)
}
Powyższe snippety to tylko wycinki najistotniejszych części aplikacji.
Tutaj znajdziesz kompletny kod.
Czas na wyniki - Servlet vs Reactive?
Servlet - 2000 użytkowniów robiących 200 requestów każdy.
Webflux - 2000 użytkowniów robiących 200 requestów każdy.
Servlet - 7500 użytkowniów robiących 50 requestów każdy.
Webflux - 7500 użytkowniów robiących 50 requestów każdy.
Wyniki
- Przy 7500 użytkownikach zbliżyłem się do ograniczeń swojego sprzętu - stąd też większa ilość błędów.
- Reaktywny stack obsłużył requesty mniej więcej dwa razy szybciej.
- Reaktywny był bardziej responsywny. Czasy odpowiedzi są lepsze. Jest to najbardziej widoczne przy dużym obciążeniu.
Projekt znajdziesz na Githubie.
Comments