实现
-
按一定的速率往令牌桶添加令牌
-
只有获取令牌才能处理任务。
-
令牌桶满后,新增的令牌丢弃。
-
消费令牌后,发现令牌桶没有令牌,及时把消费的令牌还给令牌桶。
优点
滑动窗口:允许在最小窗口的边界,超过限定的速率。例如:按最小窗口为分钟,每分钟最大消费60次,在01分59秒和02分0秒这2秒的时间内,允许消费120次,其他时间不消费。
漏桶算法:当漏桶满以后,只能按一定速率消费。无法过载处理,缺乏效率。
令牌算法:允许过载处理,可以一下子把令牌桶的令牌消费,由于按一定速率添加令牌,令牌消费按照一定速率限速。消费令牌后,发现令牌桶没有令牌,及时补充令牌,防止添加令牌过慢,系统相对空闲的情况。
TypeScript
interface Options {
interval: number;
max: number;
}
// 简单地实现令牌桶限速
export class RateLimit {
private _interval = 60000;
private _max = 100;
private _buckets = new Map<string, { count: number, resolves: Set<{count: number, resolve: () => void}> }>();
constructor(options: Options) {
this.interval = options.interval;
this.max = options.max;
setInterval(() => {
const tokens = this._max / (this._interval / 1000);
for (const bucket of this._buckets.values()) {
if (bucket.count < this._max) {
bucket.count += tokens;
}
for (const resolveInfo of bucket.resolves) {
if (bucket.count - resolveInfo.count > 0) {
bucket.count -= resolveInfo.count;
resolveInfo.resolve();
bucket.resolves.delete(resolveInfo);
}
}
}
}, 1000);
}
get max() {
return this._max
}
set max(val: number) {
if (val > 0) {
this._max = Math.floor(val);
}
}
get interval() {
return this._interval;
}
set interval(val: number) {
if (val >= 1000) {
this._interval = Math.floor(val);
}
}
limit(key: string, count = 1) {
return new Promise<void>((resolve) => {
if (count <= 0) {
resolve();
return;
}
let bucket = this._buckets.get(key);
if (!bucket) {
bucket = { count: 0, resolves: new Set() };
this._buckets.set(key, bucket);
}
if (bucket.count - count > 0) {
bucket.count -= count;
resolve();
} else {
bucket.resolves.add({count, resolve});
}
});
}
consume(key: string, count: number) {
if (count <= 0) {
return;
}
const bucket = this._buckets.get(key);
if (!bucket) {
return;
}
// 令牌桶为空,补充令牌
if (bucket.count == 0) {
bucket.count += count;
}
}
}