یک REST API با محدودیت نرخ در Node.js
بخش ۲: یک Token Bucket
پیادهسازی الگوریتم token bucket در Redis تا محدودیت در همهی نمونههای سرویس شما پابرجا بماند.
یک محدودکنندهی درونحافظهای، بهمحض اجرای دو نمونه از سرویس از کار میافتد. سطل (bucket) را در Redis نگه میداریم تا هر نمونه یک منبع واحد از حقیقت را به اشتراک بگذارد.
الگوریتم
هر کلاینت یک سطل دارد که با نرخی ثابت تا یک سقف مشخص پر میشود. هر درخواست یک توکن هزینه دارد؛ اگر سطل خالی باشد، درخواست رد میشود.
async function allow(key: string): Promise<boolean> {
const now = Date.now()
const capacity = 20
const refillPerSec = 5
const [tokensStr, tsStr] = await redis.hmget(key, "tokens", "ts")
let tokens = tokensStr ? Number(tokensStr) : capacity
const last = tsStr ? Number(tsStr) : now
tokens = Math.min(capacity, tokens + ((now - last) / 1000) * refillPerSec)
if (tokens < 1) return false
await redis.hmset(key, { tokens: tokens - 1, ts: now })
return true
}
در محیط تولید، این را بهصورت یک اسکریپت Lua اجرا کنید تا خواندن-تغییر-نوشتن اتمی باشد. دو درخواست همزمان هرگز نباید یک شمار توکن یکسان را ببینند.
نصب آن
app.use(async (req, res, next) => {
if (await allow(`rl:${req.ip}`)) return next()
res.status(429).json({ error: "rate limited" })
})
هدرهای Retry-After و X-RateLimit-Remaining را برگردانید تا کلاینتها
مؤدبانه عقبنشینی کنند. این پایان مجموعه است — یک محدودکنندهی توزیعشده در
کمتر از ۴۰ خط.