04:服务网关Spring Cloud Gateway

一:Gateway

1.1:GetWay概念

Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

一句话概括:SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。

1.2:核心概念(组成)

1.2.1:路由

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

1.2.2:断言

断言函数允许开发者去自定义匹配Http request中的任何信息,比如请求头和参数等等。

过滤器

SpringCloud GetWay中filter分为GetWay Filter和Global Filter,filter可以对请求和响应进行处理。

1.3:Spring Cloud Gateway 具有如下特性:

  • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定 Predicate(断言)和 Filter(过滤器);
  • 集成Hystrix的断路器功能;
  • 集成 Spring Cloud 服务发现功能;
  • 易于编写的 Predicate(断言)和 Filter(过滤器);
  • 请求限流功能;
  • 支持路径重写。

1.4:服务网关GetWay实现原理-流程图

动态路由+过滤链实现的,再yml中配置过滤,断言,可以过滤掉一些非法请求。Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。

1:请求进来通过webflux+reactor(响应式编程)

2:然后调用DispatcherHandler.handle(请求接受转发处理器)

3:这个方法中调用了mapping.getHandler方法,这个方法主要就是获取我们配置文件中配置的路由信息。包括断言,过滤的信息。将其转为Route对象,过滤一下然后返回。然后将这个route对象放入请求上下文的Getway_Route_Attribut里面。

4:然后调用invokeHandler,这个方法就是拿到请求上下文中的Getway_Route_Attribut里面的Route对象。就可以得到我们Route对象中的过滤器,将这个过期加入全局过滤器List集合中,然后返回一个过滤器链条,这链条里面就是放的全局过滤器List集合中的过滤器。遍历这个链条执行filter过滤方法。然后调用一个具体的服务。

5:在通过我们的路由和过滤后,filter过滤方法就会调用到loadBalancer的chooes方法。

6:这个choose方法中的getserver方法getLoadBalancer通过轮询或者我们修改后的负载均衡的策略找到我们对应的一台应用实例,

7:然后通过loadBalancer的reconstruactURI方法将getway的url替换为对应的后台机器的url。然后继续调用过滤链中的下一个filter过滤器。最后调用到NettyRoutingFilter。通过httpclient调用url到具体的机器。

1.5:代码示例git仓库-cloud-gateway-gateway9527模块

1.6:自定义路由断言工厂

自定义路由断言工厂需要继承AbstractRoutePredicateFactory ,重写apply方法,在apply方法中可以通过exchange.getRequest()拿到ServerHttpRequest对象,从而可以获取到请求的参数,请求方式,请求头等信息。

  • 此类必须是spring能扫描到的即bean
  • 类必须加上RoutePredicateFactory作为结尾。
  • 类必须继承AbstractRoutePredicateFactory,重写apply方法。
  • 必须声明静态内部类,声明属性来接受配置文件中对应的断言信息。
  • 需要结合shortcutFieldOrder进行绑定。
  • 通过apply方法进行判断,true就是匹配成功,false就是匹配失败。

1.6.1:代码-Factory

java 复制代码
package com.zgs.springcloud.predicate;

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {

    public MyRoutePredicateFactory() {
        super(MyRoutePredicateFactory.Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("");
    }

    @Override
    public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                if (config.getName().equalsIgnoreCase("test")) {
                    return true;
                } else {
                    return false;
                }
            }
        };
    }


    //用来接受配置文件中断言信息
    @Validated
    public static class Config {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

1.6.2:代码-断言配置

java 复制代码
spring:
	cloud:
		gateway:
			routes:
				 - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址 lb 使用负载均衡策略
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由
            #- After=2021-08-10T05:04:07.722+08:00[Asia/Shanghai]
            #- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai]
            #- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
            #- Cookie=username,zgs
            #- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式
            #- Host=**.zgs.com
            #- Method=GET
            - Query=username, \d+  # 要有参数名username并且值还要是整数才能路由

1.6.3:代码-自定义过滤器工厂

需要继承AbstractGatewayFilterFactory,名称必须由GatewayFilterFactory结束并且交给spring管理

java 复制代码
package com.zgs.springcloud.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {

    public MyGatewayFilterFactory() {
        super(MyGatewayFilterFactory.Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("value");
    }

    @Override
    public GatewayFilter apply(MyGatewayFilterFactory.Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange,
                                     GatewayFilterChain chain) {
                String name = exchange.getRequest().getQueryParams().getFirst("name");

                if (Objects.equals(name, config.getValue())) {
                    return chain.filter(exchange);
                } else {
                    exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
                    return exchange.getResponse().setComplete();
                }
            }
        };
    }

    public static class Config {

        private String value;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }

}

1.6.4:代码-全局过滤器

java 复制代码
package com.zgs.springcloud.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Date;
import java.util.UUID;

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("################" + new Date() + ":执行了全局过滤器:[" + UUID.randomUUID() + "]###################");
        String zgs = exchange.getRequest().getQueryParams().getFirst("zgs");
        if (zgs == null) {
            log.info("****用户名为null,无法登录");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

1.6.5:过滤配置

java 复制代码
spring:
	cloud:
		gateway:
			routes:
				- id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
	#         uri: http://localhost:8001          #匹配后提供服务的路由地址
	          uri: lb://cloud-payment-service #匹配后提供服务的路由地址 lb 使用负载均衡策略
	          filters:
	            - RewritePath=/payment_routh/(?<segment>.*), /$\{segment}
	#            - Addrequestheader=X-Request-color,red #添加请求头
	            - name: RequestRateLimiter
	              args:
	                # 使用SpEL从容器中获取对象pathKeyResolver,根据这个对象来进行限流
	                key-resolver: '#{@pathKeyResolver}'
	                # 令牌桶每秒填充平均速率
	                redis-rate-limiter.replenishRate: 1
	                # 令牌桶的总容量
	                redis-rate-limiter.burstCapacity: 3

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

局部:局部针对某个路由,需要在路由中进行配置。

全局:针对所有路由请求,一旦定义就会生效。

1.7:GetWay跨域处理

1.7.1:配置文件

java 复制代码
spring:
	cloud:
	    gateway:
	      globalcors:
	        cors-configurations:
	          '[/**]':  #允许跨域访问的资源
	            allowedOrigins: "*" #跨域允许来源
	            allowedMethods:
	              - GET
	              - POST
	              - PUT

1.7.2:配置类

java 复制代码
package com.zgs.springcloud.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("*/**", corsConfiguration);
        return new CorsWebFilter(source);
    }
}

1.8:GetWay整合Nacos

java 复制代码
 #配合使用nacos 避免写死路径,使用nacos可以从nacos中根据服务名获取路径
spring:
	cloud:
		nacos:
	      discovery:
	        server-addr: localhost:8848
	        uaername: nacos
	        password: nacos

1.9:GetWay整合sentinel流控降级

1.9.1:添加依赖

XML 复制代码
  <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-sentinel-gateway -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
            <version>2021.0.4.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2021.1</version>
        </dependency>

1.9.2:添加配置

java 复制代码
spring:
  application:
    name: cloud-gateway
    # get配置
  cloud:
    # 配置sentinel
    sentinel:
      transport:
        dashboard: localhost:8858
相关推荐
Rain的Java大神之路12 小时前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
需要82616 小时前
MySQL 索引 B+ 树与“查询为什么变慢了
java·spring boot·spring·spring cloud·tomcat
需要82620 小时前
Arthas 一个命令排查线上问题
java·jvm·spring·spring cloud
苏渡苇2 天前
Spring Insight 里如何把 Span 画成瀑布时间线
后端·spring·spring cloud·springboot·监控·apm
叶总没有会2 天前
Nacos—注册中心
spring cloud·微服务
苏渡苇3 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring·spring cloud·系统监控·apm
叶总没有会3 天前
微服务--分布式基础
java·spring·spring cloud·微服务
孙启超4 天前
【AI开发之Rust】第 7 课:错误处理 —— panic、Result 与 `?`
人工智能·分布式·后端·爬虫·spring cloud·架构·rust
行百里er5 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring cloud·监控
砍材农夫5 天前
spring|spring web|拦截器、过滤器、aop
java·spring boot·spring·spring cloud