Redis系列三 限流

君子终日乾乾,夕惕若,厉,无咎。(《周易·䷀乾》)

整体概述

开发高并发系统时有三把利器用来保护系统:缓存、降级和限流

缓存目的: 提升系统访问速度和增大系统能处理的容量;

降级: 在意外情况下,保证系统的可用性。

限流:对某些场景进行并发/请求量限制,如:稀缺资源<秒杀>、写服务<评论>、复杂的查询<评论的最后几页>

  1. 目的:通过对 并发访问/请求进行限速 或者一个时间窗口内的的请求进行限速来保护系统;
  2. 常见的限流方向
    1. 限制总并发数(比如数据库连接池、线程池)、
    2. 限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、
    3. 限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);
    4. 限制远程接口调用速率、
    5. 限制MQ的消费速率。
  3. 常见的限流算法
    1. 计数器
    2. 漏桶
    3. 令牌桶

限流

计数器滑动窗口算法

采用redis zset实现滑动窗口限流,核心:

  1. 清除时间段外的请求数
  2. 在时间段内进行数据标记
因为有序集合,很容易按照score进行处理,所以加上时间戳就可是实现.

缺点

  1. 如果数据量过大,如60s-100w,则需要保存大量的行为记录,消耗大量的存储空间
  2. 如果第1s,达到访问量,之后就一直被拒绝。❌

代码实现

/**
* 滑动时间限流
* @param key redis 标示
* @param period 限流时间范围 单位:秒
* @param limit 最大运行访问次数
* 1. 非原子性
* 2. 允许 60s 的最大访问次数为 1000w 的时候,此时如果使用 ZSet 的方式就会占用大量的空间用来存储请求的记录信息
* 3. 第一秒达到了最大访问次数,之后就一直拒绝
*/
async function isPeriodLimiting(key: string, period: number, limit: number) {
const unixMS = UtilDate.formatUnix({ type: "MS" });
// console.log(`清除 0 - ${unixS - period} ----`, unixS);
// 从目前时间戳开始,清除过去区间的数据【 N 秒】
// !单位要一致,都是秒
await redis.zremrangebyscore(key, 0, (unixMS - period * 1000));
// key对应的总个数
const currentCount = await redis.zcard(key);
// console.log('currentCount: ', currentCount)
// 如果该区间内标记的个数 大于 限制个数,则返回false
if (currentCount >= limit) {
return false;
}
// 否则,正常写入zset; key score field
// !field 不能一样
await redis.zadd(key, unixMS, unixMS);
return true;
}

(async function () {

let i = 0;
// 针对某个接口进行限流
const key = 'limit:period:buy';
// 区间限制次数 20
const limit = 5;
// 滑动时间区间为 2s
const period = 2;
while (i < 20) {
const noLimit = await isPeriodLimiting(key, period, limit);
if (noLimit) {
console.log('正常请求:', i);
} else {
console.log('被限制:', i);
// 如果被限制,等待 区间 时间后,再次判断
await UtilCommon.sleep(period);
}
i++;
}
await UtilCommon.sleep(1);
process.exit(1);
})()

返回值

➜  3限流 git:(main) ✗ ts-node periodLimit.ts
正常请求: 0
正常请求: 1
正常请求: 2
正常请求: 3
正常请求: 4
被限制: 5
正常请求: 6
正常请求: 7
正常请求: 8
正常请求: 9
正常请求: 10
被限制: 11
正常请求: 12
正常请求: 13
正常请求: 14
正常请求: 15
正常请求: 16
被限制: 17
正常请求: 18
正常请求: 19

漏⽃限流

核心概念

  1. 漏斗的容量: 最多存多少数据
  2. 漏⽃的剩余空间:当前⾏为可以持续进⾏的数量;
  3. 漏嘴的流⽔速率代表着系统允许该⾏为的最⼤频率

优缺点

  1. 实现很简单,对后端的负载是恒定的;
  2. 无法解决有突增流量的情况

场景

如果你的系统没有突增流量,对于流量绝对均匀有很强的要求,可以采用使用漏斗算法。

令牌桶

参考链接

聊聊高并发系统之限流特技
API 调用次数限制实现
基于Redis的限流系统的设计
我司用了6年的Redis分布式限流器,可以说是非常厉害了!
Redis高并发限流策略之漏斗限流算法
一文搞懂高频面试题之限流算法
「预热桶」限流算法详解(附 Node.js 实现)