Spring Cloud Gateway 微服务网关

本文详细介绍了Spring Cloud Gateway的使用,包括创建项目、配置路由、添加过滤器等。强调了过滤器在微服务网关中的重要性,如鉴权、限流和日志输出,并解释了Filter的生命周期和分类。还提到了如何自定义GatewayFilter和GlobalFilter,以及利用Nacos实现动态路由。最后展示了如何在Nacos中配置路由并测试。

Spring Cloud Gateway 微服务网关

  • 创建项目(命名 为 Gateway), 添加pom 依赖

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
      <version>2.2.0.RELEASE</version>
    </dependency>
    
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      <version>2.2.0.RELEASE</version>
    </dependency>
    
  • Router 路由器:包括,11种路由模式,具体参见官网

    • 添加路由配置: 意思是所有路径匹配到 /acc/** ,都会路由到 lb://account-service

      spring:
        cloud:
          gateway:
            routes:
            - id: host_route
              uri: lb://account-service
              predicates:
              - Path=/acc/**
      
    • 在启动类添加注解,服务注册和发现

      @EnableDiscoveryClient
      public class GatewayApplication {
      }
      
    • 测试

      • 默认路由地址:http://localhost:9001/account-service/acc/user?id=2
      • 使用Path路由配置:http://localhost:9001/acc/user?id=2
    • 打开gateway 监控端点 ,各端点介绍,参见Gateway管理API

      <!-- 添加 actuator 依赖 -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      
      #将 gateway 端点暴露
      management.endpoint.gateway.enabled=true 
      management.endpoints.web.exposure.include=gateway
      
      查询全局过滤器, http://localhost:9001/actuator/gateway/globalfilters
      查询路由中的过滤器, http://localhost:9001/actuator/gateway/routefilters
      刷新路由缓存, http://localhost:9001/actuator/gateway/refresh
      
      展示路由列表,  http://localhost:9001/actuator/gateway/routes
      获取单个路由的信息, http://localhost:9001/actuator/gateway/routes/host_route
      创建路由,发送Post请求,  ../actuator/gateway/routes/{id_route_to_create}
      删除路由,发送Delete请求: ../actuator/gateway/routes/{id_route_to_delete}
      
  • Filter 过滤器

    • 网关经常需要对路由请求进行过滤,如鉴权之后构造头部之类的,过滤的种类很多,如增加请求头、增加请求参数 、增加响应头和断路器等等功能,这就用到了Spring Cloud Gateway 的 Filter。

    • Filter 的作用:

      • 当我们有很多个服务时,客户端请求各个服务的Api时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。
      • 因此可以将这些功能等转移到网关处理以减少重复开发。
    • Filter 的生命周期

      • PRE: 这种过滤器在被代理的微服务(Proxied Service)执行之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
      • POST:这种过滤器在被代理的微服务(Proxied Service)执行完成后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
    • Filter 的分类

    • GatewayFilter:应用到单个路由或者一个分组的路由上。自定义的GatewayFilter,同样需要指定某个路由器

    • GlobalFilter:应用到所有的路由上。当请求到来时,Filtering Web Handler 处理器会添加所有 GlobalFilter 实例和匹配的 GatewayFilter 实例到过滤器链中。因此,自定义的GlobalFilter,不需要什么额外的配置。

    • 自定义 GatewayFilter,有两种实现方式

      • 一种是直接 实现GatewayFilter接口,不过还需要手动将自定义的GatewayFilter 注册到 router中

        // 统计某个或者某种路由的处理时长
        public class CustomerGatewayFilter implements GatewayFilter, Ordered {
            private static final Logger log = LoggerFactory.getLogger( CustomerGatewayFilter.class );
            private static final String COUNT_START_TIME = "countStartTime";
        
            @Override
            public Mono<Void> filter(ServerWebExchange exch, GatewayFilterChain chain) {
                return chain.filter(exch).then(
                    Mono.fromRunnable(() -> {
                      long startTime = exch.getAttribute(COUNT_START_TIME);
                      long endTime=(Instant.now().toEpochMilli() - startTime);
                      log.info(exch.getRequest().getURI().getRawPath() + ": " + endTime + "ms");
                  })
                );
            }
        
            @Override
            public int getOrder() {
                return 0;
          }
        }
        
      • 另一种是 自定义过滤器工厂(继承AbstractGatewayFilterFactory类) , 选择自定义过滤器工厂的方式,可以在配置文件中配置过滤器

        @Component
        public class CustomerGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomerGatewayFilterFactory.Config> {
        
            private static final Logger log = LoggerFactory.getLogger( CustomerGatewayFilterFactory.class );
            private static final String COUNT_START_TIME = "countStartTime";
        
            @Override
            public List<String> shortcutFieldOrder() {
                return Arrays.asList("enabled");
            }
        
            public CustomerGatewayFilterFactory() {
                super(Config.class);
                log.info("Loaded GatewayFilterFactory [CustomerGatewayFilterFactory]");
            }
        
            @Override
            public GatewayFilter apply(Config config) {
                return (exchange, chain) -> {
                    if (!config.isEnabled()) {
                      return chain.filter(exchange);
                    }
                    exchange.getAttributes().put(COUNT_START_TIME, 
                                                 System.currentTimeMillis());
                  
                  return chain.filter(exchange).then(
                        Mono.fromRunnable(() -> {
                            Long startTime = exchange.getAttribute(COUNT_START_TIME);
                            if (startTime != null) {
                                StringBuilder sb = new StringBuilder(
                                  exchange.getRequest().getURI().getRawPath()
                                ).append(": ")
                                 .append(System.currentTimeMillis() - startTime)
                                 .append("ms");
        
                                sb.append(" params:")
                                  .append(exchange.getRequest().getQueryParams());
        
                                log.info(sb.toString());
                            }}));
                };
            }
        
            public static class Config {
                //控制是否开启统计
                private boolean enabled;
                public Config() {}
                // getter setter 省略
            }
        }
        
        spring:
          cloud:
            gateway:
              routes:
                - id: elapse_route
                  uri: lb://account-service
                  filters:
                  - Customer=true
                  predicates:
                  - Method=GET
        
    • 自定义GlobalFilter : 可以在这里实现授权、日志等功能

      @Component
      public class AuthorizeFilter implements GlobalFilter, Ordered {
          private static final Logger log = LoggerFactory.getLogger(AuthorizeFilter.class);
          private static final String AUTHORIZE_TOKEN = "token";
      
          @Override
          public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
              String token = exchange.getRequest().getQueryParams()
                .getFirst(AUTHORIZE_TOKEN);
      
              if ( StringUtils.isBlank( token )) {
                  log.info( "token is empty ..." );
                  exchange.getResponse().setStatusCode( HttpStatus.UNAUTHORIZED );
                  return exchange.getResponse().setComplete();
              }
              return chain.filter(exchange);
          }
      
          @Override
          public int getOrder() {
              return 0;
          }
      }
      
  • Gateway + Nacos 实现动态路由 , 参见 springcloud-demo

    • 路由信息定义在配置文件中,这种方式有一个缺点就是修改路由信息必须重启服务才能生效。网关作为全部流量的入口,可用时间当然越长越好,不重启服务而修改路由是一个更好的选择,结合Nacos可以做到这一点。在Gateway项目中添加 nacos config 依赖,并配置nacos地址

      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
      </dependency>
      
      spring.cloud.nacos.config.server-addr=127.0.0.1:8848
      
    • Spring Cloud Gateway本身还不支持直接从Nacos动态加载路由配置表,需要自己编写监听器监听配置变化并刷新路由表

      @Component
      public class NacosDynamicRouteService implements ApplicationEventPublisherAware {
          @Override
          public void setApplicationEventPublisher(ApplicationEventPublisher aep) {
              this.applicationEventPublisher = aep;
          }
      
          private ApplicationEventPublisher applicationEventPublisher;
      
          private String dataId = "gateway-router";
          private String group = "DEFAULT_GROUP";
          @Value("${spring.cloud.nacos.config.server-addr}")
          private String serverAddr;
      
          @Autowired
          private RouteDefinitionWriter routeDefinitionWriter;
      
          private static final List<String> ROUTE_LIST = new ArrayList<>();
      
          @PostConstruct
          public void dynamicRouteByNacosListener() {
              try {
                  ConfigService configService = NacosFactory
                       .createConfigService(serverAddr);
      
                  // springboot 启动时,获取配置信息
                  String configInfo = configService.getConfig(dataId, group, 5000);
                  configRouter(configInfo);
                  // 监听 nacos 上的配置文件,当配置文件被修改后,获取配置信息
                  configService.addListener(dataId, group, new Listener() {
                      @Override
                      public void receiveConfigInfo(String configInfo) {
                          configRouter(configInfo);
                      }
      
                      @Override
                      public Executor getExecutor() {
                          return null;
                      }
                  });
              } catch (NacosException e) {
                  e.printStackTrace();
              }
          }
      
          private void configRouter(String configInfo){
              clearRoute();
              try {
                  List<RouteDefinition> gatewayRouteDefinitions = JSONObject.parseArray(
                      configInfo, RouteDefinition.class);
      
                  for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
                      addRoute(routeDefinition);
                  }
                  publish();
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          private void clearRoute() {
              for(String id : ROUTE_LIST) {
                  this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
              }
              ROUTE_LIST.clear();
          }
      
          private void addRoute(RouteDefinition definition) {
              try {
                  routeDefinitionWriter.save(Mono.just(definition)).subscribe();
                  ROUTE_LIST.add(definition.getId());
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          private void publish() {
              this.applicationEventPublisher.publishEvent(
                  new RefreshRoutesEvent(this.routeDefinitionWriter));
          }
      }
      
    • 在 Nacos 管理后台,创建配置文件 gateway-router.json ,内容如下。

      [{
          "id": "account-router",
          "order": 0,
          "predicates": [{"args": {"pattern": "/acc/**"},"name": "Path" }],
          "filters":[{"args": {"pattern": true },"name": "Customer" }],
          "uri": "lb://account-service"
      }]
      
    • 同时删除原来在 application.xml 中关于路由的配置

    • 测试:http://localhost:9001/acc/user?id=2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值