跳到主要内容

Upstash 封装与实践

Upstash 由于其 Serverless 和基于 HTTP 的特性,非常适合集成在 Next.js 的服务端架构(RSC、Middleware、Route Handlers)中。通过合理的封装,可以实现高效的限流、缓存管理和向量检索。

1. 架构流向:Client -> Service -> Integration

由于 Redis 通常涉及敏感操作和 Token 安全,封装建议主要集中在服务端层面

  1. Client 层:初始化 Redis 和 Ratelimit 单例。
  2. Service 层:封装具体的业务逻辑(如:增加点击量、缓存用户信息)。
  3. 集成层:在渲染组件或中间件中调用。

2. 第 0 步:初始化客户端 (TS 版)

确保环境变量 UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN 已配置。

// lib/upstash.ts
import { Redis } from "@upstash/redis";
import { Ratelimit } from "@upstash/ratelimit";

// 1. Redis 实例
export const redis = Redis.fromEnv();

// 2. 限流器实例 (基于 Redis)
export const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, "10 s"), // 自定义规则
analytics: true,
});

3. 第一步:封装常通业务方法 (Service)

// services/redisService.ts
import { redis, ratelimit } from "../lib/upstash";

/**
* 访问统计:增加计数并返回
*/
export const incrementCount = async (key: string) => {
return await redis.incr(key);
};

/**
* 缓存数据封装
*/
export const cacheData = {
set: async <T>(key: string, value: T, ex = 3600) => {
return await redis.set(key, JSON.stringify(value), { ex });
},
get: async <T>(key: string): Promise<T | null> => {
const data = await redis.get(key);
if (!data) return null;
return typeof data === "string" ? JSON.parse(data) : data;
},
};

/**
* 通用限流校验
*/
export const checkRateLimit = async (identifier: string) => {
const result = await ratelimit.limit(identifier);
return result; // 返回 { success, limit, remaining, reset }
};

4. 第二步:在 Next.js 中的深度应用

方案 A:在 Middleware 中做全局限流

这是保护应用免受恶意请求和爬虫攻击的最佳位置。

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { checkRateLimit } from "./services/redisService";

export async function middleware(request: NextRequest) {
const ip = request.ip ?? "127.0.0.1";
const { success, limit, remaining } = await checkRateLimit(`mw_${ip}`);

if (!success) {
return new NextResponse("请求过于频繁", {
status: 429,
headers: { "X-RateLimit-Limit": limit.toString() },
});
}
return NextResponse.next();
}

方案 B:在 Server Components (RSC) 中获取实时状态

无需经过 API 路由,直接在服务端拉取 Redis 数据。

// app/dashboard/page.tsx
import { incrementCount, cacheData } from "@/services/redisService";

export default async function Dashboard() {
// 1. 实时访问计数
const views = await incrementCount("dashboard_views");

// 2. 尝试从缓存读取耗时配置
let config = await cacheData.get("site_config");
if (!config) {
// config = await fetchFromSlowDB();
// await cacheData.set('site_config', config);
}

return (
<div>
<h1>仪表盘</h1>
<p>实时访问量:{views}</p>
</div>
);
}

5. 配合 React Query (Client side)

如果你需要在客户端按钮点击或实时交互中调用 Upstash,建议通过 Next.js 的 Route Handlers 作为中转,保护 Token。

// app/api/vote/route.ts (服务端)
export async function POST(req: Request) {
const { itemId } = await req.json();
const newVotes = await redis.incr(`vote:${itemId}`);
return Response.json({ votes: newVotes });
}

// hooks/useVote.ts (客户端)
export function useVote(itemId: string) {
const queryClient = useQueryClient();

return useMutation({
mutationFn: async () => {
const res = await fetch("/api/vote", {
method: "POST",
body: JSON.stringify({ itemId }),
});
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["votes", itemId] });
},
});
}

6. 核心建议与避坑

  1. 环境变量命名:Upstash SDK 会静默读取以 UPSTASH_REDIS_REST_ 开头的环境变量,确保不要在客户端侧(NEXT_PUBLIC_)暴露这些变量。
  2. 原子性:利用 redis.pipeline() 批量处理多个命令,减少 HTTP 往返延迟。
  3. 序列化:Upstash Redis 虽然能自动处理对象,但在复杂的 TypeScript 项目中,手动进行 JSON.parse/stringify 结合泛型能提供更准确的类型保障。
  4. Vector 数据库:对于 AI 场景,建议将索引操作封装在独立的向量服务中,并利用 metadata 存储原始文档 ID。

相关资源: