springcloud-gateway-源码之降级篇

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springcloud-gateway-源码之降级篇相关的知识,希望对你有一定的参考价值。

参考技术A 这篇文章主要从源码的角度剖析gateway是这么对我们的请求进行降级的

springcloud-gateway 熔断降级也是基于hystrix实现的

Hystrix是Netflix开源的一个限流熔断的项目、主要有以下功能:

隔离(线程池隔离和信号量隔离):限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。

优雅的降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。

熔断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。

先来看一张图回顾下Hystrix熔断处理的过程

熟悉Hystrix的同学对这张图是不是很熟悉?

好了,回顾了Hystrix的流程之后,我们再正式探索下springcloud-gateway的熔断原理

首先springcloud-gateway的熔断是依赖filter实现的,给我们提供了一个名为HystrixGatewayFilterFactory的工厂类,这个类的apply方法返回一个GatewayFilter类的匿名对象来处理请求,我们来看下这个类的apply方法内容

这里红框出构建了RouteHystrixCommand对象,而HystrixObservableCommand的继承关系

可见它继承了HystrixObservableCommand这个类,是不是和上图完美契合呢?

源码中这个匿名的过滤器中调用了这个command.toObservable方法并监听其事件,最终调用的是AbstractCommand的toObservable方法

我们看下

跟进去发现调用了 applyHystrixSemantics方法如图

继续,发现调用OnError方法

再进入调用如下

不多说继续

最后回到RouteHystrixCommand的resumeWithFallback方法

留意红框处获取DispatcherHandler对象并调用其handle方法,

遍历handlerMappiing这里匹配的mapping为RouterFunctionMapping返回HandlerFunction对象,我这里是HystrixFallbackHandler对象

根据HystrixFallbackHandler对象匹配到HandlerFunctionAdapter并调用其handle方法跟进去最终调用的是HystrixFallbackHandler的handle方法

最后返回HandlerResult对象给DispatcherHandler,完成整个降级的过程。

SpringCloud-Gateway实现网关

网关作为流量的入口,常用的功能包括路由转发、权限校验、限流等

Spring Cloud 是Spring官方推出的第二代网关框架,由WebFlux+Netty+Reactor实现的响应式的API网关,它不能在传统的servlet容器工作,也不能构建war包。基于Filter的方式提供网关的基本功能,例如说安全认证、监控、限流等。

一、功能特征

  • 基于Spring Framework5、Project Reactor和SpringBoot2.0进行构建

  • 动态路由:能够匹配任何请求属性

  • 支持路径重写

  • 集成Spring Cloud服务发现功能(Nacos)

  • 可集成流控降级功能(Sentinel)

  • 可以对路由指定易于编写的Predicate(断言)和Filter(过滤器)

1、路由:

路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成,如果断言为真,则说明请求的URL和配置的路由匹配。

2、断言:

Java8的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。

3、过滤器:

SpringCloud Gateway中的Filter分为Gateway Filter和Global Filter。Filter可以对请求和响应进行处理。

官网文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

二、工作原理

Gateway的工作原理和Zuul的差不多,最大区别就是Gateway的Filter只有pre和post两种。

客户端向Spring Cloud Gateway发出请求,如何请求与网关程序定义的路由匹配,则该请求就会被发送到网关Web处理程序,此时处理程序运行特定的请求过滤器链,过滤器之间用虚线分开的原因是过滤器可能会在发送代理请求的前后处理逻辑。所有pre过滤器逻辑先执行,然后执行代理请求;代理请求完成后,执行post过滤器逻辑组。

本案例未融合Cloud体系,只是使用Gateway网关功能

三、项目案例

  1. pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway</name>
    <description>gateway</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-web</artifactId>-->
<!--            <exclusions>-->
<!--                &lt;!&ndash; 排除掉springmvc相关的配置信息 &ndash;&gt;-->
<!--                <exclusion>-->
<!--                    <groupId>org.springframework</groupId>-->
<!--                    <artifactId>spring-webmvc</artifactId>-->
<!--                </exclusion>-->
<!--                &lt;!&ndash; 排除掉tomcat相关的配置 &ndash;&gt;-->
<!--                <exclusion>-->
<!--                    <groupId>org.springframework.bootk</groupId>-->
<!--                    <artifactId>spring-boot-starter-tomcat</artifactId>-->
<!--                </exclusion>-->
<!--                <exclusion>-->
<!--                    <groupId>org.apache.tomcat.embed</groupId>-->
<!--                    <artifactId>tomcat-embed-core</artifactId>-->
<!--                </exclusion>-->
<!--                <exclusion>-->
<!--                    <groupId>org.apache.tomcat.embed</groupId>-->
<!--                    <artifactId>tomcat-embed-el</artifactId>-->
<!--                </exclusion>-->
<!--                <exclusion>-->
<!--                    <groupId>org.apache.tomcat.embed</groupId>-->
<!--                    <artifactId>tomcat-embed-websocket</artifactId>-->
<!--                </exclusion>-->
<!--            </exclusions>-->
<!--        </dependency>-->

<!--        &lt;!&ndash; https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-loadbalancer &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-loadbalancer</artifactId>-->
<!--            <version>2.2.5.RELEASE</version>-->
<!--        </dependency>-->

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-gateway-core -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>3.1.5</version>
        </dependency>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.75.Final</version>
        </dependency>

        <!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-webflux</artifactId>-->
<!--            <version>2.2.6.RELEASE</version>-->
<!--        </dependency>-->

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  1. yml文件

spring:
  application:
    name: spring-cloud-gateway-sample
  cloud:
    gateway:
      routes:
        - id: test
          uri: http://www.baidu.com
          predicates:
            - Path=/api/**
          filters:
            - StripPrefix=1
server:
  port: 9090

启动项目,访问http://localhost:9090/api 会重定向到自己指定的链接

四、路由断言工厂配置

  1. 官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

  1. 自定义路由断言工厂

继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑和shortcutFieldOrder方法。

在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。

注意事项:类必须是Spring组件;类必须以RoutePredicateFactory作为结尾;类必须继承AbstractRoutePredicateFactory;必须声明静态内部类,声明属性接受配置文件中的信息

@Slf4j
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> 
 
 
    public CheckAuthRoutePredicateFactory() 
        super(Config.class);
        log.info("Loaded RoutePredicateFactory [CheckAuth]");
    
 
    @Override
    public List<String> shortcutFieldOrder() 
        return Arrays.asList("name");
    
 
 
    @Override
    public Predicate<ServerWebExchange> apply(Config config) 
        return exchange -> 
            if (config.getName().equals("mengmeng")) 
                return true;
            
            return false;
        ;
    
 
 
    public static class Config 
 
        private String name;
 
        public void setName(String name) 
            this.name = name;
        
 
        public String getName() 
            return name;
        
    
 

五、过滤器工厂配置

1.官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

内置过滤器列表:https://blog.csdn.net/swiftxx/article/details/120545261

2.过滤器有分为三类:

  • 默认过滤器

  • 自定义过滤

  • 全局过滤器

3.过滤器执行顺序:

  • 全局过滤器与其他2类过滤器相比,永远是最后执行的;它的优先级只对其他全局过滤器起作用

  • 当默认过滤器与自定义过滤器的优先级一样时,优先出发默认过滤器,然后才是自定义过滤器;同类型的过滤器,出发顺序与他们在配置文件中声明的顺序一致

  • 默认过滤器与自定义过滤器使用同样的order顺序空间,即他们会按照各自的顺序来进行排序

4.自定义全局过滤器

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered 

    @Autowired
    ObjectMapper objectMapper;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) 
        String token = exchange.getResponse().getHeaders().getFirst("token");//获取第一个名为token的请求头
        //无权限
        if (StringUtils.isBlank(token)) 
            // 如果消息头中没有 token ,则抛出异常
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            String result = "";
            try 
                Map<String, Object> map = new HashMap<>(16);
                map.put("code", HttpStatus.UNAUTHORIZED.value());
                map.put("msg", "当前请求未认证,不允许访问");
                map.put("data", null);
                result = objectMapper.writeValueAsString(map);
             catch (JsonProcessingException e) 
                log.error(e.getMessage(), e);
            
            DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Flux.just(buffer));
        
        //有权限
        return chain.filter(exchange);
    

    @Override
    public int getOrder() 
        //值越小,越优先执行
        return 1;
    

局部过滤器与全局过滤器区别:

  • 局部:针对某个路由请求

  • 全局:针对所有路由请求

相关链接:https://blog.csdn.net/qq_43437874/article/details/121626379

全局请求日志打印:https://blog.csdn.net/qq_39529562/article/details/108911943

以上是关于springcloud-gateway-源码之降级篇的主要内容,如果未能解决你的问题,请参考以下文章

Dubbo之降级Mock源码分析

4.Sentinel源码分析— Sentinel是如何做到降级的?

SpringCloud-Gateway

SpringCloud-Gateway 网关路由断言过滤

springcloud-gateway集成knife4j

黑马学SpringCloud-Gateway