Loading

02_Spring Cloud Gateway实战

微服务接入Spring Cloud Gateway

1) 引入依赖

        <!-- 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>
注意:getway网关的表现层使用的是webflux,会和spring-webmvc的依赖冲突,需要排除spring-webmvc。
或者指定使用Netty服务器:
spring:
  main:
    web-application-type: reactive # 指定使用Netty

2) 编写yml配置文件

spring:
  main:
    web-application-type: reactive # 指定使用Netty
  application:
    name: mall-gateway
  #配置nacos注册中心地址
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      #设置路由:路由id、路由到微服务的uri、断言
      routes:
        - id: order_route  #路由ID,全局唯一,建议配置服务名
          # 测试 http://localhost:8888/order/findOrderByUserId/1
          uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
          predicates:
            - Path=/order/**   # 断言,路径相匹配的进行路由
        - id: user_route   #路由ID,全局唯一,建议配置服务名
          uri: lb://mall-user  #lb 整合负载均衡器ribbon,loadbalancer
          predicates:
            - Path=/user/**   # 断言,路径相匹配的进行路由
3)测试:http://localhost:8888/order/findOrderByUserId/1

2.2 路由断言工厂(Route Predicate Factories)配置

predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地。application.yml配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
通过网关启动日志,可以查看内置路由断言工厂:

2.2.1 路径匹配

spring:
  main:
    web-application-type: reactive # 指定使用Netty
  application:
    name: mall-gateway
  #配置nacos注册中心地址
  cloud:
    gateway:
      #设置路由:路由id、路由到微服务的uri、断言
      routes:
        - id: order_route  #路由ID,全局唯一,建议配置服务名
          # 测试 http://localhost:8888/order/findOrderByUserId/1
          uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
          predicates:
            - Path=/order/**   # 断言,路径相匹配的进行路由

2.2.2 Header匹配

spring:
  main:
    web-application-type: reactive # 指定使用Netty
  application:
    name: mall-gateway
  #配置nacos注册中心地址
  cloud:
    gateway:
      #设置路由:路由id、路由到微服务的uri、断言
      routes:
        - id: order_route  #路由ID,全局唯一,建议配置服务名
          # 测试 http://localhost:8888/order/findOrderByUserId/1
          uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
          predicates:
            - Path=/order/**   # 断言,路径相匹配的进行路由
            # Header匹配  请求中带有请求头名为 x-request-id,其值与 \d+ 正则表达式匹配
            - Header=X-Request-Id, \d+
测试:
未添加请求头则断言失败

请求头中添加X-Request-Id则断言成功:

 

过滤器工厂(GatewayFilter Factories)配置

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

添加请求头

需求:给所有进入mall-order的请求添加一个请求头:X-Request-color=red。只需要修改gateway服务的application.yml文件,添加路由过滤即可: 
spring:
  application:
    name: mall-gateway
    gateway:
      #设置路由:路由id、路由到微服务的uri、断言
      routes:
        - id: order_route  #路由ID,全局唯一,建议配置服务名
          # 测试 http://localhost:8888/order/findOrderByUserId/1
          uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
          predicates:
            - Path=/order/**   # 断言,路径相匹配的进行路由
          filters:
            - AddRequestHeader=X-Request-color, red  #添加请求头
 
    @GetMapping("/test/gateway")
    public String testGateway(HttpServletRequest request) throws Exception {
        log.info("gateWay获取请求头X-Request-color:"
                + request.getHeader("X-Request-color"));
        return "success";
    }

    @GetMapping("/test/gateway/2")
    public String testGateway(@RequestHeader("X-Request-color") String color) throws Exception {
        log.info("gateWay获取请求头X-Request-color:" + color);
        return "success";
    }

结果:

添加请求参数:

spring:
  application:
    name: mall-gateway
  #配置nacos注册中心地址
  cloud:
    gateway:
      #设置路由:路由id、路由到微服务的uri、断言
      routes:
        - id: order_route  #路由ID,全局唯一,建议配置服务名
          # 测试 http://localhost:8888/order/findOrderByUserId/1
          uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
          predicates:
            - Path=/order/**   # 断言,路径相匹配的进行路由
          #配置过滤器工厂
          filters:
            - AddRequestParameter=color, blue # 添加请求参数

添加测试controller:

    @GetMapping("/test/gateway/3")
    public String testGateway3(@RequestParam("color") String color) throws Exception {
        log.info("gateWay获取请求参数color:" + color);
        return "success";
    }

测试:http://192.168.3.174:8888/order/test/gateway/3

自定义过滤器工厂

继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。
@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);
        };
    }
}

配置自定义的过滤器工厂 :

server:
  port: 8888
spring:
  application:
    name: mall-gateway
    gateway:
      #设置路由:路由id、路由到微服务的uri、断言
      routes:
        - id: order_route  #路由ID,全局唯一,建议配置服务名
          # 测试 http://localhost:8888/order/findOrderByUserId/1
          uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
          predicates:
            - Path=/order/**   # 断言,路径相匹配的进行路由
          #配置过滤器工厂
          filters:
            - CheckAuth=yyj, 男  #自定义过滤器工厂
测试:

全局过滤器(Global Filters)配置

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
GatewayFilter:网关过滤器,需要通过spring.cloud.routes.filters配置在具体的路由下,只作用在当前特定路由上,也可以通过配置spring.cloud.default-filters让它作用于全局路由上。
GlobalFilter:全局过滤器,不需要再配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain能够识别的过滤器。

LoadBalancerClientFilter

LoadBalancerClientFilter 会查看exchange的属性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值(一个URI),如果该值的scheme是 lb,
比如:lb://myservice ,它将会使用Spring Cloud的LoadBalancerClient 来将myservice 解析成实际的host和port,并替换掉ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的内容。
其实就是用来整合负载均衡器Ribbon的
server:
  port: 8888
spring:
  application:
    name: mall-gateway
    gateway:
      #设置路由:路由id、路由到微服务的uri、断言
      routes:
        - id: order_route  #路由ID,全局唯一,建议配置服务名
          # 测试 http://localhost:8888/order/findOrderByUserId/1
          uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
          predicates:
            - Path=/order/**   # 断言,路径相匹配的进行路由

自定义全局过滤器:

自定义全局过滤器定义方式是实现GlobalFilter接口。每一个过滤器都必须指定一个int类型的order值,order值越小,过滤器优先级越高,执行顺序越靠前。GlobalFilter通过实现Ordered接口来指定order值。
/**
 * 每一个过滤器都必须指定一个int类型的order值,order值越小,过滤器优先级越高,执行顺序越靠前。
 * GlobalFilter通过实现Ordered接口,来指定order值
 */
@Component
@Slf4j
public class CheckAuthFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取token
        String token = exchange.getRequest().getHeaders().getFirst("token");
        if (null == token) {
            log.info("token is null");
            ServerHttpResponse response = exchange.getResponse();
            response.getHeaders().add("Content-Type",
                    "application/json;charset=UTF-8");
            // 401 用户没有访问权限
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            byte[] bytes = HttpStatus.UNAUTHORIZED.getReasonPhrase().getBytes();
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            // 请求结束,不继续向下请求
            return response.writeWith(Mono.just(buffer));
        }
        //TODO 校验token进行身份认证
        log.info("校验token");
        return chain.filter(exchange);
    }

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

测试:

Gateway跨域配置(CORS Configuration)

在前端领域中,跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。同源策略(Same Orgin Policy)是一种约定,它是浏览器核心也最基本的安全功能,它会阻止一个域的js脚本和另外一个域的内容进行交互,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源(即在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)。
测试代码 :
order.html:
    <input type="button" value="订单列表" onclick="getData()">
    <script>
        function getData() {
            $.get('http://localhost:8888/order/findOrderByUserId/1',function(data){
                console.log(data.orders)
                var list = data.orders;
                var str = '';
                for(var i=0;i<list.length;i++){
                    str +='<tr><th>'+list[i].id+'</th><th>'+list[i].userId+'</th><th>'+list[i].commodityCode+'</th><th>'+list[i].count+'</th><th>'+list[i].amount+'</th></tr>';
                }
                $('#orderlist').html(str);
            });
        }
    </script>

如何解决gateway跨域问题:

通过yml配置的方式:

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration

spring:
  application:
    name: mall-gateway
  #配置nacos注册中心地址
  cloud:
    gateway:
      #跨越配置
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION

通过java配置的方式:

@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

Gateway基于redis+lua脚本限流

spring cloud alibaba官方提供了RequestRateLimiter过滤器工厂,基于redis+lua脚本方式采用令牌桶算法实现了限流。

1)添加依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

2)修改 application.yml ,添加redis配置和RequestRateLimiter过滤器工厂配置

spring:
  #配置redis地址
  redis:
    host: localhost
    port: 6379
    database: 0
    timeout: 5000
    lettuce:
      pool:
        max-active: 200
        max-wait: 10000
        max-idle: 100
        min-idle: 10
cloud:
    gateway:
      #设置路由:路由id、路由到微服务的uri、断言
      routes:
        - id: order_route  #路由ID,全局唯一,建议配置服务名
          uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
          predicates:
            - Path=/order/**   # 断言,路径相匹配的进行路由
          #配置过滤器工厂
          filters:
            - name: RequestRateLimiter   #限流过滤器
              args:
                redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充速率
                redis-rate-limiter.burstCapacity: 2 #令牌桶的总容量
                key-resolver: "#{@keyResolver}" #使用SpEL表达式,从Spring容器中获取Bean对象

3) 配置keyResolver,可以指定限流策略,比如url限流,参数限流,ip限流等等 

@Configuration
public class RateLimiterConfig {

    @Bean
    KeyResolver keyResolver() {
        //url限流
        return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
        //参数限流
//        return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getQueryParams().getFirst("user")));
    }
}
4) 测试

Gateway整合sentinel限流

从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组 
Gateway整合sentinel实现网关限流
1)引入依赖
     <!-- gateway接入sentinel  -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
2)添加yml配置,接入sentinel dashboard,通过sentinel控制台配置网关流控规则
server:
  port: 8888
spring:
  application:
    name: mall-gateway-sentinel-demo
  main:
    allow-bean-definition-overriding: true
  #配置nacos注册中心地址
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

    sentinel:
      transport:
        # 添加sentinel的控制台地址
        dashboard: 127.0.0.1:8080
      datasource:
        gateway-flow-rules:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}-gateway-flow-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: gw-flow
        gateway-api-rules:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}-gateway-api-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: gw-api-group
        degrade-rules:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: degrade
        system-rules:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}-system-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: system

    gateway:
      #设置路由:路由id、路由到微服务的uri、断言
      routes:
      - id: order_route  #路由ID,全局唯一,建议配合服务名
        uri: lb://mall-order  #lb 整合负载均衡器ribbon,loadbalancer
        predicates:
        - Path=/order/**

      - id: user_route
        uri: lb://mall-user  #lb 整合负载均衡器ribbon,loadbalancer
        predicates:
        - Path=/user/**
关于网关流控规则持久化需要自行进行改造(待补充)

Sentinel网关流控实现原理

当通过 GatewayRuleManager 加载网关流控规则(GatewayFlowRule)时,无论是否针对请求属性进行限流,Sentinel 底层都会将网关流控规则转化为热点参数规则(ParamFlowRule),存储在 GatewayRuleManager 中,与正常的热点参数规则相隔离。转换时 Sentinel 会根据请求属性配置,为网关流控规则设置参数索引(idx),并同步到生成的热点参数规则中。
外部请求进入 API Gateway 时会经过 Sentinel 实现的 filter,其中会依次进行 路由/API 分组匹配、请求属性解析和参数组装。Sentinel 会根据配置的网关流控规则来解析请求属性,并依照参数索引顺序组装参数数组,最终传入 SphU.entry(res, args) 中。Sentinel API Gateway Adapter Common模块向 Slot Chain 中添加了一个 GatewayFlowSlot,专门用来做网关规则的检查。
GatewayFlowSlot 会从 GatewayRuleManager 中提取生成的热点参数规则,根据传入的参数依次进行规则检查。若某条规则不针对请求属性,则会在参数最后一个位置置入预设的常量,达到普通流控的效果。

 

 

posted @ 2023-05-18 08:18  1640808365  阅读(36)  评论(0编辑  收藏  举报