美文网首页spring cloudJava 程序员Java技术升华
又被面试官装到了:Spring Cloud Gateway 网关

又被面试官装到了:Spring Cloud Gateway 网关

作者: 马小莫QAQ | 来源:发表于2021-09-15 21:39 被阅读0次

    一、背景

    在我们平时开发过程中,一般一个请求都是需要经过多个微服务的,比如:请求从A服务流过B服务,如果A服务请求过快,导致B服务响应慢,那么必然会导致系统出现问题。因为,我们就需要有限流操作。

    二、实现功能

    1. 提供自定义的限流key生成,需要实现KeyResolver接口。
    2. 提供默认的限流算法,实现实现RateLimiter接口。
    3. 当限流的key为空时,直接不限流,放行,由参数spring.cloud.gateway.routes[x].filters[x].args[x].deny-empty-key 来控制
    4. 限流时返回客户端的相应码有 spring.cloud.gateway.routes[x].filters[x].args[x].status-code 来控制,需要写这个 org.springframework.http.HttpStatus类的枚举值。
    5. RequestRateLimiter 只能使用name || args这种方式来配置,不能使用简写的方式来配置。
    1. RequestRateLimiter过滤器的redis-rate-limiter参数是在RedisRateLimiter的CONFIGURATION_PROPERTY_NAME属性配置的。构造方法中用到了。

    三、网关层限流

    限流的key 生成规则,默认是 PrincipalNameKeyResolver来实现 限流算法,默认是 RedisRateLimiter来实现,是令牌桶算法。

    1、使用默认的redis来限流

    在Spring Cloud Gateway中默认提供了 RequestRateLimiter 过滤器来实现限流操作。

    1.引入jar包

    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    

    2.编写配置文件

    spring:
      application:
        name: gateway-9205
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8847
        gateway:
          routes:
            - id: user-provider-9206
              uri: lb://user-provider-9206
              predicates:
                - Path=/user/**
              filters:
                - RewritePath=/user(?<segment>/?.*), $\{segment}
                - name: RequestRateLimiter
                  args:
                    # 如果返回的key是空的话,则不进行限流
                    deny-empty-key: false
                    # 每秒产生多少个令牌
                    redis-rate-limiter.replenishRate: 1
                    # 1秒内最大的令牌,即在1s内可以允许的突发流程,设置为0,表示阻止所有的请求
                    redis-rate-limiter.burstCapacity: 1
                    # 每次请求申请几个令牌
                    redis-rate-limiter.requestedTokens: 1
      redis:
        host: 192.168.7.1
        database: 12
        port: 6379
        password: 123456
    
    server:
      port: 9205
    debug: true
    

    3.网关正常响应

    4.网关限流响应

    2、自定义限流算法和限流key

    1.自定义限流key

    编写一个类实现 KeyResolver 接口即可。

    package com.huan.study.gateway;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
    import org.springframework.cloud.gateway.route.Route;
    import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
    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.Optional;
    
    /**
     * 限流的key获取
     *
     * @author huan.fu 2021/9/7 - 上午10:25
     */
    @Slf4j
    @Component
    public class DefaultGatewayKeyResolver implements KeyResolver {
    
        @Override
        public Mono<String> resolve(ServerWebExchange exchange) {
            // 获取当前路由
            Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
    
            ServerHttpRequest request = exchange.getRequest();
            String uri = request.getURI().getPath();
            log.info("当前返回的uri:[{}]", uri);
    
            return Mono.just(Optional.ofNullable(route).map(Route::getId).orElse("") + "/" + uri);
        }
    }
    

    配置文件中的写法(部分)

    spring:
      cloud:
        gateway:
          routes:
            - id: user-provider-9206
              filters:
                - name: RequestRateLimiter
                  args:
                    # 返回限流的key
                    key-resolver: "#{@defaultGatewayKeyResolver}"
    

    2.自定义限流算法

    编写一个类实现 RateLimiter ,此处使用内存限流

    package com.huan.study.gateway;
    
    import com.google.common.collect.Maps;
    import com.google.common.util.concurrent.RateLimiter;
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter;
    import org.springframework.cloud.gateway.support.ConfigurationService;
    import org.springframework.context.annotation.Primary;
    import org.springframework.stereotype.Component;
    import reactor.core.publisher.Mono;
    
    /**
     * @author huan.fu 2021/9/7 - 上午10:36
     */
    @Component
    @Slf4j
    @Primary
    public class DefaultGatewayRateLimiter extends AbstractRateLimiter<DefaultGatewayRateLimiter.Config> {
    
        /**
         * 和配置文件中的配置属性相对应
         */
        private static final String CONFIGURATION_PROPERTY_NAME = "default-gateway-rate-limiter";
    
        private RateLimiter rateLimiter = RateLimiter.create(1);
    
        protected DefaultGatewayRateLimiter(ConfigurationService configurationService) {
            super(DefaultGatewayRateLimiter.Config.class, CONFIGURATION_PROPERTY_NAME, configurationService);
        }
    
        @Override
        public Mono<Response> isAllowed(String routeId, String id) {
            log.info("网关默认的限流 routeId:[{}],id:[{}]", routeId, id);
    
            Config config = getConfig().get(routeId);
    
            return Mono.fromSupplier(() -> {
                boolean acquire = rateLimiter.tryAcquire(config.requestedTokens);
                if (acquire) {
                    return new Response(true, Maps.newHashMap());
                } else {
                    return new Response(false, Maps.newHashMap());
                }
            });
        }
    
        @Getter
        @Setter
        @ToString
        public static class Config {
            /**
             * 每次请求多少个 token
             */
            private Integer requestedTokens;
        }
    }
    

    配置文件中的写法(部分)

    spring:
      cloud:
        gateway:
          routes:
            - id: user-provider-9206
              filters:
                - name: RequestRateLimiter
                  args:
                    # 自定义限流规则
                    rate-limiter: "#{@defaultGatewayRateLimiter}"
    

    注意⚠️: 这个类需要加上 @Primary 注解。

    3.配置文件中的写法

    spring:
      application:
        name: gateway-9205
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8847
        gateway:
          routes:
            - id: user-provider-9206
              uri: lb://user-provider-9206
              predicates:
                - Path=/user/**
              filters:
                - RewritePath=/user(?<segment>/?.*), $\{segment}
                - name: RequestRateLimiter
                  args:
                    # 自定义限流规则
                    rate-limiter: "#{@defaultGatewayRateLimiter}"
                    # 返回限流的key
                    key-resolver: "#{@defaultGatewayKeyResolver}"
                    # 如果返回的key是空的话,则不进行限流
                    deny-empty-key: false
                    # 限流后向客户端返回的响应码429,请求太多
                    status-code: TOO_MANY_REQUESTS
                    # 每次请求申请几个令牌  default-gateway-rate-limiter 的值是在 defaultGatewayRateLimiter 中定义的。
                    default-gateway-rate-limiter.requestedTokens: 1
    server:
      port: 9205
    debug: true
    

    作者:huan1993
    链接:https://juejin.cn/post/7005060165892309022
    来源:掘金

    相关文章

      网友评论

        本文标题:又被面试官装到了:Spring Cloud Gateway 网关

        本文链接:https://www.haomeiwen.com/subject/gamlgltx.html