Spring Cloud Gateway 原生支援介面限流該怎麼玩

冷冷gg發表於2018-07-01
關於pig:

基於Spring Cloud、oAuth2.0開發基於Vue前後分離的開發平臺,支援賬號、簡訊、SSO等多種登入,提供配套視訊開發教程。

碼雲地址:gitee.com/log4j/pig

關於 Spring Cloud Gateway

SpringCloudGateway是Spring官方基於Spring 5.0,Spring Boot 2.0和Project Reactor等技術開發的閘道器,Spring雲閘道器旨在提供一種簡單而有效的路由API的方法。Spring Cloud Gateway作為Spring Cloud生態系中的閘道器,目標是替代Netflix ZUUL,其不僅提供統一的路由方式,並且基於Filter鏈的方式提供了閘道器基本的功能,例如:安全,監控/埋點,和限流等。

zuul如何實現多維度限流請參考我的部落格

Zuul:構建高可用閘道器之多維度限流

開始Gateway 限流

POM 依賴

<!--spring cloud gateway依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--基於 reactive stream 的redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
複製程式碼

配置按照請求IP 的限流

spring:
  cloud:
    gateway:
      routes:
      - id: requestratelimiter_route
        uri: lb://pigx-upms
        order: 10000
        predicates:
        - Path=/admin/**
        filters:
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 1  # 令牌桶的容積
            redis-rate-limiter.burstCapacity: 3  # 流速 每秒
            key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表示式去的對應的bean
        - StripPrefix=1
複製程式碼

配置bean,多維度限流量的入口

/**
* 自定義限流標誌的key,多個維度可以從這裡入手
* exchange物件中獲取服務ID、請求資訊,使用者資訊等
*/
@Bean
KeyResolver remoteAddrKeyResolver() {
    return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
複製程式碼

OK 完成。

壓力測試

併發5個執行緒。

image

Redis 資料變化

我們使用redis的monitor 命令,實時檢視redis 的操作情況。
會發現在redis中會操作兩個key

  • request_rate_limiter.{xxx}.timestamp
  • request_rate_limiter.{xxx}.tokens
    image

實現原理

image

Spring Cloud Gateway 預設實現 Redis限流,如果擴充套件只需要實現ratelimter介面即可。

RedisRateLimter 的核心程式碼,判斷是否取到令牌的實現,通過呼叫 redis的LUA 指令碼。

public Mono<Response> isAllowed(String routeId, String id) {
	Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig);
	int replenishRate = routeConfig.getReplenishRate();
	int burstCapacity = routeConfig.getBurstCapacity();

	try {
		List<String> keys = getKeys(id);
		returns unixtime in seconds.
		List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
				Instant.now().getEpochSecond() + "", "1");
		// 這裡是核心,執行redis 的LUA 指令碼。
		Flux<List<Long>> flux =
		this.redisTemplate.execute(this.script, keys, scriptArgs);
		return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
				.reduce(new ArrayList<Long>(), (longs, l) -> {
					longs.addAll(l);
					return longs;
				}) .map(results -> {
					boolean allowed = results.get(0) == 1L;
					Long tokensLeft = results.get(1);

					Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));

					if (log.isDebugEnabled()) {
						log.debug("response: " + response);
					}
					return response;
				});
	}
	catch (Exception e) {
		log.error("Error determining if user allowed from redis", e);
	}
	return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
}
複製程式碼

LUA 指令碼

image

相關文章