【深入解析spring cloud gateway】13 Reactive Feign的使用

问题引入

在gateway中如果使用feignClient的话,会报如下错误

bash 复制代码
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3
	at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.4.15.jar:3.4.15]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ HTTP GET "/get" [ExceptionHandlingWebHandler]

其报错的原因是:网关的reactive线程模型,并不支持像openfeign这样的阻塞IO的操作。

网上给出了一种解决方案

解决方案之一

方案1:自定义一个BlockingLoadBalancerClient.java Bean覆盖原有Bean

step1:创建自定义类CustomBlockingLoadBalancerClient.java

CustomBlockingLoadBalancerClient.java继承BlockingLoadBalancerClient.java,并重写方法BlockingLoadBalancerClient#choose(java.lang.String, org.springframework.cloud.client.loadbalancer.Request)

java 复制代码
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
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 org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import reactor.core.publisher.Mono;

import java.util.concurrent.CompletableFuture;

/**
 * @Author: ekko
 * @Description: 自定义CustomBlockingLoadBalancerClient.java
 * @Date: 2023/9/20 16:16
 */
public class CustomBlockingLoadBalancerClient extends BlockingLoadBalancerClient {
    private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;

    public CustomBlockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory, LoadBalancerProperties properties) {
        super(loadBalancerClientFactory, properties);
        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) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (loadBalancerResponse == null) {
            return null;
        }
        return loadBalancerResponse.getServer();
    }
}

step2: 创建BlockingLoadBalancerClient类

将自定义CustomBlockingLoadBalancerClient注入到容器中

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: ekko
 * @Description: BlockingLoadBalancerClient配置类,创建自定义的BlockingLoadBalancerClient Bean
 * @Date: 2023/9/20 16:31
 */
@Configuration
public class BlockingLoadBalancerClientConfig {

    @Autowired
    LoadBalancerClientFactory loadBalancerClientFactory;

    @Autowired
    LoadBalancerProperties properties;

    @Bean
    public LoadBalancerClient BlockingLoadBalancerClient() {
        return new CustomBlockingLoadBalancerClient(loadBalancerClientFactory, properties);
    }
}

这种方式可以解决报错的问题。BUT,在gateway网关中强行使用feignClient,同步调用,其实是有风险的一个事情。假设feignClient的下游服务,由于某些原因导致性能变慢。而gateway是同步阻塞式的调用。那么gateway的主线程也会被阻塞。由于gateway底层实际上就是netty的线程池,有两个线程池(主从多线程模型)这种模型使用一个独立的线程池来处理连接请求(Acceptor),而I/O操作则由另一个线程池处理。这种方式可以减少连接请求处理对I/O操作的干扰,提高系统并发性能。

更优雅的解决方案-feign-reactive

好了,切入正题,优雅的解决方案,当然还是回归到使用Reactive 异步调用的feign了。

看看官网是怎么说的:

https://docs.spring.io/spring-cloud-openfeign/reference/spring-cloud-openfeign.html

bash 复制代码
As the OpenFeign project does not currently support reactive clients, such as Spring WebClient, neither does Spring Cloud OpenFeign.

SinceSpring Cloud OpenFeign project is now considered feature-complete, we're not planning on adding support even if it becomes available in the upstream project. We suggest migrating over to Spring Interface Clients instead. Both blocking and reactive stacks are supported there.

Until that is done, we recommend using feign-reactive for Spring WebClient support.

翻译一下就是,open-feign并不支持reactive异步客户端。如果要使用reactive 异步客户端,请移步 feign-reactive

网上关于feign-reactive这方面的介绍实在太少,这里做一下介绍吧。

引入依赖

xml 复制代码
 <artifactId>gateway</artifactId>
    <properties>
        <reactor.feign.version>3.2.6</reactor.feign.version>
    </properties>
    <dependencies>
        <!--reactivefeign-->
        <dependency>
            <groupId>com.playtika.reactivefeign</groupId>
            <artifactId>feign-reactor-spring-configuration</artifactId>
            <version>${reactor.feign.version}</version>
        </dependency>
        <dependency>
            <groupId>com.playtika.reactivefeign</groupId>
            <artifactId>feign-reactor-webclient</artifactId>
            <version>${reactor.feign.version}</version>
        </dependency>
        <dependency>
            <groupId>com.playtika.reactivefeign</groupId>
            <artifactId>feign-reactor-cloud</artifactId>
            <version>${reactor.feign.version}</version>
        </dependency>
        <!--reactivefeign-->

定义reactivefeign的client接口

java 复制代码
@ReactiveFeignClient(name = "hello-service")
public interface TestFeignClient {
    @RequestMapping(value = "/hello1", method = RequestMethod.GET)
    Mono<String> hello(@RequestParam("name") String name);
}

返回结果一定要用Mono包装。

其下游对应着一个hello-service的接口,如下所示

java 复制代码
    @RequestMapping(value = "/hello1", method = RequestMethod.GET)
    public String hello(@RequestParam("name") String name) {
        return helloService.hello(name);
    }

启用reactivefeign的client接口

java 复制代码
@Configuration(proxyBeanMethods = false)
@EnableReactiveFeignClients(clients = {TestFeignClient.class})
public class FeignClientsConfig {

}

在filter中调用feignClient

java 复制代码
@Slf4j
public class FeignTestFilter implements GlobalFilter, Ordered {
    private final TestFeignClient testFeignClient;

    public FeignTestFilter(TestFeignClient testFeignClient) {
        this.testFeignClient = testFeignClient;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Mono<String> mono = testFeignClient.hello("zhangsan");
        return mono.doOnNext(e -> {
            log.info("feignClient请求结果是:" + e);
        }).then(chain.filter(exchange));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

这里由于是reactive的写法。如果想先处理返回值 ,处理完后,再执行后续的filter,那么写法就如上面代码。

如果不太会这种写法,请参考Reactive 官网相关的文档

调用效果

通过网关访问任意下游的接口,日志输出如下

总结

  • 从使用方式来看,其实reactivefeign和openFeign很相似,RestFull的接口和我们平常写的注解一样。
  • 注意返回值需要用Mono封装。在处理请求结果时,也要用reactive的写法。
  • 通过这种reactive的写法,当我们下游feign调用的微服务变慢,并不会影响gateway的主线程,并不会拖垮网关
相关推荐
还是大剑师兰特1 小时前
Spring面试题及详细答案 125道(1-15) -- 核心概念与基础1
spring·大剑师·spring面试题·spring教程
python_1364 小时前
web请求和响应
java·spring·github
sniper_fandc4 小时前
Spring Cloud系列—Alibaba Sentinel授权与规则管理及推送
spring cloud·sentinel
ciku5 小时前
Spring AI Starter和文档解读
java·人工智能·spring
javadaydayup5 小时前
Apollo 凭什么能 “干掉” 本地配置?
spring boot·后端·spring
耳东哇9 小时前
spring ai-openai-vl模型应用qwen-vl\gpt-文字识别-java
java·人工智能·spring
long31620 小时前
java 策略模式 demo
java·开发语言·后端·spring·设计模式
柏油21 小时前
Spring @Cacheable 解读
redis·后端·spring
柏油1 天前
Spring @TransactionalEventListener 解读
spring boot·后端·spring
鼠鼠我捏,要死了捏1 天前
Spring WebFlux 性能优化实践指南
reactive·performance-optimization·spring-webflux