Next.js App Router中如何实现单例Unleash Client实例
Unleash服务器超时与Next.js实例过多问题
我的Unleash服务器出现超时问题,Next.js应用每秒请求数(RPS)最高仅约150,数值过低。我怀疑这是应用存在多个Unleash Client实例导致的。
我按照@unleash/nextjs文档在Next.js App Router中做了如下实现:
Dependencies - "@unleash/nextjs": "^1.4.1" - "next": "14.2.3" (使用App Router) export default async function getFlag(): Promise<{ isEnabled: (name: string) => boolean getVariant: (name: string) => IVariant }> { const sessionId = `${Math.floor(Math.random() * 1_000_000_000)}` try { const definitions = await getDefinitions({ fetchOptions: { next: { revalidate: 0 }, }, }) const { toggles } = evaluateFlags(definitions, { sessionId, }) const flags = flagsClient(toggles) return flags } catch (e) { } } export default async function Layout(props: PropsWithChildren) { const flag = await getFlag(); // 在这里使用flag return <SomeComponent />; }
我的问题:
- 当前实现是否确实创建了多个客户端实例,进而导致Unleash服务器崩溃?
- 如果是这样,如何在Next.js SSR应用的所有路由处理器中仅使用单个客户端实例?
问题解答
1. 当前实现是否会创建多实例并导致服务器超时?
是的,你的实现确实会引发大量不必要的Unleash请求,进而拖垮服务器:
getDefinitions设置了revalidate: 0,意味着每次请求都会重新拉取全量的feature flag定义,完全没有缓存复用。- 每个请求进入Layout组件时,都会调用
getFlag(),每次调用都会创建新的flagsClient实例,同时触发一次对Unleash服务器的请求。 - 当RPS达到150时,Unleash服务器每秒要处理150次全量拉取请求,很容易被打满,最终出现超时。
2. 如何实现单实例与请求复用?
你需要结合Next.js的缓存机制和单例模式,复用flag定义和客户端逻辑,具体操作如下:
步骤1:创建单例Unleash工具类
在项目中新建utils/unleash.ts文件,实现缓存与单例逻辑:
import { getDefinitions, evaluateFlags, flagsClient, IVariant } from '@unleash/nextjs'; // 缓存flag定义,避免重复请求 let cachedDefinitions: Awaited<ReturnType<typeof getDefinitions>> | null = null; // 缓存过期时间(可根据业务调整,示例为5分钟) const CACHE_TTL = 5 * 60 * 1000; let lastFetchTime = 0; export async function getUnleashFlags(sessionId: string) { const now = Date.now(); // 缓存失效时重新拉取定义 if (!cachedDefinitions || now - lastFetchTime > CACHE_TTL) { cachedDefinitions = await getDefinitions({ fetchOptions: { next: { revalidate: CACHE_TTL / 1000 }, // 转换为秒数设置revalidate }, }); lastFetchTime = now; } const { toggles } = evaluateFlags(cachedDefinitions, { sessionId }); return flagsClient(toggles); }
步骤2:在Layout中复用工具类
修改你的getFlag函数和Layout组件:
import { getUnleashFlags } from '@/utils/unleash'; export default async function getFlag(): Promise<{ isEnabled: (name: string) => boolean getVariant: (name: string) => IVariant }> { // 建议用用户ID/稳定匿名ID替代随机sessionId,保证同用户评估结果一致 const sessionId = `${Math.floor(Math.random() * 1_000_000_000)}`; try { return await getUnleashFlags(sessionId); } catch (e) { // 异常降级,返回默认值避免影响业务 return { isEnabled: () => false, getVariant: () => ({ name: 'disabled', enabled: false }) as IVariant }; } } export default async function Layout(props: PropsWithChildren) { const flag = await getFlag(); // 使用flag return <SomeComponent />; }
额外优化建议
- 替换随机
sessionId:如果用户有登录态,用用户ID;无登录态时用Next.js的cookies生成稳定匿名ID,确保同用户的flag评估结果一致,减少不必要的变体计算。 - 调整
CACHE_TTL:如果你的feature flag不会频繁变更,可以把缓存时间设得更长(比如10-15分钟),进一步降低Unleash服务器的请求压力。
内容的提问来源于stack exchange,提问作者yosiawan
相关产品推荐
相关产品推荐

