随着我们系统的规模逐步扩大,用户数量也会呈几何形上升,一个非常关键的技术在许多项目中显得越来越必要————限流
需求:一个新的抢购项目,预计QPS峰值会达到1000+,考虑到我们后端服务器数量和性能有限,为了防止服务器崩溃,需要将某些请求快速失败,返回用户重试
令牌桶算法 和 漏桶算法
令牌桶算法
基本思路:
-
以恒定的速率向桶内加入令牌(当桶慢后加入的令牌丢弃)
-
请求过来后先去桶内获取令牌,成功则通行,没有获取到则拒绝
漏桶算法
基本思路:
-
请求过来后加入桶内(可排队或非排队),超过桶大小则抛弃返回
-
每秒通过恒定速率的请求到业务层
滑动窗口与非滑动窗口
比如设计1分钟只能通过1个请求,则如上图所示,在不管从任何位置开始算起的1分钟内都只会出现一个请求,像一个1分钟长度的窗口横向滑动一样,窗口内只能有一个请求,而非滑动窗口则只能是固定开始位置的1分钟
令牌桶和漏桶的使用场景
令牌桶算法放入令牌的速率是恒定的,但是获取令牌可以很快,这样就能够允许一定量的并发请求通过,而且有令牌总量的限制也能保证一定的请求速率,场景比较灵活,这也是为什么限流场景下令牌桶算法使用更广泛的原因。
漏铜算法每秒只通过固定速率的请求,这样做实现了对峰值请求的消峰逻辑,能够将瞬时并发转化为平滑请求,当然这个是以请求处理的等待时间为代价的。
滑动与非滑动
按照目前理解,如果要实现精准限流则滑动窗口是更加准确的,因为不管从任何时间段内算都不会超过设置的速率,非滑动窗口在特殊情况下,比如前一个时间段的末尾和后一个时间段的开始这两个点的统计可能超过设定的速率。
下面我们着重描述下令牌桶的逻辑
如何生产令牌?
一种解法是,开启一个定时任务,由定时任务持续生成令牌。这样的问题在于会极大的消耗系统资源,如,某接口需要分别对每个用户做访问频率限制,假设系统中存在6W用户,则至多需要开启6W个定时任务来维持每个桶中的令牌数,这样的开销是巨大的。
另一种解法则是延迟计算,如一个函数会在每次获取令牌之前调用,其实现思路为,若当前时间晚于上一次操作的事件,则计算该段时间内可以生成多少令牌,将生成的令牌加入令牌桶中并更新数据。这样一来,只需要在获取令牌时计算一次即可(后面会在代码中详细解释)。
实现逻辑选型: