• Spring Cloud Gateway详解


    引言

      在传统的单体项目中,前端和后端的交互相对简单,只需通过一个调用地址即可实现。然而,随着微服务架构的兴起,后端服务被拆分成多个微服务,这给前端带来了新的挑战:需要记住每个微服务的地址才能进行交互。显然,这种做法不仅繁琐,还容易导致前端代码的不可维护性和耦合度的增加。为了解决这一问题,网关应运而生!

      在微服务中,比较常见的网关有Spring Cloud GatewayNetflix Zuul等等,由于Zuul已经停止维护了,并且Spring Cloud Gateway 在性能、灵活性、易用性和社区支持等方面都具有优势,所以现目前常用Gateway作为微服务的网关。

    Gateway搭建

    版本说明:

    -cloud.version>Greenwich.SR5-cloud.version>
    -cloud-alibaba.version>2.1.2.RELEASE-cloud-alibaba.version>
    

    创建一个Gateway微服务项目,引入依赖

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
    dependencies>
    

    再写个启动类,启动即可

    下面介绍Gateway的配置三要素,路由(route)断言(Predicate )过滤器(filter)

    路由(route)

      在网关中,路由决定了请求应该被转发到哪个后端服务处理,路由功能使得网关能够将请求动态地转发到不同的后端服务,从而实现请求的分发和负载均衡。

    从代码中不难看出,路由的配置主要有下面几个属性
    在这里插入图片描述

    spring:
      cloud:
        gateway:
          ## 路由
          routes:
            ## id名称任意,唯一即可
            - id: gateway-service-01
              ## 路由转发的uri
              uri: http://localhost:8011
              
    
            - id: gateway-service-02
              uri: http://localhost:8012
              
    

      routes下面是可以配置多个路由的,如上图所示,那到底什么时候走8011,什么时候走8012呢,这就需要用到Gateway中非常重要的组件,Predicate断言!

    断言(Predicate )

      简单来说,Gateway的断言就是一种条件判断,用来控制请求向那个路由转发,可以根据需要灵活地定制路由规则。

      在Gateway中,内置的断言有很多,可以根据不同的属性进行配置,提供的路由断言工厂有如下几个:
    在这里插入图片描述

      比如有根据请求路径的PathRoutePredicateFactory,有根据请求方法的MethodRoutePredicateFactory,有根据请求头的HeaderRoutePredicateFactory,等等,因为这些断言的命名都是有规范的,都是xxxRoutePredicateFactory的格式,所以在配置的时候只需要写前面部分的名称就行了,比如Path,Method。

    好,现在我们就来配置一个根据Path的断言

    spring:
      cloud:
        gateway:
          ## 路由
          routes:
            ## id名称任意,唯一即可
            - id: gateway-service-01
              ## 路由转发的uri
              uri: http://localhost:8011
              ## 配置断言
              predicates:
                ## 满足/api/orderService/** 这个请求路径的,都会被路由到http://localhost:8011
                - Path=/api/orderService/**
    
            - id: gateway-service-02
              uri: http://localhost:8012
              ## 配置断言
              predicates:
                ## 满足/api/userService/** 这个请求路径的,都会被路由到http://localhost:8012
                - Path=/api/userService/**
    

    当然,断言可以配置多个,之间是and的关系,比如我们可以配置权重比例

    spring:
      cloud:
        gateway:
          ## 路由
          routes:
            ## id名称任意,唯一即可
            - id: gateway-service-01
              ## 路由转发的uri
              uri: http://localhost:8011
              ## 配置断言
              predicates:
                ## 满足/api/orderService/** 这个请求路径的,都会被路由到http://localhost:8011
                - Path=/api/orderService/**
                
                
            - id: gateway-service-02
              uri: http://localhost:8012
              ## 配置断言
              predicates:
                ## 满足/api/userService/** 这个请求路径的,都会被路由到http://localhost:8012
                - Path=/api/userService/**
                ## 同一分组按照权重进行分配流量,这里分配了60%
                ## 第一个userGroup是分组名,第二个参数是权重
                - Weight=userGroup, 6
            
            - id: gateway-service-03
              uri: http://localhost:8013
              predicates:
                - Path=/api/userService/**
                - Weight=userGroup, 4    
    

      这里路由gateway-service-02和gateway-service-03断言的Path都是一样的,但是加了Weight权重,当大量请求过来时,路由到8012的流量大约有60%,路由到8013的流量大约有40%,做到负载均衡的作用。

    下面建立OrderService和UserService服务进行验证

    // userService
    @RestController
    @RequestMapping("/api/userService")
    @Slf4j
    public class UserController {
    
        @GetMapping("/test")
        public ResponseEntity<String> testOrder() {
            log.info("进入userService...");
            return ResponseEntity.ok().body("进入userService");
        }
    }
    
    // orderService
    @RestController
    @RequestMapping("/api/orderService")
    @Slf4j
    public class OrderController {
    
        @GetMapping("/test")
        public ResponseEntity<String> testOrder(){
            log.info("进入orderService...");
            return ResponseEntity.ok().body("进入orderService");
        }
    }
    

    我这里启动网关服务,一个订单服务,两个用户服务
    在这里插入图片描述

    启动两个userService是为了测试权重的配置

    输入地址,注意这里是网关的IP+端口

    http://localhost:8010/api/orderService/test
    在这里插入图片描述

    http://localhost:8010/api/userService/test
    在这里插入图片描述

    网关配置的Path起到了效果

    再测试下权重的配置

    连续访问http://localhost:8010/api/userService/test 10次
    在这里插入图片描述
    在这里插入图片描述

    可以看出和我们在网关配置的比例大致相等,做到了按权重分发请求流量

    自定义断言

    接下来,我们自己定义一个断言来试试

    /**
     * 自定义一个指定时间访问的断言
     * 1.类名称必须是配置+RoutePredicateFactory
     * 2.必须继承AbstractRoutePredicateFactory<自定义的内部类>
     */
    @Component
    public class MyHourRoutePredicateFactory extends AbstractRoutePredicateFactory<MyHourRoutePredicateFactory.MyConfig> {
    
        /**
         * 必须用无参构造
         */
        public MyHourRoutePredicateFactory() {
            super(MyHourRoutePredicateFactory.MyConfig.class);
        }
    
        /**
         * 通过apply进行逻辑判断 true就是匹配成功 false匹配失败
         *
         * @param config
         * @return
         */
        @Override
        public Predicate<ServerWebExchange> apply(MyHourRoutePredicateFactory.MyConfig config) {
            return new Predicate<ServerWebExchange>() {
                @Override
                public boolean test(ServerWebExchange serverWebExchange) {
                    // 获取当前时间
                    LocalDateTime now = LocalDateTime.now();
                    int hour = now.getHour();
                    // 判断时间是否大于配置的时间
                    if (hour >= config.getMyHour()) {
                        return true;
                    }
                    return false;
                }
            };
        }
    
        /**
         * 读取配置文件的参数值,赋值到配置类中的属性上
         *
         * @return
         */
        @Override
        public List<String> shortcutFieldOrder() {
            // 顺序必须必须和yml文件中的配置顺序一致
            return Arrays.asList("myHour");
        }
    
    
        /**
         * 接收配置参数
         */
        @Data
        @NoArgsConstructor
        public static class MyConfig {
            private int myHour;
        }
    
    }
    
    

    我们直接在OrderService增加这个配置

    spring:
      cloud:
        gateway:
          ## 路由
          routes:
            ## id名称任意,唯一即可
            - id: gateway-service-01
              ## 路由转发的uri
              uri: http://localhost:8011
              ## 配置断言
              predicates:
                ## 满足/api/orderService/** 这个请求路径的,都会被路由到http://localhost:8011
                - Path=/api/orderService/**
                ## 自定义的断言 当前时间在16点后才允许访问
                - MyHour=16  
    

    在这里插入图片描述

    如上图所示,当前时间是15点,未到16点,不能访问,我再把时间配置为15点试试
    在这里插入图片描述

    顺利进入!

    过滤器(filter)

      接下来,介绍下另外一个重要组件,过滤器,路由过滤器会对请求或响应做相应的处理,Gateway目前有30多种内置的过滤器,具体可参考 https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

    其中分为局部过滤器和全局过滤器。

    常见的局部过滤器:

    名称说明
    AddRequestHeaderGatewayFilterFactory添加一个请求头
    RemoveRequestHeaderGatewayFilterFactory移除一个请求头
    RemoveResponseHeaderGatewayFilterFactory移除一个响应头
    RequestRateLimiterGatewayFilterFactory请求限流设置
    AddResponseHeaderGatewayFilterFactory添加一个响应头

    我们简单举个例子,使用AddResponseHeaderGatewayFilterFactory,为请求添加响应头

    配置的时候还是和断言一样的,只需要写前面的AddResponseHeader即可

    spring:
      cloud:
        gateway:
          ## 路由
          routes:
            ## id名称任意,唯一即可
            - id: gateway-service-01
              ## 路由转发的uri
              uri: http://localhost:8011
              ## 配置断言
              predicates:
                ## 满足/api/orderService/** 这个请求路径的,都会被路由到http://localhost:8011
                - Path=/api/orderService/**
                - MyHour=15
              # 过滤器配置(局部)
              filters:
                - AddResponseHeader=myKey,myVal
    

    访问下就可以看到设置的值已经在响应头了
    在这里插入图片描述

    但是对于其他的请求是没有的,下面将配置一个默认过滤器配置 ,也是个全局过滤器,默认过滤器default-filters的配置和routes是平级的。

    server:
      port: 8010
    
    spring:
      cloud:
        gateway:
          ## 路由
          routes:
            ## id名称任意,唯一即可
            - id: gateway-service-01
              ## 路由转发的uri
              uri: http://localhost:8011
              ## 配置断言
              predicates:
                ## 满足/api/orderService/** 这个请求路径的,都会被路由到http://localhost:8011
                - Path=/api/orderService/**
                - MyHour=15
    
            - id: gateway-service-02
              uri: http://localhost:8012
              ## 配置断言
              predicates:
                ## 满足/api/userService/** 这个请求路径的,都会被路由到http://localhost:8012
                - Path=/api/userService/**
                ## 同一分组按照权重进行分配流量,这里分配了60%
                ## 第一个userGroup是分组名,第二个参数是权重
                - Weight=userGroup, 6
    
            - id: gateway-service-03
              uri: http://localhost:8013
              predicates:
                - Path=/api/userService/**
                - Weight=userGroup, 4
            # 默认过滤器,对所有路由生效
          default-filters:
            - AddResponseHeader=myKey,myVal
    
    

    在这里插入图片描述
    在这里插入图片描述

    配置一个,全局生效

    自定义全局过滤器

      和断言一样,也可以自定义过滤器,自定义局部过滤器需要extends AbstractGatewayFilterFactory,自定义全局过滤器需要实现GlobalFilter ,我们这里定义一个全局的过滤器吧。

    /**
     * 全局过滤器 认证
     */
    @Component
    @Order(-1)
    public class MyGatewayFilter implements GlobalFilter {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 1.获取请求参数
            MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
            // 2.获取user参数
            String userName = params.getFirst("name");
            if ("root".equals(userName)) {
                return chain.filter(exchange);// 继续执行后续的过滤器
            }
            // 3.拦截
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);// 禁止访问
            return exchange.getResponse().setComplete();
        }
    }
    

    在这里插入图片描述
    在这里插入图片描述

    我们可以定义多个过滤器,那么Order的值就决定了执行的先后顺序

    Order的值越大,优先级越低,值越小,优先级越高

    定义Order的值有两种方式

    • 实现Ordered接口,重写getOrder方法
    • 实用@Order注解

    过滤器的执行顺序:默认过滤器(default-filters) ----> 局部过滤器------> 全局过滤器

  • 相关阅读:
    牛客打开摄像头几秒后画面消失 | 相机打开画面一闪一闪
    王道3.2 队列
    数据库安全:Hadoop 未授权访问-命令执行漏洞.
    Linux下FastDFS的安装和配置
    红绿正方形染色问题
    数据结构-链表的简单操作代码实现3-LinkedList【Java版】
    [机缘参悟-48]:鬼谷子-第七揣篇-提前收集信息、提前做出预判和实施计划
    BAT034:批处理打开电脑常用功能面板
    【回眸】写篇博客记录一下嵌入式软件实习萌新的具体工作之使用AURIX Development Studio编译
    LSM Tree
  • 原文地址:https://blog.csdn.net/weixin_44328487/article/details/138707181