Vercel Edge Config 完全指南:毫秒级配置下发与 A/B 测试驱动

深入 Vercel Edge Config 全局键值存储:与 Edge Functions / Middleware 的低延迟集成、A/B 测试与功能开关实现、多环境管理、版本控制与回滚策略,提供 TypeScript 端到端实现与性能基准。

前置阅读:建议先阅读 Vercel Edge Functions 深度指南 了解 Edge Runtime 基础知识。

关键概念:Edge Config 是 Vercel 提供的全局键值存储,数据部署到全球 Edge Network,读取延迟 < 5ms,写入后 10 秒内全球同步,专为配置管理、功能开关和 A/B 测试场景设计。

  1. ² 核心特性与适用场景

    特性规格说明
    读取延迟< 5ms (p99)数据缓存在 Edge Network 节点
    写入同步~10s 全球传播异步最终一致
    单条记录上限8 KB适合配置和开关,不适合大体积数据
    单 Store 记录数10,000 条(Pro)按 tier 递增
    读取配额500K reads/mo(Hobby)Pro 50M/mo

    最佳场景

    • 🚦 功能开关(Feature Flags)— 无需重新部署即可控制功能上线
    • 🧪 A/B 测试 — 基于用户属性分流
    • 🌍 维护模式 — 一键切换 “系统维护中” 页面
    • 💰 动态定价 — 实时调整费率或促销配置

    不适用于:用户会话存储(写少读多但写延迟不可控)、大文件缓存、需要强一致性的事务。

  2. ³ 基础操作与数据结构

    # 安装 CLI 工具
    npm i -g vercel
    
    # 创建 Edge Config Store
    vercel edge-config create my-app-config
    
    # 设置键值(支持 JSON 值)
    vercel edge-config set my-app-config FEATURE_DARK_MODE=true
    vercel edge-config set my-app-config PRICING_V2='{"enabled":true,"percentage":25}'
    
    # 读取
    vercel edge-config get my-app-config FEATURE_DARK_MODE
    
    # 批量导入
    vercel edge-config import my-app-config ./flags.json
    
    # flags.json
    {
      "features": {
        "new_checkout": {
          "enabled": true,
          "rollout_percentage": 10,
          "target_regions": ["us-east-1", "eu-west-1"]
        },
        "beta_search": {
          "enabled": false,
          "allowed_user_ids": ["user_123", "user_456"]
        }
      },
      "maintenance": {
        "active": false,
        "message": "Scheduled maintenance at 02:00 UTC"
      }
    }
    
  3. ⁴ Edge Functions 中读取

    // app/api/flags/route.ts
    import { get } from "@vercel/edge-config";
    
    export const runtime = "edge";
    
    export async function GET() {
      // 单次读取:< 5ms
      const features = await get("features");
      const maintenance = await get("maintenance");
    
      return Response.json({
        features,
        maintenance,
        timestamp: Date.now(),
      });
    }
    
    // 批量读取(减少网络往返)
    import { getAll } from "@vercel/edge-config";
    
    export async function GET() {
      // 一次获取多个 key
      const { features, maintenance, pricing } = await getAll([
        "features",
        "maintenance",
        "pricing",
      ]);
    
      return Response.json({ features, maintenance, pricing });
    }
    
  4. ⁵ Middleware 中实现 Feature Flags

    // middleware.ts
    import { NextResponse } from "next/server";
    import { get } from "@vercel/edge-config";
    import type { NextRequest } from "next/server";
    
    export const config = {
      matcher: ["/checkout/:path*", "/dashboard/:path*"],
    };
    
    export async function middleware(request: NextRequest) {
      const features = await get<FeatureFlags>("features");
    
      // 1. 维护模式检查(最高优先级)
      const maintenance = await get<MaintenanceConfig>("maintenance");
      if (maintenance?.active) {
        return NextResponse.rewrite(new URL("/maintenance", request.url));
      }
    
      // 2. 功能开关路由重写
      if (request.nextUrl.pathname.startsWith("/checkout")) {
        const checkoutFlag = features?.new_checkout;
    
        if (checkoutFlag?.enabled) {
          // A/B 测试:按用户 ID hash 分流
          const userId = request.cookies.get("user_id")?.value;
          const isInRollout = userId
            ? hashToPercentage(userId) < (checkoutFlag.rollout_percentage || 0)
            : false;
    
          if (isInRollout) {
            // 重写到新版结账页
            return NextResponse.rewrite(
              new URL("/checkout/v2" + request.nextUrl.pathname.replace("/checkout", ""), request.url)
            );
          }
        }
      }
    
      return NextResponse.next();
    }
    
    // 简单哈希函数:将字符串转为 0-100 的百分比
    function hashToPercentage(str: string): number {
      let hash = 0;
      for (let i = 0; i < str.length; i++) {
        hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0;
      }
      return Math.abs(hash) % 100;
    }
    
  5. ⁶ A/B 测试完整实现

    // lib/ab-test.ts
    import { get } from "@vercel/edge-config";
    
    interface ABTestConfig {
      enabled: boolean;
      variants: Array<{
        id: string;
        weight: number;  // 0-1
        target?: {
          region?: string[];
          device?: ("mobile" | "desktop")[];
        };
      }>;
    }
    
    export async function assignVariant(
      experimentId: string,
      userId: string,
      context: { region?: string; device?: string }
    ): Promise<string | null> {
      const config = await get<ABTestConfig>(`ab_${experimentId}`);
      if (!config?.enabled) return null;
    
      // 按目标过滤
      const eligible = config.variants.filter(v => {
        if (v.target?.region && !v.target.region.includes(context.region || "")) return false;
        if (v.target?.device && !v.target.device.includes(context.device as any)) return false;
        return true;
      });
    
      // 一致性哈希:同一用户始终分配到同一 variant
      const hash = hashToPercentage(`${experimentId}:${userId}`);
      let cumulative = 0;
      for (const variant of eligible) {
        cumulative += variant.weight;
        if (hash < cumulative * 100) return variant.id;
      }
    
      return eligible[0]?.id || null;
    }
    
    // 在 Edge Function 中使用
    export async function GET(request: Request) {
      const userId = request.headers.get("x-user-id") || "anonymous";
      const variant = await assignVariant("homepage_redesign", userId, {
        region: request.headers.get("x-vercel-ip-country") || "",
        device: "desktop",
      });
    
      if (variant === "v2") {
        return fetch("https://cdn.example.com/homepage-v2.html");
      }
      return fetch("https://cdn.example.com/homepage.html");
    }
    
  6. ⁷ 版本控制与回滚

    Edge Config 本身没有原生版本控制,需要通过外部方案实现:

    // lib/edge-config-versioned.ts
    import { createClient } from "@vercel/edge-config";
    
    const client = createClient(
      process.env.EDGE_CONFIG,
      { cache: "no-store" }  // 跳过缓存,始终读取最新
    );
    
    // 方案:配置变更时写入带版本号的 key
    async function setVersionedConfig(key: string, value: any) {
      const version = Date.now();
      await client.set(`${key}:v${version}`, JSON.stringify(value));
      await client.set(`${key}:current`, version.toString());
    }
    
    async function getVersionedConfig(key: string, version?: string) {
      const targetVersion = version || (await client.get(`${key}:current`));
      return client.get(`${key}:v${targetVersion}`);
    }
    
    // 回滚:只需将 current 指向前一个版本
    async function rollback(key: string) {
      // 获取版本列表并回退
      // 实际实现需要在外部(如 Redis / 数据库)维护版本索引
    }
    

    推荐方案:将 Edge Config 作为热缓存层,配置源放在 Git(版本控制)或数据库中,通过 CI/CD 或管理后台同步到 Edge Config。

  7. ⁸ 与专业 Feature Flags 服务对比

    维度Vercel Edge ConfigLaunchDarklyUnleashFlagsmith
    读取延迟< 5ms~50-100ms~20-50ms~30-80ms
    SDK 复杂度极简(单函数)中等中等中等
    A/B 测试需自建原生支持原生支持原生支持
    分析与归因完整基础基础
    规则引擎简单 JSON复杂规则集中等中等
    成本(起始)含在 Vercel 套餐$10/席位/mo开源(自托管)开源/托管
    适合场景Vercel + 简单开关企业级全功能自托管偏好开源预算敏感

    推荐:如果全栈在 Vercel,且需求是简单的功能开关和维护模式,Edge Config 足够;如果需要复杂 A/B 测试和用户分析,搭配 LaunchDarkly SDK 在客户端实现。

延伸阅读

← 上一篇

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「工具与平台」更多文章