计划执行与检查节点
executing-plans
按既定实施计划完成工作,保持过程检查和反馈。
upstash-ratelimit【效率与协作】Add rate limiting to API routes, middleware, and edge functions with @upstash/ratelimit: sliding window, fixed window, and token bucket backed by Upstash Redis.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
@upstash/ratelimit implements distributed rate limiting on top of Upstash
Redis. Because state lives in Redis, every instance of a serverless function
or edge worker shares the same counters, which an in-memory limiter cannot
do. It ships three algorithms (fixed window, sliding window, token bucket),
per-identifier keys, optional in-memory blocking of already-limited
identifiers, and optional analytics.
429 Too Many Requests.api-rate-limit-handler.npm install @upstash/ratelimit @upstash/redis
Set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN in the environment.
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
export const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"), // 10 requests per 10 seconds
prefix: "rl:api",
analytics: true,
});
Constructing the limiter at module scope lets the built-in ephemeral cache short-circuit blocked identifiers without a Redis call.
limit() with a stable identifierconst { success, limit, remaining, reset, pending } = await ratelimit.limit(userId);
success is false when the identifier is over its limit. reset is a Unix
timestamp in milliseconds. pending is a promise for background work
(analytics, multi-region sync); await it or pass it to waitUntil on edge
runtimes so the function is not frozen before it completes.
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
import { NextResponse, type NextRequest } from "next/server";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(20, "1 m"),
});
export async function middleware(request: NextRequest) {
const ip = request.headers.get("x-forwarded-for") ?? "anonymous";
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
return new NextResponse("Too Many Requests", {
status: 429,
headers: {
"X-RateLimit-Limit": String(limit),
"X-RateLimit-Remaining": String(remaining),
"X-RateLimit-Reset": String(reset),
"Retry-After": String(Math.ceil((reset - Date.now()) / 1000)),
},
});
}
return NextResponse.next();
}
export const config = { matcher: "/api/:path*" };
const limiters = {
free: new Ratelimit({
redis: Redis.fromEnv(),
prefix: "rl:free",
limiter: Ratelimit.tokenBucket(5, "10 s", 10), // refill 5 per 10 s, burst 10
}),
pro: new Ratelimit({
redis: Redis.fromEnv(),
prefix: "rl:pro",
limiter: Ratelimit.tokenBucket(50, "10 s", 100),
}),
};
const { success } = await limiters[plan].limit(apiKey);
prefix per endpoint or plan so limits do not collide.Retry-After and X-RateLimit-* headers with 429 responses.slidingWindow for most APIs; use tokenBucket when short bursts
are acceptable; use fixedWindow when the lowest Redis cost matters.Ratelimit inside the request handler.pending completing on its own in edge runtimes.x-forwarded-for without validating it is set by
your proxy; clients can spoof it otherwise.limit() call is at least one HTTP round trip to Redis, so it adds
latency to every request it guards.MultiRegionRatelimit trades strict accuracy for lower latency and does
not support the token bucket algorithm.timeout (5 s) lets requests through
(reason: "timeout"); this fails open, not closed.undefined or a fresh random value.pending to waitUntil (ctx.waitUntil(pending)) so
the background request is not cancelled when the response is sent.@upstash-redis - The client this package uses for storage.@api-rate-limit-handler - Client-side backoff and retry when you are the
one being rate limited.@upstash-qstash - Queue and smooth traffic to downstream services instead
of rejecting it.