ChuChencheng / note

菜鸡零碎知识笔记
Creative Commons Zero v1.0 Universal
3 stars 0 forks source link

NestJS 限流代码分析 #47

Open ChuChencheng opened 2 years ago

ChuChencheng commented 2 years ago

背景

有关限流的知识可以参考 5 种限流算法,7 种限流方式,挡住突发流量?

代码地址

https://github.com/nestjs/throttler/blob/v3.0.0/src/throttler.guard.ts#L77

核心代码应该是这行开始了, Guard 中的 handleRequest

https://github.com/nestjs/throttler/blob/v3.0.0/src/throttler.service.ts#L17

以及 ThrottlerStorageServiceaddRecord 方法

代码分析

ThrottlerGuard

const ttls = await this.storageService.getRecord(key);
const nearestExpiryTime = ttls.length > 0 ? Math.ceil((ttls[0] - Date.now()) / 1000) : 0;

// Throw an error when the user reached their limit.
if (ttls.length >= limit) {
  res.header('Retry-After', nearestExpiryTime);
  this.throwThrottlingException(context);
}

ttls.length 大于等于 limit (ttl 中允许的请求数量,如果 ttl 是 1秒 ,则 limit 就是 QPS 的意思),则会被限流。

那么, ttls 里面存放的东西就很关键

ThrottlerStorageService

深入 ThrottlerStorageServiceaddRecord 方法,可以看到 ttls 数组存的是什么

const ttlMilliseconds = ttl * 1000;
if (!this.storage[key]) {
  this.storage[key] = [];
}

this.storage[key].push(Date.now() + ttlMilliseconds);

从代码中可以看出, ttls 存的是请求过期的时间点

也就是说,当一个请求过来,会往 ttls 数组中 push 一个时间点,代表到这个时间点之后,允许多放行一个新的请求。

还有多久可以放行请求

知道了 ttls 存放的是时间点,那么假设一位用户被限流了,他想知道多久后才会解除限流,就可以从 ttls 数组中得到这个信息,只需要取数组的第一项,减去现在的时间。

const nearestExpiryTime = ttls.length > 0 ? Math.ceil((ttls[0] - Date.now()) / 1000) : 0;

优缺点

NestJS 自带限流其实就是采用滑动日志的方式。

优点

优点就是限流比较精确,可以防止流量突刺

缺点

缺点也很明显,比较占用内存,假设 ttl 为 1 ,限制 QPS 为 1000 , ttls 最大就会有 1000 个元素

addRecord 中, ttls 数组是通过 setTimeout 的方式去清理的,那么 ttls 有多少个元素,就会有多少个 setTimeout 在排队

优化

本来是想着把 setTimeout 给优化掉,但是好像意义并不大,因为这种限流算法本来就是一种用空间妥协换取限流精确度的做法。