Spring Cloud 微服務五:Spring cloud gateway限流


前言:在互聯網應用中,特別是電商,高並發的場景非常多,比如:秒殺、搶購、雙11等,在開始時間點會使流量爆發式地涌入,如果對網絡流量不加控制很有可能造成后台實例資源耗盡。限流是指通過指定的策略削減流量,使到達后台實例的請求在合理范圍內。本章將介紹spring cloud gateway如何實現限流。

前情回顧請參考:

Spring Cloud 微服務一:Consul注冊中心

Spring Cloud 微服務二:API網關spring cloud zuul

Spring Cloud 微服務三: API網關Spring cloud gateway

Spring Cloud 微服務四:熔斷器Spring cloud hystrix

  • 限流算法
        主流的限流算法有兩種:漏桶(leaky bucket)和令牌桶(token bucket)。漏桶算法 有一個固定容量的桶,對於流入的水無法預計速率,流出的水以固定速率,當水滿之后會溢出。

    令牌桶算法,有一個固定容量的桶,桶里存放着令牌(token)。桶最開始是空的,token以一個固定速率向桶中填充,直到達到桶的容量,多余的token會被丟棄。每當一個請求過來時,都先去桶里取一個token,如果沒有token的話請求無法通過。

 

         兩種算法的最主要區別是令牌桶算法允許一定流量的突發,因為令牌桶算法中取走token是不需要時間的,即桶內有多少個token都可以瞬時拿走。基於這個特點令牌桶算法在互聯網企業中應用比較廣泛,我們在實現限流的時候也會基於這個算法。

  • gateway如何實現限流
  •  方法1:Spring cloud gateway實現限流的方式主要是通過添加自定義filter來實現,自定義filter需要實現GatewayFilter和Ordered接口。本章將結合開源的Bucket4j來實現,Bucket4j是基於令牌桶算法實現,Bucket4j代碼參考:https://github.com/vladimir-bukhtoyarov/bucket4j
      首先修改api-gateway module,pom中添加Bucket4j依賴,最新版本是4.3.0,工程的版本已經在父工程中定義好了
<dependency>
       <groupId>com.github.vladimir-bukhtoyarov</groupId> 
       <artifactId>bucket4j-core</artifactId> 
</dependency>

                第二步,添加filter實現GatewayFilter和Ordered,添加相應的參數,並使用一個ConcurrentHashMap存儲ip以及bucket,實現filter方法,對客戶端訪問ip進行過濾

public class LimitFilter implements GatewayFilter, Ordered {
    private static final Logger logger = LoggerFactory.getLogger(LimitFilter.class);

    private int capacity;
    private int refillTokens;
    private Duration refillDuration;

    public LimitFilter(int capacity, int refillTokens, Duration refillDuration) {
        this.capacity = capacity;
        this.refillTokens = refillTokens;
        this.refillDuration = refillDuration;
    }

    private static final Map<String, Bucket> CACHE = new ConcurrentHashMap<>();

    private Bucket createNewBucket() {
        Refill refill = Refill.greedy(refillTokens, refillDuration);
        Bandwidth limit = Bandwidth.classic(capacity, refill);
        return Bucket4j.builder().addLimit(limit).build();
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
        Bucket bucket = CACHE.computeIfAbsent(ip, k -> createNewBucket());
        logger.info("IP: "+ip+", available tokens :"+bucket.getAvailableTokens());
        if (bucket.tryConsume(1L)) {
            return chain.filter(exchange);
        }
        logger.info("IP: "+ip+", available tokens :"+bucket.getAvailableTokens()+" too many requests");
        exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
        return exchange.getResponse().setComplete();

    }

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

             第三步,添加自定義路由,添加配置類,RouteLocator構造器中添加filter以及相應的地址信息,設置同一ip同時只能訪問一次,多余的將被忽略。另外,由於我們在程序中配置了路由,需要將application.yml中的gateway相關屬性刪除。

@Configuration
public class RouteLocatorConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        System.out.println("============================RouteLocatorConfig "+ builder.routes());
        return builder
                .routes()
                .route(r -> r.path("/*")
                        .filters(f -> f.filter(new LimitFilter(1,
                                1, Duration.ofSeconds(1))))
                        .uri("http://localhost:10080/")
                        .order(0)
                        .id("user_route"))
                .build();
    }
}

         最后,測試,重啟api-gateway,訪問http://localhost:8088/users,第一次訪問成功,頻繁刷新會出現空白頁,控制台會輸出相關信息

             方法2:使用spring cloud 原生的redis方式

    第一步,搭建redis服務器,具體方法參考redis官網

    第二步,pom中添加redis依賴

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

    自定義resolver

@Configuration
public class CustomResolver {
    @Bean
    public KeyResolver ipKeyResolver(){
        System.out.println("##############ipKeyResolver########################");
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }
}

  第三步,修改application.yml配置

 routes:
        - id: user_route
          uri: http://localhost:10080
          predicates:
            - Path=/*
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1
                key-resolver: "#{@ipKeyResolver}

        最后做測試,並使用monitor命令監控redis


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM