解决 Spring Cloud 升级后的gateway同步调用问题:自定义阻塞负载均衡客户端

你好,我是柳岸花开。

在微服务架构中,负载均衡是确保系统高可用性和可靠性的重要机制之一。然而,随着 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();
    }
}

代码解读

  1. 类定义与继承

    • CustomBlockingLoadBalancerClient 类继承自 BlockingLoadBalancerClient,并使用了 @Slf4j 注解来启用日志记录。
  2. 构造方法

    • 构造方法接收一个 ReactiveLoadBalancer.Factory<ServiceInstance> 类型的参数,并调用父类构造方法进行初始化。
  3. choose 方法

    • 重写了 choose 方法,该方法根据服务 ID 和请求来选择服务实例。
    • 通过 loadBalancerClientFactory 获取 ReactiveLoadBalancer 实例,如果未找到,则返回 null
    • 使用 CompletableFuture 异步执行负载均衡选择,并阻塞等待结果。
    • 处理可能的 InterruptedExceptionExecutionException 异常,并记录日志。
    • 返回负载均衡选择的服务实例。

配置自定义负载均衡客户端

接下来,我们需要在 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);
    }
}

配置解读

  1. 类注解

    • 使用 @Configuration 注解将该类标记为 Spring 配置类。
  2. 自动注入

    • 使用 @Autowired 注解自动注入 LoadBalancerClientFactory 实例。
  3. Bean 定义

    • 定义一个 blockingLoadBalancerClient 方法,并使用 @Bean 注解将其注册为 Spring Bean。
    • 该方法返回一个自定义的 CustomBlockingLoadBalancerClient 实例。

结论

通过自定义阻塞负载均衡客户端,我们可以解决 Spring Cloud 升级后不再支持同步调用的问题。这种方法不仅能保持现有业务逻辑的稳定性,还能利用 Spring Cloud 的新特性提升系统的灵活性和可维护性。如果您在升级 Spring Cloud 时遇到类似问题,不妨尝试这种解决方案。

希望这篇文章能帮助您在 Spring Cloud 项目中实现更高效的负载均衡策略。如果您有任何问题或建议,欢迎在评论区留言讨论。

👇关注我,下期了解👇 ​ Nacos源码 ​ ​ alt ​ ​ 回复 222,获取Java面试题合集 ​ 关于我 ​ 一枚爱折腾的Java程序猿,专注Spring干货。把路上的问题记录下来,帮助那些和我一样的人。 ​ 好奇心强,喜欢并深入研究古天文。 ​ 崇尚 个人系统创建,做一些时间越长越有价值的事情。思考 把时间留下来 又 每刻都是新的。

本文由 mdnice 多平台发布

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值