Skip to content

Commit 145ddcd

Browse files
改进限流器实现,确保请求按先来后到顺序处理
- 引入FIFO队列机制确保请求顺序处理 - 使用互斥处理防止并发竞态条件 - 优化时间戳管理,避免频繁数组过滤 - 添加请求追踪和调试方法 - 使用项目一致的异步等待方式
1 parent 4091a38 commit 145ddcd

1 file changed

Lines changed: 80 additions & 9 deletions

File tree

deno-proxy/src/rate_limiter.ts

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,94 @@
1-
import { delay } from "https://deno.land/std@0.224.0/async/delay.ts";
1+
interface PendingRequest {
2+
id: string;
3+
resolve: () => void;
4+
timestamp: number;
5+
}
6+
7+
// 简单的异步等待函数,与项目中其他部分保持一致
8+
function sleep(ms: number): Promise<void> {
9+
return new Promise(resolve => setTimeout(resolve, ms));
10+
}
211

312
export class RateLimiter {
413
private timestamps: number[] = [];
14+
private pendingQueue: PendingRequest[] = [];
15+
private isProcessing = false;
16+
private requestCounter = 0;
517

618
constructor(private readonly limit: number, private readonly windowMs: number) {}
719

820
async acquire(): Promise<void> {
921
if (this.limit <= 0) {
1022
return;
1123
}
12-
while (true) {
13-
const now = Date.now();
14-
this.timestamps = this.timestamps.filter((ts) => now - ts < this.windowMs);
15-
if (this.timestamps.length < this.limit) {
16-
this.timestamps.push(now);
17-
return;
24+
25+
const requestId = `req_${++this.requestCounter}_${Date.now()}`;
26+
27+
return new Promise<void>((resolve) => {
28+
// 将请求添加到队列中,确保先来后到
29+
this.pendingQueue.push({
30+
id: requestId,
31+
resolve,
32+
timestamp: Date.now(),
33+
});
34+
35+
// 如果当前没有在处理队列,开始处理
36+
if (!this.isProcessing) {
37+
this.processQueue();
38+
}
39+
});
40+
}
41+
42+
private async processQueue(): Promise<void> {
43+
if (this.isProcessing || this.pendingQueue.length === 0) {
44+
return;
45+
}
46+
47+
this.isProcessing = true;
48+
49+
try {
50+
while (this.pendingQueue.length > 0) {
51+
const now = Date.now();
52+
53+
// 清理过期的时间戳
54+
this.cleanupExpiredTimestamps(now);
55+
56+
// 如果还有配额,处理下一个请求
57+
if (this.timestamps.length < this.limit) {
58+
const request = this.pendingQueue.shift()!;
59+
this.timestamps.push(now);
60+
request.resolve();
61+
} else {
62+
// 没有配额,计算需要等待的时间
63+
const oldestTimestamp = this.timestamps[0];
64+
const waitMs = Math.max(0, this.windowMs - (now - oldestTimestamp));
65+
66+
if (waitMs > 0) {
67+
// 等待最旧的时间戳过期
68+
await sleep(waitMs);
69+
} else {
70+
// 如果等待时间为0,立即清理并继续
71+
this.cleanupExpiredTimestamps(Date.now());
72+
}
73+
}
1874
}
19-
const waitMs = Math.max(0, this.windowMs - (now - this.timestamps[0]));
20-
await delay(waitMs || 1);
75+
} finally {
76+
this.isProcessing = false;
2177
}
2278
}
79+
80+
private cleanupExpiredTimestamps(now: number): void {
81+
// 保留在时间窗口内的时间戳
82+
this.timestamps = this.timestamps.filter((ts) => now - ts < this.windowMs);
83+
}
84+
85+
// 用于测试和调试的方法
86+
getQueueLength(): number {
87+
return this.pendingQueue.length;
88+
}
89+
90+
getActiveRequestsCount(): number {
91+
this.cleanupExpiredTimestamps(Date.now());
92+
return this.timestamps.length;
93+
}
2394
}

0 commit comments

Comments
 (0)