开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
写在前面
相信针对接口限流大家在实际的业务开发中都或多或少的遇到过,尤其现在微服务横行的情况下,单机限流方案绝大多数不能满足实际的微服务的架构,就使得像 GuavaRateLimiter 限流神器,在很多分布式场景下就无法大展身手。
今天彻彻底底当一次标题党,通过五行代码介绍一种简单实用的分布式限流方案,当你的接口限流场景对平滑性没有非常高要求的情况下,可以放心大胆的使用,简洁而高效。
话不多说上代码
我的限流场景是对一个业务接口的调用实施限流,限流的要求是一秒钟限制请求一次,同时我所请求的接口对一秒的限制并没有严格的连续性要求,如在 22:24:30.500 请求一次,又在22:24:31.100 请求一次,如果将请求精确到秒的维度来看,确实是22:24:30 请求了一次接口,再22:24:31 又请求了一次接口满足一秒一次调用的限制;但要是将时间放到毫秒的维度来看,在22:24:30.500 - 22:24:31:500 这一秒中来看,实际上请求了接口两次,不满足接口限流的要求,所以是否使用这种方案,要结合具体的业务场景具体分析。
/**
* 判断当前时间是否能够执行接口请求
*
* @param key 接口key
* @param execTime 接口请求时间
* @return 执行时间是否可以调度
*/
private boolean checkLimitLock(String key, Long execTime) {
//组装接口请求时间(秒)的接口限流key
//hhmmss_key 如 222430_key
String lockKey = wrapRateLimitLock(key, execTime);
//利用 redis incr() 函数的原子性来锁定调度的那一秒是否能够执行
Long routeCount = redisTemplate.opsForValue().increment(lockKey);
//设置 key 过期时间,自动释放 redis 内存
final long lockExpire = (execTime - System.currentTimeMillis()) / 1000 + 1;
redisTemplate.boundValueOps(lockKey).expire(lockExpire, TimeUnit.SECONDS);
return routeCount <= 1;
}
说说限流模型
限流的原理是利用 redis 实现了一个分布式计数器进行限流,即是一个固定窗口算法限流模型,其流程为:
- 按时间划分多个独立且固定大小的时间窗口
- 请求落在时间窗口内计数器就 +1
- 超过计数器限流阈值的请求将会被拒绝
固定窗口胜在实现简单,但由于时间窗口固定不够平滑,极端情况下会出现双倍突发请求的情况。若业务对限流有严格的要求,建议尝试换个模型:
- 滑动窗口计数器:滑动时间窗口计数器是对固定时间窗口算法的一种改进,将固定窗口在进行区间划分同时对窗口进行滑动,丢弃老的一个小区间加入新的一个小区间,因为区间粒度更细,所以限流更加均匀。
- 令牌桶算法:一个恒定的速率往桶里放入令牌,如果要请求接口时,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝请求。
在后面的文章中,会接口具体的场景介绍两种方法的实现。