你好,我是柳岸花开。
在微服务架构中,负载均衡是确保系统高可用性和可靠性的重要机制之一。然而,随着 Spring Cloud 的升级,默认不再支持同步调用,这给一些依赖同步调用的应用带来了挑战。在本文中,我们将介绍如何通过自定义阻塞负载均衡客户端来解决这一问题。
背景
Spring Cloud 在最新版本中默认使用响应式编程模型,不再支持同步调用。这对于一些依赖同步调用逻辑的应用来说,可能会导致不兼容问题。因此,我们需要自定义一个阻塞负载均衡客户端来实现同步调用。
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-epoll-35
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83)
at reactor.core.publisher.Mono.block(Mono.java:1742)
at org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient.choose(BlockingLoadBalancerClient.java:155)
at org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient.execute(FeignBlockingLoadBalancerClient.java:97)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:119)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
at org.springframework.cloud.openfeign.FeignCachingInvocationHandlerFactory$1.proceed(FeignCachingInvocationHandlerFactory.java:66)
at org.springframework.cache.interceptor.CacheInterceptor.lambda$invoke$0(CacheInterceptor.java:54)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:351)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64)
at org.springframework.cloud.openfeign.FeignCachingInvocationHandlerFactory.lambda$create$1(FeignCachingInvocationHandlerFactory.java:53)
at com.sun.proxy.$Proxy126.getPublicKey(Unknown Source)
自定义阻塞负载均衡客户端
代码实现
首先,我们来看一下自定义阻塞负载均衡客户端的完整代码:
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import reactor.core.publisher.Mono;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@Slf4j
public class CustomBlockingLoadBalancerClient extends BlockingLoadBalancerClient {
private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;
public CustomBlockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {
super(loadBalancerClientFactory);
this.loadBalancerClientFactory = loadBalancerClientFactory;
}
@Override
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
CompletableFuture<Response<ServiceInstance>> f = CompletableFuture.supplyAsync(() -> {
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
return loadBalancerResponse;
});
Response<ServiceInstance> loadBalancerResponse = null;
try {
loadBalancerResponse = f.get();
} catch (InterruptedException e) {
log.warn("LoadBalancerClient.choose error", e);
} catch (ExecutionException e) {
log.warn("LoadBalancerClient.choose error", e);
}
if (loadBalancerResponse == null) {
return null;
}
return loadBalancerResponse.getServer();
}
}
代码解读
-
类定义与继承
-
CustomBlockingLoadBalancerClient
类继承自BlockingLoadBalancerClient
,并使用了@Slf4j
注解来启用日志记录。
-
-
构造方法
-
构造方法接收一个 ReactiveLoadBalancer.Factory<ServiceInstance>
类型的参数,并调用父类构造方法进行初始化。
-
-
choose 方法
-
重写了 choose
方法,该方法根据服务 ID 和请求来选择服务实例。 -
通过 loadBalancerClientFactory
获取ReactiveLoadBalancer
实例,如果未找到,则返回null
。 -
使用 CompletableFuture
异步执行负载均衡选择,并阻塞等待结果。 -
处理可能的 InterruptedException
和ExecutionException
异常,并记录日志。 -
返回负载均衡选择的服务实例。
-
配置自定义负载均衡客户端
接下来,我们需要在 Spring 配置类中注册这个自定义的负载均衡客户端:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LoadBalancerConfig {
@Autowired
private LoadBalancerClientFactory loadBalancerClientFactory;
@Bean
public LoadBalancerClient blockingLoadBalancerClient() {
return new CustomBlockingLoadBalancerClient(loadBalancerClientFactory);
}
}
配置解读
-
类注解
-
使用 @Configuration
注解将该类标记为 Spring 配置类。
-
-
自动注入
-
使用 @Autowired
注解自动注入LoadBalancerClientFactory
实例。
-
-
Bean 定义
-
定义一个 blockingLoadBalancerClient
方法,并使用@Bean
注解将其注册为 Spring Bean。 -
该方法返回一个自定义的 CustomBlockingLoadBalancerClient
实例。
-
结论
通过自定义阻塞负载均衡客户端,我们可以解决 Spring Cloud 升级后不再支持同步调用的问题。这种方法不仅能保持现有业务逻辑的稳定性,还能利用 Spring Cloud 的新特性提升系统的灵活性和可维护性。如果您在升级 Spring Cloud 时遇到类似问题,不妨尝试这种解决方案。
希望这篇文章能帮助您在 Spring Cloud 项目中实现更高效的负载均衡策略。如果您有任何问题或建议,欢迎在评论区留言讨论。
👇关注我,下期了解👇 Nacos源码 回复 222,获取Java面试题合集 关于我 一枚爱折腾的Java程序猿,专注Spring干货。把路上的问题记录下来,帮助那些和我一样的人。 好奇心强,喜欢并深入研究古天文。 崇尚 个人系统创建,做一些时间越长越有价值的事情。思考 把时间留下来 又 每刻都是新的。
本文由 mdnice 多平台发布