高并发限流那些事

关于限流的算法和方案。计数器算法、滑动窗口算法、漏桶算法、令牌桶算法个人极简笔记。

为什么需要限流

正常的业务量增长不是瞬时的,可以采用应用实例或者数据库实例的垂直或水平伸缩应对,而限流针对场景主要两种:

  1. 网络攻击、爬虫程序

  2. 热点事件触发业务(如各平台营销活动、微博热点话题)

限流算法

1、计数器算法

原理:请求达到时计数器+1,然后比较当前计数是否达到阈值。

具体有两种实现:

1.1 根据并发数限流:判断系统正在处理的请求数是否达到阈值

请求处理前计数器+1,请求处理完成计数器-1,比如要控制系统同时处理的请求不超过100个。

static AtomicInteger runningThread = new AtomicInteger(0);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    if (runningThread.get() > MAX_VALUE) {
    return getVoidMono(exchange, BaseResult.builder().code("SYSTEM_BUSY").message("系统繁忙").build());
    }
    runningThread.getAndIncrement();
    Mono<Void> mono = chain.filter(exchange)
    .then(Mono.fromRunnable(() -> runningThread.getAndDecrement()));
    return mono;
}

1.2  根据TPS限流:判断单位时间内请求数是否达到阈值

如:每分钟处理请求不超过100个。

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    //redis键值自增,key不存在时自动创建,初始值为0
    Long v = redisTemplate.opsForValue().increment(key, 1L);
    //初始化生效时间
    if (v == 1) {
    redisTemplate.expire(key, 1L, TimeUnit.MINUTES);
    }
    //请求数大于阈值拦截处理
    if (v > MAX_VALUE) {
    return getVoidMono(exchange, BaseResult.builder().code("SYSTEM_BUSY").message("系统繁忙").build());
    }
    return chain.filter(exchange);
}

这种限流方法有明显的缺陷,如下图,分别看两个单位时间内的并发数是被限制到每分钟100个请求了,但是如果请求如果集中分布在第一分钟的最后一秒和第二分钟的第一秒之间,算出的并发成了每秒100个请求。

2、滑动窗口算法

滑动窗口本质上还是计数器算法,只是采用了更加细粒度的计数,对滚动区间进行计数。针对上述问题,将1分钟拆分为10个格子即每10秒一次滑动,统计当前一分钟窗口内的总计数。针对上述的问题,把一分钟拆成60个格子,即每秒一次滑动。00:59秒进入100个请求,01:00的请求则被限流拦截。

so 滑动窗口的本质是对粗粒度限流一定程度的优化,假如一开始就用极细粒度时间间隔做计数统计,也能实现较为精确的限流,但同时因为频繁的进行计数器的重置牺牲部分效率。

3、 漏桶算法

原理:请求排队,系统匀速取出排队请求进行处理。

漏桶很形象,不管进水速度如何,漏孔滴水的速率是匀速稳定的。

4、令牌桶算法

划重点,目前应用比较广泛的算法。

原理: 每秒/分钟产生一定数目的令牌到令牌桶中(令牌桶满则忽略),请求到达时网关获取令牌成功则处理,失败则触发限流逻辑。

Google开源工具包Guava提供了基于令牌桶算法的限流工具类RateLimiter。

详细用法 :https://cloud.tencent.com/developer/article/1408819

//每秒生成10个令牌,5秒预热时间。
static RateLimiter rateLimiter = RateLimiter.create(10, 5, TimeUnit.SECONDS);

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    if (!rateLimiter.tryAcquire()) {
    return getVoidMono(exchange, BaseResult.builder().code("SYSTEM_BUSY").message("系统繁忙").build());
    }
    return chain.filter(exchange);
}

总结限流实现方案

1、 网关应用层限流,自定义限流逻辑,可使用
Semaphore、AtomicInteger、Redis等计数器
RateLimiter(guava令牌桶方案)
RedisRateLimter(可根据IP、用户、接口路径等定制限流规则)
Sentinel (阿里巴巴开源方案,Springcloud/Springboot适用)

Sentinel特性 Sentinel特性

2、运维代理层
nginx配置:限制并发连接数、根据IP、接口限制并发访问速率。


本文持续完善中….