SpringCloud学习系列Gateway-(3)限流篇

目录

前言

简单限流

源码分析

扩展限流


前言

通常一个应用,无论是全部请求还是细化到每一个接口的请求,都会有一个每秒请求量QPS的峰值,一般会通过压测的方式评估出接口的峰值是多少,一旦超过这个峰值,应用将会响应缓慢,严重的甚至会导致应用假死。

如何防止这种情况的发生呢?这时候就需要对请求进行限流,在某一时刻只允许一部分低于峰值的请求流量进来应用,提高系统的可用性。

这章节就会基于SpringCloud Gateway进行限流实现。

简单限流

一、通过配置的方式进行限流,配置gateway的application.yml文件,如下:

server:
  port: 8099
spring:
  application:
    name: gateway-frame
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          # 服务名小写
          lower-case-service-id: true
      routes:
      - id: gateway-service
        # lb代表从注册中心获取服务,且已负载均衡方式转发
        uri: lb://gateway-service
        predicates:
        - Path=/service/**
        # 限流filter配置
        filters:
        - name: RequestRateLimiter
          args:
            key-resolver: '#{@uriKeyResolver}'
            redis-rate-limiter.replenishRate: 300
            redis-rate-limiter.burstCapacity: 300
  redis:
      host: xxxxxxx
      port: 6379
      password: xxxxxx
      database: 0

# 注册中心
eureka:
  instance:
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://xxxxxxx:8761/eureka/

限流配置组成解读:

1、引入filte的配置-name: RequestRateLimiter,这个是使用已经实现好的RequestRateLimiterGatewayFilterFactory类进行限流;

2、key-resolver:用于获取限流维度的实现类,可以根据ip、uri、设备号、用户id等进行限流,这里使用的uriKeyResolver对应实现使用uri限流的类;

3、redis-rate-limiter.burstCapacity:令牌桶容量,就是没秒能够同时有多少个访问请求;

4、redis-rate-limiter.replenishRate:令牌桶每秒的填充量

5、因为限流类依赖于redis进行统计数据的存储,所以这里要加上redis的连接配置;

二、编写key-resolver

使用uri的维度限流

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class UriKeyResolver implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getURI().getPath());
    }

}

三、bean初始化

@Component
public class RateConfig {
   
    @Bean
    public UriKeyResolver uriKeyResolver() {
        return new UriKeyResolver();
    }

}

这样就完成了应用的简单限流。测试的时候可以把令牌桶的参数设置小一点,然后使用jmeter进行压测。

源码分析

首先,我们来看下SpringCloud Gateway的初始化方式和路由执行方式。(gateway的maven版本:2.2.3.RELEASE)

1、初始化配置信息,我们来看下org.springframework.cloud:spring-cloud-gateway-core包下面的META-INF/spring.factories文件;

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
// 校验引用配置
org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\
// gateway网关的核心配置,路由等
org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\
// 熔断配置
org.springframework.cloud.gateway.config.GatewayHystrixCircuitBreakerAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayResilience4JCircuitBreakerAutoConfiguration,\
// 负载均衡配置
org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\
// redis限流配置
org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\
// 注册中心配置
org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration,\
org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration
 
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.gateway.config.GatewayEnvironmentPostProcessor

这一章,我们关注redis限流配置类org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration

class GatewayRedisAutoConfiguration {

	@Bean
	@SuppressWarnings("unchecked")
	public RedisScript redisRequestRateLimiterScript() {
		DefaultRedisScript redisScript = new DefaultRedisScript<>();
		redisScript.setScriptSource(new ResourceScriptSource(
				new ClassPathResource("META-INF/scripts/request_rate_limiter.lua")));
		redisScript.setResultType(List.class);
		return redisScript;
	}

	@Bean
	@ConditionalOnMissingBean
	public RedisRateLimiter redisRateLimiter(ReactiveStringRedisTemplate redisTemplate,
			@Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript,
			ConfigurationService configurationService) {
		return new RedisRateLimiter(redisTemplate, redisScript, configurationService);
	}

}

这里初始化了两个类:

一个是RedisScript,用于执行redis的限流数据操作的lua脚本,lua文件路径定义;

另外一个是RedisRateLimiter,负责限流判断逻辑,这里采用@ConditionalOnMissingBean,就是当没有继承RateLimiter接口类的bean进行初始化时才生效。(注意:这里就是预留的让我们进行自定义限流的实现方式,后面实现动态限流就可以从这里入手)

那么什么时候才会用到这个RedisRateLimiter类呢,先别急,我们回头再看我们配置里的-name: RequestRateLimiter对应的工厂类RequestRateLimiterGatewayFilterFactory,限流的入口就是从这里来的:

@ConfigurationProperties("spring.cloud.gateway.filter.request-rate-limiter")
public class RequestRateLimiterGatewayFilterFactory extends
		AbstractGatewayFilterFactory<RequestRateLimiterGatewayFilterFactory.Config> {

	@SuppressWarnings("unchecked")
	@Override
	public GatewayFilter apply(Config config) {
		// 这里设置使用配置的限流维度实现类KeyResolver和限流逻辑类RateLimiter
		KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver);
		RateLimiter<Object> limiter = getOrDefault(config.rateLimiter,
				defaultRateLimiter);
		boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey);
		HttpStatusHolder emptyKeyStatus = HttpStatusHolder
				.parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode));

		return (exchange, chain) -> resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY)
				.flatMap(key -> {
					if (EMPTY_KEY.equals(key)) {
						if (denyEmpty) {
							setResponseStatus(exchange, emptyKeyStatus);
							return exchange.getResponse().setComplete();
						}
						return chain.filter(exchange);
					}
					String routeId = config.getRouteId();
					if (routeId == null) {
						Route route = exchange
								.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
						routeId = route.getId();
					}
					// 执行限流判断
					return limiter.isAllowed(routeId, key).flatMap(response -> {

						for (Map.Entry<String, String> header : response.getHeaders()
								.entrySet()) {
							exchange.getResponse().getHeaders().add(header.getKey(),
									header.getValue());
						}

						if (response.isAllowed()) {
							return chain.filter(exchange);
						}

						setResponseStatus(exchange, config.getStatusCode());
						return exchange.getResponse().setComplete();
					});
				});
	}

}

这里就会调用RedisRateLimiter的isAllowed方法判断是否进行限流:

@ConfigurationProperties("spring.cloud.gateway.redis-rate-limiter")
public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Config>
		implements ApplicationContextAware {

	@Override
	@SuppressWarnings("unchecked")
	public Mono<Response> isAllowed(String routeId, String id) {
		if (!this.initialized.get()) {
			throw new IllegalStateException("RedisRateLimiter is not initialized");
		}
        // 根据服务应用id获取限流配置
		Config routeConfig = loadConfiguration(routeId);

		// 设置的令牌每秒加入数量
		int replenishRate = routeConfig.getReplenishRate();
        // 设置的令牌桶容量
		int burstCapacity = routeConfig.getBurstCapacity();

		// How many tokens are requested per request?
		int requestedTokens = routeConfig.getRequestedTokens();

		try {
            // 获取redis的中的统计数据查询key
			List<String> keys = getKeys(id);

			// The arguments to the LUA script. time() returns unixtime in seconds.
			List<String> scriptArgs = Arrays.asList(replenishRate + "",
					burstCapacity + "", Instant.now().getEpochSecond() + "",
					requestedTokens + "");
			// allowed, tokens_left = redis.eval(SCRIPT, keys, args)
			Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys,
					scriptArgs);
			// .log("redisratelimiter", Level.FINER);
			return flux.onErrorResume(throwable -> {
				if (log.isDebugEnabled()) {
					log.debug("Error calling rate limiter lua", throwable);
				}
				return 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) {
			/*
			 * We don't want a hard dependency on Redis to allow traffic. Make sure to set
			 * an alert so you know if this is happening too much. Stripe's observed
			 * failure rate is 0.01%.
			 */
			log.error("Error determining if user allowed from redis", e);
		}
		return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
	}
	
}

我们可以debug看下传入的参数routeId和id,分别是请求的应用服务id和uri:

看下getKeys(id)方法,知道redis中的统计数据存储格式:

	static List<String> getKeys(String id) {
		String prefix = "request_rate_limiter.{" + id;

		String tokenKey = prefix + "}.tokens";
		String timestampKey = prefix + "}.timestamp";
		return Arrays.asList(tokenKey, timestampKey);
	}
  • request_rate_limiter.{uri}.tokens 可使用的令牌数量
  • request_rate_limiter.{uri}.timestamp  最后一个请求的时间

扩展限流

在实际生产中,限流参数不是一开始就定好不变,会根据实际业务情况进行相应的调整,而且一般会采用数据库存储的方式。但是由于RequestRateLimiterGatewayFilterFactory初始化后,不能实时进行限流参数的变更,那么限流的作用就要大打折扣了。

这时候,我们可以通过继承RateLimiter接口类实现isAllow方法进行拓展成实现刷新限流配置的效果。

1、仿照RedisRateLimiter类实现DynamicRedisRateLimiter


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter;
import org.springframework.cloud.gateway.support.ConfigurationService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Instant;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @PackageName 
 * @ClassName DynamicRedisRateLimiter
 * @Description 动态限流模块
 * @Auth 
 * @Date 2020/6/20 19:02
 * @Version 1.0
 **/
public class DynamicRedisRateLimiter extends AbstractRateLimiter<DynamicRedisRateConfig> implements ApplicationContextAware {

    private static final String CONFIGURATION_PROPERTY_NAME = "custom-redis-rate-limiter";
    private static final String REDIS_SCRIPT_NAME = "redisRequestRateLimiterScript";
    private static final String REMAINING_HEADER = "X-RateLimit-Remaining";
    private static final String REPLENISH_RATE_HEADER = "X-RateLimit-Replenish-Rate";
    private static final String BURST_CAPACITY_HEADER = "X-RateLimit-Burst-Capacity";
    private static final String REQUESTED_TOKENS_HEADER = "X-RateLimit-Requested-Tokens";

    private Log log;
    private ReactiveStringRedisTemplate redisTemplate;
    private RedisScript<List<Long>> script;
    private AtomicBoolean initialized;
    private DynamicRedisRateConfig defaultConfig;
    private boolean includeHeaders;
    private String remainingHeader;
    private String replenishRateHeader;
    private String burstCapacityHeader;
    private String requestedTokensHeader;

    public DynamicRedisRateLimiter(int defaultReplenishRate, int defaultBurstCapacity, int defaultRequestedTokens) {
        super(DynamicRedisRateConfig.class, CONFIGURATION_PROPERTY_NAME, (ConfigurationService)null);
        this.log = LogFactory.getLog(this.getClass());
        this.initialized = new AtomicBoolean(false);
        this.includeHeaders = true;
        this.remainingHeader = REMAINING_HEADER;
        this.replenishRateHeader = REPLENISH_RATE_HEADER;
        this.burstCapacityHeader = BURST_CAPACITY_HEADER;
        this.requestedTokensHeader = REQUESTED_TOKENS_HEADER;
        this.defaultConfig = (new DynamicRedisRateConfig()).setReplenishRate(defaultReplenishRate).setBurstCapacity(defaultBurstCapacity).setRequestedTokens(defaultRequestedTokens);
    }

    public DynamicRedisRateLimiter(DynamicRedisRateConfig defaultConfig) {
        super(DynamicRedisRateConfig.class, CONFIGURATION_PROPERTY_NAME, (ConfigurationService)null);
        this.log = LogFactory.getLog(this.getClass());
        this.initialized = new AtomicBoolean(false);
        this.includeHeaders = true;
        this.remainingHeader = REMAINING_HEADER;
        this.replenishRateHeader = REPLENISH_RATE_HEADER;
        this.burstCapacityHeader = BURST_CAPACITY_HEADER;
        this.requestedTokensHeader = REQUESTED_TOKENS_HEADER;
        this.defaultConfig = defaultConfig;
    }

    private static List<String> getKeys(String routeKey) {
        String prefix = "request_rate_limiter.{" + routeKey;
        String tokenKey = prefix + "}.tokens";
        String timestampKey = prefix + "}.timestamp";
        return Arrays.asList(tokenKey, timestampKey);
    }

    public boolean isIncludeHeaders() {
        return this.includeHeaders;
    }

    public void setIncludeHeaders(boolean includeHeaders) {
        this.includeHeaders = includeHeaders;
    }

    public String getRemainingHeader() {
        return this.remainingHeader;
    }

    public void setRemainingHeader(String remainingHeader) {
        this.remainingHeader = remainingHeader;
    }

    public String getReplenishRateHeader() {
        return this.replenishRateHeader;
    }

    public void setReplenishRateHeader(String replenishRateHeader) {
        this.replenishRateHeader = replenishRateHeader;
    }

    public String getBurstCapacityHeader() {
        return this.burstCapacityHeader;
    }

    public void setBurstCapacityHeader(String burstCapacityHeader) {
        this.burstCapacityHeader = burstCapacityHeader;
    }

    public String getRequestedTokensHeader() {
        return this.requestedTokensHeader;
    }

    public void setRequestedTokensHeader(String requestedTokensHeader) {
        this.requestedTokensHeader = requestedTokensHeader;
    }



    public DynamicRedisRateConfig getDefaultConfig() {
        return defaultConfig;
    }

    public void setDefaultConfig(DynamicRedisRateConfig defaultConfig) {
        this.defaultConfig = defaultConfig;
    }

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        if (this.initialized.compareAndSet(false, true)) {
            if (this.redisTemplate == null) {
                this.redisTemplate = context.getBean(ReactiveStringRedisTemplate.class);
            }

            this.script = (RedisScript)context.getBean(REDIS_SCRIPT_NAME, RedisScript.class);
            if (context.getBeanNamesForType(ConfigurationService.class).length > 0) {
                this.setConfigurationService(context.getBean(ConfigurationService.class));
            }
        }

    }

    public Mono<Response> isAllowed(String routeId, String id) {
        if (!this.initialized.get()) {
            throw new IllegalStateException("RedisRateLimiter is not initialized");
        } else {
            // 采用服务应用id+uri的方式组成统计唯一key,防止多应用的uri存在冲突问题
            String routeKey = routeId + id;
            // 实时获取限流配置的地方
            DynamicRedisRateConfig routeConfig = this.loadConfiguration(routeKey);
            int replenishRate = routeConfig.getReplenishRate();
            int burstCapacity = routeConfig.getBurstCapacity();
            int requestedTokens = routeConfig.getRequestedTokens();

            try {
                List<String> keys = getKeys(routeKey);
                List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "", Instant.now().getEpochSecond() + "", requestedTokens + "");
                Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
                return flux.onErrorResume((throwable) -> {
                    if (this.log.isDebugEnabled()) {
                        this.log.debug("Error calling rate limiter lua", throwable);
                    }

                    return Flux.just(Arrays.asList(1L, -1L));
                }).reduce(new ArrayList(), (longs, l) -> {
                    longs.addAll(l);
                    return longs;
                }).map((results) -> {
                    boolean allowed = ((Long)results.get(0)).longValue() == 1L;
                    Long tokensLeft = (Long)results.get(1);
                    Response response = new Response(allowed, this.getHeaders(routeConfig, tokensLeft));
                    if (this.log.isDebugEnabled()) {
                        this.log.debug("response: " + response);
                    }

                    return response;
                });
            } catch (Exception var10) {
                this.log.error("Error determining if user allowed from redis", var10);
                return Mono.just(new Response(true, this.getHeaders(routeConfig, -1L)));
            }
        }
    }

    private DynamicRedisRateConfig loadConfiguration(String routeKey) {
        // 根据routeId到配置map中获取对应的配置
        DynamicRedisRateConfig routeConfig = BizRateRule.BIZ_RATE_MAP.get(routeKey);
        if (routeConfig == null) {
            routeConfig = this.defaultConfig;
        }

        if (routeConfig == null) {
            throw new IllegalArgumentException("No Configuration found for routeKey " + routeKey);
        } else {
            return routeConfig;
        }
    }

    private Map<String, String> getHeaders(DynamicRedisRateConfig config, Long tokensLeft) {
        Map<String, String> headers = new HashMap<>(8);
        if (this.isIncludeHeaders()) {
            headers.put(this.remainingHeader, tokensLeft.toString());
            headers.put(this.replenishRateHeader, String.valueOf(config.getReplenishRate()));
            headers.put(this.burstCapacityHeader, String.valueOf(config.getBurstCapacity()));
            headers.put(this.requestedTokensHeader, String.valueOf(config.getRequestedTokens()));
        }
        return headers;
    }


}

关键不同代码就是isAllow()方法中:

这里的this.loadConfiguration(routeKey)方法实现获取动态限流配置:

private DynamicRedisRateConfig loadConfiguration(String routeKey) {
	// 根据routeId到配置map中获取对应的配置
	DynamicRedisRateConfig routeConfig = BizRateRule.BIZ_RATE_MAP.get(routeKey);
	if (routeConfig == null) {
		routeConfig = this.defaultConfig;
	}

	if (routeConfig == null) {
		throw new IllegalArgumentException("No Configuration found for routeKey " + routeKey);
	} else {
		return routeConfig;
	}
}

2、定义DynamicRedisRateConfig类

import org.springframework.core.style.ToStringCreator;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Min;

/**
 * @PackageName 
 * @ClassName DynamicRedisRateConfig
 * @Description 配置类
 * @Auth Sam.Chen
 * @Date 2020/6/20 19:10
 * @Version 1.0
 **/
@Validated
public class DynamicRedisRateConfig {

    // 令牌桶每秒填充平均速率
    @Min(1L)
    private int replenishRate;

    // 令牌桶总容量
    @Min(0L)
    private int burstCapacity = 1;


    @Min(1L)
    private int requestedTokens = 1;

    public DynamicRedisRateConfig() {
    }

    public int getReplenishRate() {
        return this.replenishRate;
    }

    public DynamicRedisRateConfig setReplenishRate(int replenishRate) {
        this.replenishRate = replenishRate;
        return this;
    }

    public int getBurstCapacity() {
        return this.burstCapacity;
    }

    public DynamicRedisRateConfig setBurstCapacity(int burstCapacity) {
        this.burstCapacity = burstCapacity;
        return this;
    }

    public int getRequestedTokens() {
        return this.requestedTokens;
    }

    public DynamicRedisRateConfig setRequestedTokens(int requestedTokens) {
        this.requestedTokens = requestedTokens;
        return this;
    }

    public String toString() {
        return (new ToStringCreator(this)).append("replenishRate", this.replenishRate).append("burstCapacity", this.burstCapacity).append("requestedTokens", this.requestedTokens).toString();
    }
    
}

3、定义BizRateRule.BIZ_RATE_MAP对应的缓存类BizRateRule

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @PackageName 
 * @ClassName BizRateRule
 * @Description 存储业务限流规则
 * @Auth
 * @Date 2020/6/20 20:28
 * @Version 1.0
 **/
public class BizRateRule {

    public static Map<String, DynamicRedisRateConfig> BIZ_RATE_MAP = new ConcurrentHashMap<>();

    private static final String LOCK = "1";

    /**
     * @Description 刷新bizMap
     * @Date 2020/6/20 20:34
     **/
    public static void refreshBizRateMap(Map<String, DynamicRedisRateConfig> newMap) {
        Map<String, DynamicRedisRateConfig> tmpMap = new ConcurrentHashMap<>();
        tmpMap.putAll(newMap);
        synchronized (LOCK) {
            BIZ_RATE_MAP.clear();
            BIZ_RATE_MAP = tmpMap;
        }
    }

}

4、定义初始化这些类的config:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @PackageName 
 * @ClassName BizRateConfig
 * @Description 限流配置
 * @Auth 
 * @Date 2020/6/20 20:10
 * @Version 1.0
 **/
@Component
public class BizRateConfig {

    @Value("${rate.limiter.config.default.replenishRate}")
    private int defaultReplenishRate=1;

    @Value("${rate.limiter.config.default.burstCapacity}")
    private int defaultBurstCapacity = 1;

    @Value("${rate.limiter.config.default.requestedTokens}")
    private int defaultRequestedTokens = 1;

    @Bean
    @Primary
    public DynamicRedisRateLimiter customRedisRateLimiter(DynamicRedisRateConfig defaultDynamicRedisRateConfig) {
        return new DynamicRedisRateLimiter(defaultDynamicRedisRateConfig);
    }

    @Bean
    public DynamicRedisRateConfig defaultCustomRedisRateConfig() {
        DynamicRedisRateConfig config = new DynamicRedisRateConfig();
        config.setReplenishRate(defaultReplenishRate);
        config.setBurstCapacity(defaultBurstCapacity);
        config.setRequestedTokens(defaultRequestedTokens);
        return config;
    }

    @Bean
    public UriKeyResolver uriKeyResolver() {
        return new UriKeyResolver();
    }

    @PostConstruct
    public void initBizData() {
        // 后面可以接入配置中心或者定时任务刷新数据库,这里先采用硬编码方式
        Map<String, DynamicRedisRateConfig> map = new HashMap<>(4);
        DynamicRedisRateConfig config = new DynamicRedisRateConfig();
        config.setReplenishRate(1);
        config.setBurstCapacity(1);
        config.setRequestedTokens(1);
        map.put("gateway-service/service/client/test", config);
        BizRateRule.refreshBizRateMap(map);
    }

}

扩展限流结束,赶紧跑起来试下!!!886.。。。。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值