怎么用 Durable Objects 做限流?
我在本地把滑动窗口设成 60 秒、上限 5,连续调用 7 次:前 5 次通过,后 2 次被拒绝,一次不差。
所以答案是:给每个限流 key(用户、IP、API token)一个 Durable Object(下称 DO),在对象里用 SQLite 存时间戳做滑动窗口。 计数在对象内串行,没有分布式计数的不一致。
这是 DO 的经典用法,但它不是最便宜的做法。下面先给代码,再讲代价,最后说什么时候不值得用。串行的原理见 Durable Objects:单线程模型、一个 ID 一个实例与存储。
滑动窗口限流怎么写?
export class Limiter extends DurableObject<Env> {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS w(ts INTEGER)");
}
allow(limit: number, windowMs: number): boolean {
const sql = this.ctx.storage.sql;
const now = Date.now();
sql.exec("DELETE FROM w WHERE ts < ?", now - windowMs);
const c = sql.exec("SELECT count(*) AS c FROM w").one().c as number;
if (c >= limit) return false;
sql.exec("INSERT INTO w VALUES (?)", now);
return true;
}
}allow 里没有 await,清理过期、计数、插入在一个同步段内完成,所以并发调用不会互相穿插。
这样做有什么代价?
每次判断都是一次到 DO 的往返。调用者离该对象很远时,会有额外延迟。
每次通过还会写一行,计入 SQLite 的 rows written 计费。
单个热点 key,比如一个共享 IP,会把单个对象打到软上限。
什么时候不值得用 DO 限流?
如果你只需要「大致限流」,比如防止明显滥用,在边缘做粗粒度判断会更便宜。
需要精确,并且按 key 隔离时,DO 才值得。其他情形,先想想上面三项代价你能不能接受。是否适合 DO 的整体判断,见 什么样的需求适合交给 Durable Objects?。
60 秒窗口、上限 5、连续 7 次调用前 5 通过后 2 拒绝的结果,我在本地 wrangler dev 上跑过。往返延迟、rows written 计费和单对象软上限,我没有测过。计费与限额会变化,以 2026-10 的官方文档为准。