Я сделал простой POC, и все работает точно так же с веб-клиентом и шаблоном отдыха для конфигурации по умолчанию.
Код оставшегося сервера:
@SpringBootApplication
internal class RestServerApplication
fun main(args: Array<String>) {
runApplication<RestServerApplication>(*args)
}
class BeansInitializer : ApplicationContextInitializer<GenericApplicationContext> {
override fun initialize(context: GenericApplicationContext) {
serverBeans().initialize(context)
}
}
fun serverBeans() = beans {
bean("serverRoutes") {
PingRoutes(ref()).router()
}
bean<PingHandler>()
}
internal class PingRoutes(private val pingHandler: PingHandler) {
fun router() = router {
GET("/api/ping", pingHandler::ping)
}
}
class PingHandler(private val env: Environment) {
fun ping(serverRequest: ServerRequest): Mono<ServerResponse> {
return Mono
.fromCallable {
// sleap added to simulate some work
Thread.sleep(2000)
}
.subscribeOn(elastic())
.flatMap {
ServerResponse.ok()
.syncBody("pong-${env["HOSTNAME"]}-${env["server.port"]}")
}
}
}
В application.yaml add:
context.initializer.classes: com.lbpoc.server.BeansInitializer
Зависимости в gradle:
implementation('org.springframework.boot:spring-boot-starter-webflux')
Остальной код клиента:
@SpringBootApplication
internal class RestClientApplication {
@Bean
@LoadBalanced
fun webClientBuilder(): WebClient.Builder {
return WebClient.builder()
}
@Bean
@LoadBalanced
fun restTemplate() = RestTemplateBuilder().build()
}
fun main(args: Array<String>) {
runApplication<RestClientApplication>(*args)
}
class BeansInitializer : ApplicationContextInitializer<GenericApplicationContext> {
override fun initialize(context: GenericApplicationContext) {
clientBeans().initialize(context)
}
}
fun clientBeans() = beans {
bean("clientRoutes") {
PingRoutes(ref()).router()
}
bean<PingHandlerWithWebClient>()
bean<PingHandlerWithRestTemplate>()
}
internal class PingRoutes(private val pingHandlerWithWebClient: PingHandlerWithWebClient) {
fun router() = org.springframework.web.reactive.function.server.router {
GET("/api/ping", pingHandlerWithWebClient::ping)
}
}
class PingHandlerWithWebClient(private val webClientBuilder: WebClient.Builder) {
fun ping(serverRequest: ServerRequest) = webClientBuilder.build()
.get()
.uri("http://rest-server-poc/api/ping")
.retrieve()
.bodyToMono(String::class.java)
.onErrorReturn(TimeoutException::class.java, "Read/write timeout")
.flatMap {
ServerResponse.ok().syncBody(it)
}
}
class PingHandlerWithRestTemplate(private val restTemplate: RestTemplate) {
fun ping(serverRequest: ServerRequest) = Mono.fromCallable {
restTemplate.getForEntity("http://rest-server-poc/api/ping", String::class.java)
}.flatMap {
ServerResponse.ok().syncBody(it.body!!)
}
}
In application.yaml add:
context.initializer.classes: com.lbpoc.client.BeansInitializer
spring:
application:
name: rest-client-poc-for-load-balancing
logging:
level.org.springframework.cloud: DEBUG
level.com.netflix.loadbalancer: DEBUG
rest-server-poc:
listOfServers: localhost:8081,localhost:8082
Зависимости в gradle:
implementation('org.springframework.boot:spring-boot-starter-webflux')
implementation('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
Вы можете попробовать его с двумя или более экземплярами для сервера, и он будет работать точно так же с веб-клиентом и шаблоном отдыха.
По умолчанию используется лента zoneAwareLoadBalancer, и если у вас есть только одна зона, все экземпляры для сервера будут зарегистрированы в «неизвестной» зоне.
Возможно, у вас возникла проблема с сохранением соединений через веб-клиент.Веб-клиент повторно использует одно и то же соединение в нескольких запросах, остальные шаблоны этого не делают.Если у вас есть какой-то прокси между вашим клиентом и сервером, у вас могут возникнуть проблемы с повторным использованием соединений через веб-клиент.Чтобы проверить это, вы можете изменить bean-компонент веб-клиента следующим образом и запустить тесты:
@Bean
@LoadBalanced
fun webClientBuilder(): WebClient.Builder {
return WebClient.builder()
.clientConnector(ReactorClientHttpConnector { options ->
options
.compression(true)
.afterNettyContextInit { ctx ->
ctx.markPersistent(false)
}
})
}
Конечно, это не хорошее решение для производства, но, выполнив его, вы можете проверить, есть ли у вас проблемы с конфигурацией внутри вашего клиентского приложения.или, возможно, проблема снаружи, что-то между вашим клиентом и сервером.Например, если вы используете kubernetes и регистрируете свои сервисы в обнаружении сервисов, используя IP-адрес узла сервера, то каждый вызов такого сервиса будет проходить через балансировщик нагрузки kube-proxy и будет (по умолчанию используется циклический перебор) направляться в какой-то модуль.сервис.