• Gateway服务网关


      Zuul1.X是netflix公司开发的网关组件。在升级2.X,并且在2.X中引入了许多新的思想,更新比较慢。基于BIO,同步阻塞模型。

      gateway是spring公司自己开发的网关组件。基于netty,netty本身是一个NIO框架。

      官网:https://spring.io/projects/spring-cloud-gateway

    1.简介

    1.什么是gateway  

      SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。其核心逻辑是路由转发+执行过滤器链。

      SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

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

    注意:Spring Cloud Gateway 底层使用了高性能的通信框架Netty。

    2.特性

    SpringCloud官方,对SpringCloud Gateway 特征介绍如下:

    (1)基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0

    (2)集成 Hystrix 断路器

    (3)集成 Spring Cloud DiscoveryClient

    (4)Predicates 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters

    (5)具备一些网关的高级功能:动态路由、限流、路径重写

    简单说明一下上文中的三个术语:

    (1)Filter(过滤器):

    和Zuul的过滤器在概念上类似,可以使用它拦截和修改请求,并且对上游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。

    (2)Route(路由):

    网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。

    (3)Predicate(断言):(面向函数编程,实际是返回bool类型的一个方法)

    这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。

     3.和zuul区别

      Zuul1已经进入维护阶段,而且Gateway是Springcloud团队研发的。

      SpringCloud Gateway和Zuul主要的区别,还是在底层的通信框架上。

      Zuul1是一个基于阻塞IO的API网关,基于Servlet2.5使用阻塞架构,不支持长连接(如websocket)等。Zuul的设计模式和Nginx较像,每次IO都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成。Spring中集成的Zuul版本,采用的是Tomcat容器,使用的是Servlet IO处理模型。

      Gateway基于非阻塞模型进行开发,性能方面不需要担心。Servlet3.1之后有了异步非阻塞的支持。WebFlux是一个典型的异步非阻塞框架。SpringWebflux是Spring5.0引入的新的响应式框架,区别于SpringMVC,它不需要ServletAPI,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。

    4.Springcloud工作流程

    (1) 客户端向gateway发出请求。然后在Gateway Handler Mapping中找到与请求匹配的路由,将其发送到Gateway Web Handler

    (2)Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。(过滤器链之间虚线分开是因为可能会在发送代理请求之前"pre"或者之后"post"来执行业务逻辑)。可以在pre类型的过滤器链做参数校验、权限校验、流量监控、日志输出、协议转换等等。在"post"类型的过滤器链可以做响应内容、响应头的修改、日志输出、流量监控等等。

    2.使用

    1.新建子模块cloud-gateway-gateway9527

     2.修改pom

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud</artifactId>
            <groupId>cn.qz.cloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-gateway-gateway9527</artifactId>
    
        <dependencies>
            <!--gateway-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
            <!--eureka-client-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!--引入自己抽取的工具包-->
            <dependency>
                <groupId>cn.qz.cloud</groupId>
                <artifactId>cloud-api-commons</artifactId>
                <version>${project.version}</version>
            </dependency>
            <!--一般基础配置类-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    </project>

    3.修改yml

    server:
      port: 9527
    
    spring:
      application:
        name: cloud-gateway
      cloud:
        gateway:
          routes:
            - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              uri: http://localhost:8081          #匹配后提供服务的路由地址
              predicates:
                - Path=/pay/listAll/**         # 断言,路径相匹配的进行路由
    
            - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              uri: http://localhost:8081          #匹配后提供服务的路由地址
              predicates:
                - Path=/pay/getServerPort/**         # 断言,路径相匹配的进行路由
    
    eureka:
      instance:
        hostname: cloud-gateway-service
      client: #服务提供者provider注册进eureka服务列表内
        service-url:
          register-with-eureka: true
          fetch-registry: true
          defaultZone: http://localhost:7001/eureka

      上面相当于配置了两条路由。实际上相当于从9527端口转发到8081端口。

    4.启动类:

    package cn.qz.cloud;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    /**
     * @Author: qlq
     * @Description
     * @Date: 22:29 2020/10/20
     */
    @SpringBootApplication
    @EnableEurekaClient
    public class GateWayMain9527 {
        public static void main(String[] args) {
            SpringApplication.run(GateWayMain9527.class, args);
        }
    }

    5.启动后测试:

    liqiang@root MINGW64 ~/Desktop
    $ curl http://localhost:9527/pay/getServerPort
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    52    0    52    0     0   1106      0 --:--:-- --:--:-- --:--:--  3250{"success":true,"code":"200","msg":"","data":"8081"}
    
    liqiang@root MINGW64 ~/Desktop
    $ curl http://localhost:9527/pay/listAll
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100   406    0   406    0     0   6444      0 --:--:-- --:--:-- --:--:--  8638{"success":true,"code":"200","msg":"","data":[{"createtime":"2020-09-25T06:58:21.000+0000","serial":"测试","id":1},{"createtime":"2020-09-25T07:06:10.000+0000","serial":"测试","id":2},{"createtime":"2020-09-25T14:34:43.000+0000","serial":"测试1测试测试序列好23456","id":3},{"createtime":"2020-09-25T15:25:35.000+0000","serial":"测试1测试测试序列好23456555","id":1309514424784064514}]}

    补充:uri 按服务名称负载均衡调用(实现动态路由)

    server:
      port: 9527
    
    spring:
      application:
        name: cloud-gateway
      cloud:
        gateway:
          routes:
            - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
    #          uri: http://localhost:8081          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service          #根据服务名称进行负载均衡替换
              predicates:
                - Path=/pay/listAll/**         # 断言,路径相匹配的进行路由
    
            - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
    #          uri: http://localhost:8081          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service          #根据服务名称进行负载均衡替换
              predicates:
                - Path=/pay/getServerPort/**         # 断言,路径相匹配的进行路由
    
    eureka:
      instance:
        hostname: cloud-gateway-service
      client: #服务提供者provider注册进eureka服务列表内
        service-url:
          register-with-eureka: true
          fetch-registry: true
          defaultZone: http://localhost:7001/eureka

    测试:

    hyyd@HYYD-M905AEEE MINGW64 /f/eclipseworkspace/inner-platform (dev)
    $ curl http://localhost:9527/pay/getServerPort
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    52    0    52    0     0   3466      0 --:--:-- --:--:-- --:--:-- 52000{"success":true,"code":"200","msg":"","data":"8082"}
    
    hyyd@HYYD-M905AEEE MINGW64 /f/eclipseworkspace/inner-platform (dev)
    $ curl http://localhost:9527/pay/getServerPort
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    52    0    52    0     0     52      0 --:--:-- --:--:-- --:--:-- 52000{"success":true,"code":"200","msg":"","data":"8081"}

    2.路由的两种配置方式

    一种是上面的xml配置,还有一种是基于代码的配置方式,如下:

    package cn.qz.cloud.config;
    
    import org.springframework.cloud.gateway.route.RouteLocator;
    import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class GatewayConfig {
        /**
         * 配置了一个id为route-name的路由规则
         * 当访问地址 http://localhost:9527/guonei时会自动转发到地址: http://news.baidu.com/guonei
         *
         * @param builder
         * @return
         */
        @Bean
        public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
            RouteLocatorBuilder.Builder routes = builder.routes();
            routes.route("path_route_eiletxie",
                    r -> r.path("/guonei")
                            .uri("http://news.baidu.com/guonei")).build();
            return routes.build();
        }
    
        @Bean
        public RouteLocator customRouteLocator2(RouteLocatorBuilder builder) {
            RouteLocatorBuilder.Builder routes = builder.routes();
            routes.route("path_route_eiletxie2",
                    r -> r.path("/guoji")
                            .uri("http://news.baidu.com/guoji")).build();
            return routes.build();
        }
    }

    3. 常用的断言

      常用的类图如下:

     主要介绍几个常用的:

    (1)Path 匹配路径,用法如上面

    (2)After 匹配日期,指定日期之后,如下:

     - After=2020-03-12T15:44:15.064+08:00[Asia/Shanghai] #日期后面(用于判断日期)

    (3) Before 用于判断指定日期之前,如下:

    Before=2020-03-12T15:44:15.064+08:00[Asia/Shanghai] #日期后面(用于判断日期)

    (4) Between用于判断指定日期之间:

    - Between=2020-03-12T15:44:15.064+08:00[Asia/Shanghai], 2021-03-12T15:44:15.064+08:00[Asia/Shanghai] #日期之间(用于判断日期)

    (5) Cookie 匹配带有指定Cookie

    - Cookie=uname,zs   #带Cookie,并且uname的值为zs

    (6)Header 带有指定的header

    - Header=X-Request-Id,d+ #请求头要有 X-Request-Id属性并且值为整数的正则表达式

      测试方法可以用curl进行测试,也可以用postman进行测试。

    补充:日期判断,不知道日期格式可以用如下方法获取

    import java.time.ZonedDateTime;
    
    public class PlainTest {
        public static void main(String[] args) {
            ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
            System.out.println(zbj);
        }
    }

    结果:

    2020-10-21T22:01:13.089+08:00[Asia/Shanghai]

    4.过滤器的使用

      类似于servlet的filter,可以实现前后进行特殊处理。比如过滤器开始校验权限等,过滤器处理之后处理响应头等操作。如下模拟校验权限:

    package cn.qz.cloud.filter;
    
    import cn.hutool.core.util.StrUtil;
    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.HttpCookie;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import java.util.Date;
    
    @Component
    @Slf4j
    public class MyLogGatewayFilter implements GlobalFilter, Ordered {
    
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            log.info("****** come in MyLogGateWayFilter: " + new Date());
            ServerHttpRequest request = exchange.getRequest();
            String unameHeader = request.getHeaders().getFirst("uname");
            String unameParam = request.getQueryParams().getFirst("uname");
            HttpCookie unameCookie = request.getCookies().getFirst("uname");
            if (StrUtil.isAllBlank(unameHeader, unameParam) && unameCookie == null) {
                log.info("*****用户名为null,非法用户");
                exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                return exchange.getResponse().setComplete();
            }
    
            return chain.filter(exchange);
        }
    
        /**
         * 加载过滤器的顺序,数字越小,优先级越高
         *
         * @return
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }

    测试:带uname参数、cookie、header的可以正常访问(curl 可以携带指定的cookie和header)

    hyyd@HYYD-M905AEEE MINGW64 /f/eclipseworkspace/inner-platform (dev)
    $ curl http://localhost:9527/pay/getServerPort
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
      0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
    
    hyyd@HYYD-M905AEEE MINGW64 /f/eclipseworkspace/inner-platform (dev)
    $ curl http://localhost:9527/pay/getServerPort?uname=123
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    52    0    52    0     0     52      0 --:--:-- --:--:-- --:--:-- 52000{"success":true,"code":"200","msg":"","data":"8081"}
    
    hyyd@HYYD-M905AEEE MINGW64 /f/eclipseworkspace/inner-platform (dev)
    $ curl http://localhost:9527/pay/getServerPort --Cookie 'uname=12'
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    52    0    52    0     0   3250      0 --:--:-- --:--:-- --:--:--  3250{"success":true,"code":"200","msg":"","data":"8082"}
    
    hyyd@HYYD-M905AEEE MINGW64 /f/eclipseworkspace/inner-platform (dev)
    $ curl http://localhost:9527/pay/getServerPort --Header 'uname:12'
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    52    0    52    0     0   3466      0 --:--:-- --:--:-- --:--:--  3466{"success":true,"code":"200","msg":"","data":"8081"}

    补充:curl传递JSON数据以及接收JSON数据如下:

    curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{"username": "zs", "password": "lisi"}' 'http://localhost:51117/test'

    如果遇到多行的json数据可以通过压缩变为一行(也可以不压缩),然后进行curl就可以了。 注意请求方式POST必须大写,小写无效。

    补充:curl 脚本如果是请求方法上携带多个参数需要用引号进行包裹,否则会造成参数解析错误

    curl -X POST "http://localhost:8088/v1/test?a=123&b=478"

    补充: Gateway也可以实现 黑白名单、打印请求信息等,结合ribbon打印选中的机器信息可以便于排查错误

    记录请求信息:

    package cn.qz.cloud.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.core.io.buffer.DataBuffer;
    import org.springframework.core.io.buffer.DataBufferUtils;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import java.nio.charset.StandardCharsets;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    /**
     * @author: 乔利强
     * @date: 2021/1/28 19:33
     * @description:
     */
    @Component
    @Slf4j
    public class LoggerFilter implements GlobalFilter, Ordered {
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            ServerHttpRequest request = exchange.getRequest();
            String method = request.getMethodValue();
    
            if (HttpMethod.POST.matches(method)) {
                return DataBufferUtils.join(exchange.getRequest().getBody())
                        .flatMap(dataBuffer -> {
                            byte[] bytes = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(bytes);
                            String bodyString = new String(bytes, StandardCharsets.UTF_8);
                            logtrace(exchange, bodyString);
                            exchange.getAttributes().put("POST_BODY", bodyString);
                            DataBufferUtils.release(dataBuffer);
                            Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                                DataBuffer buffer = exchange.getResponse().bufferFactory()
                                        .wrap(bytes);
                                return Mono.just(buffer);
                            });
    
                            ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                                    exchange.getRequest()) {
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    return cachedFlux;
                                }
                            };
                            return chain.filter(exchange.mutate().request(mutatedRequest)
                                    .build());
                        });
            } else if (HttpMethod.GET.matches(method)) {
                Map m = request.getQueryParams();
                logtrace(exchange, m.toString());
            }
            return chain.filter(exchange);
        }
    
        /**
         * 日志信息
         *
         * @param exchange
         * @param param    请求参数
         */
        private void logtrace(ServerWebExchange exchange, String param) {
            ServerHttpRequest serverHttpRequest = exchange.getRequest();
            String hostString = serverHttpRequest.getRemoteAddress().getHostString();
            String path = serverHttpRequest.getURI().getPath();
            String method = serverHttpRequest.getMethodValue();
            String headers = serverHttpRequest.getHeaders().entrySet()
                    .stream()
                    .map(entry -> "            " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
                    .collect(Collectors.joining("
    "));
            log.info("
    " + "----------------             ----------------             ---------------->>
    " +
                            "HttpMethod : {}
    " +
                            "requestHost : {}
    " +
                            "Uri        : {}
    " +
                            "Param      : {}
    " +
                            "Headers    : 
    " +
                            "{}
    " +
                            ""<<----------------             ----------------             ----------------"
                    , method, hostString, path, param, headers);
        }
    
        @Override
        public int getOrder() {
            return -1;
        }
    }

    黑名单:

    package cn.qz.cloud.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.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author: 乔利强
     * @date: 2021/1/28 19:49
     * @description: 模拟黑名单(也可以类似的实现白名单 , 只允许部分IP访问)
     */
    @Slf4j
    @Component
    public class BlackListFilter implements GlobalFilter, Ordered {
    
        // 模拟黑名单(可以从redis或者其他地方查询中查询)
        private static List<String> blackList = new ArrayList<>();
    
        static {
            blackList.add("0:0:0:0:0:0:0:1");  // 本机地址
        }
    
        /**
         * 过滤器核心方法
         *
         * @param exchange 封装了request和response对象的上下文
         * @param chain    网关过滤器链(包含全局过滤器和单路由过滤器)
         * @return
         */
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 思路:获取客户端ip,判断是否在黑名单中,在的话就拒绝访问,不在的话就放行
            // 从上下文中取出request和response对象
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
    
            // 从request对象中获取客户端ip
            String clientIp = request.getRemoteAddress().getHostString();
            // 拿着clientIp去黑名单中查询,存在的话就决绝访问
            if (blackList.contains(clientIp)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                log.info("=====>IP:" + clientIp + " 在黑名单中,将被拒绝访问!");
                String data = "Request be denied!";
                DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
                return response.writeWith(Mono.just(wrap));
            }
    
            // 合法请求,放行,执行后续的过滤器
            return chain.filter(exchange);
        }
    
        /**
         * 返回值表示当前过滤器的顺序(优先级),数值越小,优先级越高
         *
         * @return
         */
        @Override
        public int getOrder() {
            return -2;
        }
    }

    补充:GateWay限流 

    主要的限流算法有两种:

      漏桶算法(Leaky Bucket):水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.

      令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.

      第一种实现方式可以自己基于GateWay的filter+Guava的RateLimiter实现限流,还可以结合lua脚本操作redis实现限流。

      第二种方式是使用GateWay自带的限流。

    (1)pom引入:

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

    (2) 增加解析方式

    package cn.qz.cloud.config;
    
    import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import reactor.core.publisher.Mono;
    
    /**
     * @author: 乔利强
     * @date: 2021/2/4 12:14
     * @description:
     */
    @Configuration
    public class RequestRateLimiterConfig {
    
        @Bean
        @Primary
        KeyResolver apiKeyResolver() {
            //按URL限流,即以每秒内请求数按URL分组统计,超出限流的url请求都将返回429状态
            return exchange -> Mono.just(exchange.getRequest().getPath().toString());
        }
    
        @Bean
        KeyResolver userKeyResolver() {
            //按用户限流
            return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
        }
    
        @Bean
        KeyResolver ipKeyResolver() {
            //按IP来限流
            return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
        }
    }

    (3) yml 中增配置:

    spring:
      application:
        name: cloud-gateway
      cloud:
        gateway:
          routes:
            - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
    #          uri: http://localhost:8081          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service          #根据服务名称进行负载均衡替换
              predicates:
                - Path=/pay/listAll/**         # 断言,路径相匹配的进行路由
                - Host=**.com
    
            - id: cloud-provider-hystrix-payment #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              uri: lb://cloud-provider-hystrix-payment          #根据服务名称进行负载均衡替换
              predicates:
                - Path=/hystrix/**         # 断言,路径相匹配的进行路由
              filters:
                # 限流过滤器,使用gateway内置令牌算法
                - name: RequestRateLimiter
                  args:
                    # 令牌桶每秒填充平均速率,即等价于允许用户每秒处理多少个请求平均数
                    redis-rate-limiter.replenishRate: 1
                    # 令牌桶的容量,允许在一秒钟内完成的最大请求数
                    redis-rate-limiter.burstCapacity: 2
                    # 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
                    key-resolver: "#{@apiKeyResolver}"

    (4)测试:

    可以用jmeter进行测试,超限后返回的结果是429状态码。

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    ubuntu更换阿里源
    记一次开源软件的篡改
    linux下搜索指定内容
    随笔_1
    单细胞中的细胞类型划分
    scDNA-seq genomic analysis pipline
    NIH周三讲座视频爬虫
    ggplot2_bubble
    TCGA数据批量下载
    lncRNA芯片重注释
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/13843822.html
Copyright © 2020-2023  润新知