简单地实现令牌桶限速

实现

  1. 按一定的速率往令牌桶添加令牌

  2. 只有获取令牌才能处理任务。

  3. 令牌桶满后,新增的令牌丢弃。

  4. 消费令牌后,发现令牌桶没有令牌,及时把消费的令牌还给令牌桶。

优点

滑动窗口:允许在最小窗口的边界,超过限定的速率。例如:按最小窗口为分钟,每分钟最大消费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;
        }
    }
}
相关推荐
明月_清风6 分钟前
自定义右键菜单:在项目里实现“选中文字即刻生成新提示”
前端·javascript
明月_清风7 分钟前
告别后端转换:高质量批量导出实战
前端·javascript
刘发财5 小时前
弃用html2pdf.js,这个html转pdf方案能力是它的几十倍
前端·javascript·github
牛奶7 小时前
2026年大模型怎么选?前端人实用对比
前端·人工智能·ai编程
牛奶7 小时前
前端人为什么要学AI?
前端·人工智能·ai编程
地平线开发者9 小时前
SparseDrive 模型导出与性能优化实战
算法·自动驾驶
董董灿是个攻城狮9 小时前
大模型连载2:初步认识 tokenizer 的过程
算法
Kagol10 小时前
🎉OpenTiny NEXT-SDK 重磅发布:四步把你的前端应用变成智能应用!
前端·开源·agent
地平线开发者10 小时前
地平线 VP 接口工程实践(一):hbVPRoiResize 接口功能、使用约束与典型问题总结
算法·自动驾驶
罗西的思考10 小时前
AI Agent框架探秘:拆解 OpenHands(10)--- Runtime
人工智能·算法·机器学习