网关Spring Cloud Gateway的配置和使用

1. 什么是Spring Cloud Gateway?

       Spring Cloud Gateway 是 Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul。相比 Zuul 来说,Spring Cloud Gateway 提供更优秀的性能,更强大的有功能。

       Spring Cloud Gateway是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。

Gateway 的核心概念如下:

  • 路由(route) :
    • 路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
  • 断言(predicates) :
    • Java8中的断言函数,允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
  • 过滤器(Filter)
    • SpringCloud Gateway中的filter分为Gateway FilIerGlobal Filter。Filter可以对请求和响应进行处理。

       路由route是想要访问的目标地址,断言predicates是匹配请求中的一些信息,满足条件才允许访问目标地址,而Gateway FilIer网关过滤器可以为请求做某些扩展,比如添加一些请求头、请求参数等等,但Gateway FilIer只能作用于对应路由下的请求!

       而GlobalFilter 全局过滤器和 GatewayFilter 网关过滤器虽然有一样的接口定义,只不过, GlobalFilter 会作用于所有路由。

       
Gateway 的工作原理如下:

在这里插入图片描述
其实Gateway对请求的处理与spring mvc的处理差不多!也是一套流程下来的

框架Gatewayspring mvc
请求分发DispatcherHandlerDispatcherServlet
请求映射HandlerMappingHandlerMapping
请求适配HanderAdaperHanderAdaper
请求处理WebHanderHander

       

2. Gateway与zuul的区别

       Spring Cloud Gateway是Spring Cloud 的一个子项目。而zuul则是netflix公司的项目,只是spring将zuul集成在 Spring Cloud 中使用而已。因为zuul2.0连续跳票和zuul1.0的性能表现不是很理想,所以催生了spring团队开发了Gateway项目。

Zuul:

  • 使用的是阻塞式的 API,不支持长连接,比如 websockets
  • 底层是servlet,Zuul处理的是http请求
  • 没有提供异步支持,流控等均由hystrix支持。
  • 依赖包spring-cloud-starter-netflix-zuul

Gateway:

  • Spring Boot和Spring Webflux提供的Netty底层环境,不能和传统的Servlet容器一起使用,也不能打包成一个war包。
  • 依赖spring-boot-starter-webfluxspring-cloud-starter-gateway
  • 提供了异步支持,提供了抽象负载均衡,提供了抽象流控,并默认实现了RedisRateLimiter

相同点:

  • 底层都是servlet
  • 两者均是web网关

不同点:

  • 适用性

    • gateway 对比 zuul 多依赖了spring-webflux,在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件
    • zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等。
  • 是否支持异步

    • zuul仅支持同步
    • gateway支持异步。理论上gateway则更适合于提高系统吞吐量(但不一定能有更好的性能),最终性能还需要通过严密的压测来决定
  • 框架设计的角度

    • gateway具有更好的扩展性,并且其已经发布了2.0.0的RELESE版本,稳定性也是非常好的
  • 性能

    • gateway是基于spring-webflux的响应式、非阻塞编程,且支持异步,底层通过netty通信,性能较高。与Spring紧密集成,所以将会是一个更好的开发体验。
    • Zuul 1.x,是一个基于阻塞io的API Gateway。Zuul已经发布了Zuul 2.x,基于Netty,也是非阻塞的,支持长连接,但Spring Cloud暂时还没有整合计划。

       

3. Gateway的配置和使用

  • ①:引入依赖
    注意:由于Gateway不能在传统的 servlet 容器中工作,也不能构建成 war 包,会和spring-webmvc的依赖冲突,所以不能引入 web 环境依赖
   <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
    </properties>
    <dependencyManagement>
        <dependencies>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!-- gateway网关 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!-- nacos服务注册与发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

  • ②:编写yml配置文件
spring:
  application:
    name: mall-gateway
  # 配置nacos注册中心地址
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

    gateway:
      discovery:
        locator:
          # 默认为false,设为true开启通过微服务创建路由的功能,即可以通过微服务名访问服务
          # http://localhost:8888/mall-order/order/findOrderByUserId/1
          enabled: false
      # 是否开启网关
      enabled: true
      # 网关配置跨域
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
            - GET
            - POST
            - DELETE
            - PUT
            - OPTION

      # 设置路由:路由id、路由到微服务的uri、断言
      routes:
      - id: order_route  #路由ID,全局唯一,建议配合服务名
#       # uri: http://localhost:8020  #目标微服务的请求地址和端口
        uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
        predicates:
#        # Path路径匹配
        - Path=/order/**
        # 测试 http://localhost:8888/order/findOrderByUserId/1
        # 匹配在指定的日期时间之后发生的请求  入参是ZonedDateTime类型
        - After=2021-05-16T20:50:57.511+08:00[Asia/Shanghai]
        # Cookie匹配
        - Cookie=username, fox
        # Header匹配  请求中带有请求头名为 x-request-id,其值与 \d+ 正则表达式匹配
        - Header=X-Request-Id, \d+
        #自定义CheckAuth断言工厂
                - name: CheckAuth
                  args:
                    name: fox
        - CheckAuth=abc
        #配置过滤器工厂
        filters:
        - AddRequestHeader=X-Request-color, red  #添加请求头
        - AddRequestParameter=color, blue   # 添加请求参数
        - PrefixPath=/mall-order  # 添加前缀 对应微服务需要配置context-path
        #- RedirectTo=302, http://baidu.com  #重定向到百度
        - CheckAuth=aaa,  #配置自定义的过滤器工厂

      - id: user_route
        uri: lb://mall-user  #lb 整合负载均衡器ribbon,loadbalancer
        predicates:
        - Path=/user/**

启动应用!配置即可生效

       

①:常用的路由断言工厂

       更多路由断言工厂请参考spring官网 https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories,下面仅列出几种常用的断言类型!

  • 时间匹配:可以用在限时抢购的一些场景中。

     # 匹配在指定的日期时间之后发生的请求  入参是ZonedDateTime类型        
     - After=2021-01-31T22:22:07.783+08:00[Asia/Shanghai]
    

    其中 获取ZonedDateTime类型的指定日期时间代码如下:

    ZonedDateTime zonedDateTime = ZonedDateTime.now();//默认时区
    // 用指定时区获取当前时间
    ZonedDateTime zonedDateTime2 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
    
  • Cookie匹配:请求头必须加上 Cookie :aaa,否则请求失败!

    # Cookie匹配        
    - Cookie=username, aaa
    
  • Header匹配:请求头必须加上 X-Request-Id:2(整数),否则请求失败!

     # Header匹配  请求中带有请求头名为 x-request-id,其值与 \d+ 正则表达式匹配        
     - Header=X-Request-Id, \d+
    
  • 路径匹配:只有是 order/** 的请求,才能通过断言

    # 测试:http://localhost:8888/order/findOrderByUserId/1        
    - Path=/order/**   #Path路径匹配
    
  • 自定义路由断言工厂
    上面所有的断言配置都继承自AbstractPredicateFactory抽象工厂,如下所示:
    在这里插入图片描述
           所以当我们有个性化需求需要自定义自定义路由断言工厂时,也可参照上述配置进行定制,需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。

    注意: 命名需要以 RoutePredicateFactory 结尾

    自定义断言工厂:判断参数name是否是aaa!

    /**
     * 自定义RoutePredicateFactory
     */
    @Component
    @Slf4j
    public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
    
        public CheckAuthRoutePredicateFactory() {
            super(Config.class);
        }
    
        @Override
        public Predicate<ServerWebExchange> apply(Config config) {
            return new GatewayPredicate() {
    
                @Override
                public boolean test(ServerWebExchange serverWebExchange) {
                    log.info("调用CheckAuthRoutePredicateFactory" + config.getName());
                    if(config.getName().equals("aaa")){
                        return true;
                    }
                    return false;
                }
            };
        }
    
        /**
         * 快捷配置
         * @return
         */
        @Override
        public List<String> shortcutFieldOrder() {
            return Collections.singletonList("name");
        }
    
        /**
         * 需要定义一个内部类,该类用于封装application.yml中的配置
         */
        public static class Config {
    
            private String name;
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
        }
    }
    

    yml中配置

            predicates:
            
            - Path=/order/**
            # 自定义CheckAuth断言工厂
                    - name: CheckAuth
                      args:
                        name: aaa
           # 自定义CheckAuth断言工厂快捷配置! 二选一
           # - CheckAuth=aaa
    

       

②:常用的过滤器工厂(GatewayFilters)

       SpringCloudGateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加或去除参数等

  • 添加请求头

    filters:
    - AddRequestHeader=X-Request-color, red  #添加请求头
    
  • 添加请求参数

    filters:
    - AddRequestParameter=color, blue   # 添加请求参数
    
  • 为匹配的路由统一添加前缀

    filters:
    - PrefixPath=/mall-order  # 添加前缀 比如某个微服务需要配置context-path
    
  • 重定向操作

    filters:
    - RedirectTo=302, http://baidu.com  #重定向到百度
    
  • 自定义过滤器工厂
    与自定义断言工厂类似,自定义过滤器工厂需要继承AbstractNameValueGatewayFilterFactory抽象类,且自定义类名必须要以GatewayFilterFactory结尾并交给spring管理。重写apply方法后如下:

    自定义过滤器工厂:为当前路由下的请求添加参数namesex参数,参数值为 aaa

    @Component
    @Slf4j
    public class CheckAuthGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
    
        @Override
        public GatewayFilter apply(NameValueConfig config) {
            return (exchange, chain) -> {
                log.info("调用CheckAuthGatewayFilterFactory==="
                        + config.getName() + ":" + config.getValue());
                // TODO
                return chain.filter(exchange);
            };
        }
    }
    

    yml配置

    spring:
      cloud:
        gateway:
          routes:
          - id: order_route  #路由ID,全局唯一,建议配合服务名
            uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
            filters:
            - CheckAuth=aaa,男  #配置自定义的过滤器工厂
    

       

③:全局过滤器配置(Global Filters)

       GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过 GlobalFilter 会作用于所有路由。比如:LoadBalancerClientFilter负载均衡过滤器 lb://mall-order。自定义的全局过滤器需要实现GlobalFilter接口,并重写filter方法!
在这里插入图片描述

注意:全局过滤器(Global Filters)不需要在yml中配置!!

官方声明:GlobalFilter的接口定义以及用法在未来的版本可能会发生变化。

  • 自定义校验token的全局过滤器

    @Component
    @Order(-1)
    @Slf4j
    public class CheckAuthFilter implements GlobalFilter {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //校验请求头中的token
            List<String> token = exchange.getRequest().getHeaders().get("token");
            log.info("token:"+ token);
            if (token.isEmpty()){
                return chain.filter(exchange);
            }
            // TODO token校验
    
            return chain.filter(exchange);
        }
    }
    
  • 自定义IP全局过滤器,黑白名单

    @Component
    @Slf4j
    public class CheckIPFilter implements GlobalFilter, Ordered {
    
    	// 执行顺序控制!
        @Override
        public int getOrder() {
            return 0;
        }
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            HttpHeaders headers = exchange.getRequest().getHeaders();
            //模拟对 IP 的访问限制,即不在 IP 白名单中就不能调用的需求
            if (getIp(headers).equals("127.0.0.1")) {
                log.info("======非法访问======");
                ServerHttpResponse response = exchange.getResponse();
                byte[] bytes = new String("======非法访问======").getBytes();
                response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                DataBuffer buffer = response.bufferFactory().wrap(bytes);
                response.getHeaders().add("Content-Type",
                        "application/json;charset=UTF-8");
                return exchange.getResponse().writeWith(Mono.just(buffer));
            }
            return chain.filter(exchange);
        }
    
        private String getIp(HttpHeaders headers) {
            return headers.getHost().getHostName();
        }
    }
    

    测试结果: 如果请求ip127.0.0.1则不允许访问!如果是localhost就可以访问!

       

4. 网关高可用解决方案

       为了保证 Gateway 的高可用性,可以同时启动多个 Gateway 实例进行负载,在 Gateway 的上游使用 Nginx 进行负载转发以达到高可用。
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值