🔍 登录 注册

CDN 边缘函数 把分流与鉴权推到离用户最近的地方

打 打工人 2026-10-23 👁 0 ⏱ 6min

CDN 边缘函数 把分流与鉴权推到离用户最近的地方

边缘函数(Edge Functions)是在 CDN 节点上运行的轻量 JS/WASM 运行时,能力介于"CDN 配置规则"和"源站逻辑"之间。它的正确用法不是把业务逻辑搬到边缘,而是处理那些必须在用户附近决定、且不适合回到源站的事:分流、鉴权跳转、响应改写、缓存策略。

一、什么该放在边缘

适合:A/B 与灰度分流、地区与语言路由、未登录时跳转登录并带 redirect、SEO 与 UA 相关的头部处理、响应头改写(安全头、CORS)、简单缓存绕过判断、图片格式协商。

不适合:需要长时间持有连接、重计算、访问源站数据库、任何有状态写操作。

判断标准是运行时约束:边缘运行时通常有几毫秒到几十毫秒的 CPU 预算、禁止 Node 内置模块、子请求次数受限。把这段代码当作"一个只能跑几十毫秒、不能读数据库的无状态函数"来设计。

// edge/ab-test.ts
export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname !== "/api/v1/search") return fetch(request);

    const uid = request.headers.get("x-user-id");
    const bucket = uid ? fnv32(uid) % 100 : 100;
    const variant = bucket < 10 ? "b" : "a";

    const up = new Request(request);
    up.headers.set("x-experiment", variant);
    const res = await fetch(up);
    res.headers.set("x-experiment", variant);
    return res;
  },
};

function fnv32(s) {
  let h = 0x811c9dc5;
  for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193); }
  return h >>> 0;
}

按用户主体哈希而不是随机,才能保证同一用户始终落在同一个桶里——这是做分流最常见的 bug。

二、鉴权跳转的最佳落点

未登录访问受保护页面时跳转登录,放在边缘最合适:省一次回源、延迟最低、逻辑最短。

export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (!protectedPath(url.pathname)) return fetch(request);

    const token = readCookie(request.headers.get("cookie"), "okgoes_token");
    if (token) {
      const r = await fetch(request);
      return r.status === 401 ? redirectToLogin(request, url) : r;
    }
    return redirectToLogin(request, url);
  },
};

function redirectToLogin(req, url) {
  const next = encodeURIComponent(url.pathname + url.search);   // 保留原路径
  return new Response(null, {
    status: 302,
    headers: { location: `/login?redirect=${next}`, "cache-control": "no-store" },
  });
}

两个细节决定正确性:redirect 参数必须编码,否则路径里带 ?a=b 会被截断;跳转响应必须 no-store,否则 CDN 会把 302 缓存成永久跳转,登录之后依然被踢回登录页。这类缓存污染问题在边缘场景极常见。

边缘只能判断"有没有 token",不能验证签名(验签要么回源要么在边缘做 HMAC,前者延迟高、后者密钥管理复杂)。边缘永远不是授权决策的最后防线,源站的鉴权校验照常执行,边缘只做体验优化。

三、响应改写要保守

修改响应头是边缘最常见的正当用途:

function harden(res) {
  const h = new Headers(res.headers);
  h.set("x-content-type-options", "nosniff");
  h.set("referrer-policy", "strict-origin-when-cross-origin");
  h.set("permissions-policy", "geolocation=(), camera=(), microphone=()");
  if (new URL(res.url).pathname.startsWith("/static/")) {
    h.set("cache-control", "public, max-age=31536000, immutable");
  }
  return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
}

改写响应体要非常小心——它会破坏流式传输与压缩。需要替换少量内容(比如注入一个 meta 标签)时应使用 HTMLStream 之类的流式改写器,而不是 await res.text() 全量缓冲:

return new HTMLStream(res.body, [
  ["meta[name=description]", { set: { content: desc } }],
]);

全量缓冲响应体会使首字节延迟上升、内存占用增加,也让大文件下载场景直接崩掉。这是边缘函数最主要的性能陷阱。

四、缓存交互的三条规则

边缘函数与 CDN 缓存的关系,决定了性能上限。

  1. 确认函数是否参与缓存键。多数平台默认把 URL 与部分 header 纳入缓存键,自定义 header 不参与;分流用了自定义 header 就要显式把它加入缓存键,否则所有用户共享同一份缓存(实验彻底失效);
  2. 带 cookie 的响应禁止缓存,或把 cookie 归一化后加入缓存键,否则会出现用户之间内容串台;
  3. Vary 要精确,Vary: * 等于关闭缓存。
res.headers.set("cache-control", "public, max-age=60, stale-while-revalidate=600");
res.headers.set("vary", "Accept");
if (hasSessionCookie(request)) res.headers.set("cache-control", "private, no-store");

stale-while-revalidate 在边缘非常划算:命中过期缓存时立即返回旧内容并后台回源,既降低回源压力又降低尾延迟。

五、可观测与灰度

边缘代码分布在几十上百个节点,出问题很难复现,事前要求比事后调试重要得多。

必备三件事:每个请求生成 request-id,在边缘与源站日志里都带上;分流变体打进响应头(x-experiment),排查用户反馈时能立刻确定命中哪个版本;灰度从 1% 起步,边缘函数发布通常全网生效,务必用平台自带的百分比发布能力,而不是直接全量。

const rid = request.headers.get("x-request-id") || crypto.randomUUID();
up.headers.set("x-request-id", rid);
res.headers.set("x-request-id", rid);

日志采集上,边缘的同步日志接口会阻塞响应,尽量用异步日志通道,并且只采样打(比如 1%)。在高频路径打全量日志会让成本与延迟一起失控。

六、密钥与依赖的边界

边缘运行时的安全约束要注意两点:不要把长期有效的密钥放边缘(代码在边缘节点执行,信任边界比源站弱),尽量用短期令牌或平台提供的密钥管理服务;第三方依赖要极轻,边缘对包体积与冷启动敏感,一个几百 KB 的依赖就能让冷启动延迟明显上升。

同理,边缘不要放任何写操作密钥、数据库凭据、内部服务地址。它属于面向公网的一侧。

小结

边缘函数的定位是在用户附近的无状态短决策:分流、跳转、改头、缓存控制。四条落地纪律——分流按主体哈希、跳转保留并编码 redirect 且响应 no-store、改写响应体必须走流式、缓存键与 Vary 精确对齐。同时要清楚它的边界:不是运行时环境的替代品,不做授权决策的最终防线,不放长期密钥。把边缘当作"更聪明的 CDN 规则"来用,收益最大;把它当作"分布式的 Node 后端"来用,问题最多。

读完了?给作者一点支持

本文采用 CC BY 4.0 协议转载 · © 打工人

评论(0)

登录 后参与评论。

  • 还没有评论,来抢沙发。

🔗 相关文章