Cloudflare Workers:边缘运行 JS / Wasm 的实际玩法

起因

某些场景"在边缘节点跑代码" 比"回源" 更优:

  • 全球用户都需要的轻请求(API rate limit / auth check / A/B redirect)
  • 静态站定制(个性化 header / cookie 处理)
  • 流量塑形(特定 user 转到 staging)
  • 短 latency API(< 50ms 全球)

Cloudflare Workers:V8 isolate 跑在 250+ 城市边缘节点。
冷启动 < 5ms(不是 Lambda 那种),按请求计费便宜。

hello world

// worker.js
export default {
    async fetch(request, env, ctx) {
        return new Response('Hello from the edge!');
    },
};

部署:

npm install -g wrangler
wrangler init my-worker
cd my-worker
wrangler deploy

5 分钟得到 my-worker.user.workers.dev URL,全球 250+ 城市同时跑。

完整示例:geo redirect

export default {
    async fetch(request) {
        const country = request.cf.country;     // CF 自动加 country header
        const url = new URL(request.url);

        if (country === 'CN' && url.hostname === 'example.com') {
            return Response.redirect('https://cn.example.com' + url.pathname, 302);
        }

        // 其它转到 origin
        return fetch(request);
    },
};

中国用户自动重定向到 cn 子域,其它人正常回源。

A/B 测试

export default {
    async fetch(request) {
        const cookie = request.headers.get('cookie') || '';
        let variant = parseCookie(cookie, 'variant');

        if (!variant) {
            variant = Math.random() < 0.5 ? 'A' : 'B';
        }

        const response = await fetch(
            variant === 'B'
                ? request.url.replace('example.com', 'beta.example.com')
                : request,
        );

        const newResponse = new Response(response.body, response);
        newResponse.headers.set('set-cookie', `variant=${variant}; Path=/; Max-Age=86400`);
        return newResponse;
    },
};

50/50 分流,cookie 粘性。无需改后端。

鉴权 / API gateway

export default {
    async fetch(request, env) {
        const auth = request.headers.get('authorization');
        if (!auth || !auth.startsWith('Bearer ')) {
            return new Response('Unauthorized', { status: 401 });
        }

        const token = auth.slice(7);

        // 验 JWT(用 Web Crypto API)
        const valid = await verifyJWT(token, env.JWT_PUBLIC_KEY);
        if (!valid) return new Response('Invalid token', { status: 401 });

        // 加 user info 转到 origin
        const newRequest = new Request(request);
        newRequest.headers.set('x-user-id', valid.sub);
        return fetch(newRequest);
    },
};

边缘 JWT 验证 → 无效请求不打到 origin → 省 origin 带宽 / CPU。

KV / D1 / R2 存储

Workers 配套存储:

  • KV:edge K/V,最终一致,读快写慢
  • D1:SQLite at edge(每个 region 副本)
  • R2:S3 兼容对象存储,无 egress 费
// 读 KV
const value = await env.MY_KV.get('user:42');

// D1 query
const result = await env.MY_DB.prepare(
    'SELECT * FROM users WHERE id = ?').bind(42).first();

// R2 上传
await env.MY_BUCKET.put('file.bin', request.body);

Workers + KV / D1 / R2 全栈在边缘 → 静态站 + API + 数据全套。

Durable Objects

需要强一致 + stateful → DO:

export class Counter {
    constructor(state, env) {
        this.state = state;
    }
    async fetch(request) {
        let count = (await this.state.storage.get('count')) || 0;
        count++;
        await this.state.storage.put('count', count);
        return new Response(count.toString());
    }
}

// Worker
export default {
    async fetch(request, env) {
        const id = env.COUNTER.idFromName('global');
        const obj = env.COUNTER.get(id);
        return obj.fetch(request);
    },
};

DO 是全球唯一 instance(按 name),适合:counter / chat room /
collaborative state。WebSocket 跨用户的协作 app 杀手锏。

limits

  • 单请求 CPU: 50ms (free) / 30s (paid)
  • memory: 128 MB
  • subrequest: 50 (free) / 1000 (paid)
  • script size: 1 MB compressed (10 MB paid)

不像 Lambda 可以重计算几分钟。
Workers 是"轻量边缘 hook",不是通用 compute。

价格

  • free tier: 100k req/day
  • $5/月: 10M req
  • $5 per additional 1M req

KV / R2 / D1 各自计费但都便宜。
对比 Lambda(cold start + GB-second + egress):高 QPS edge 场景 Workers
便宜 5-10x。

本地 dev

wrangler dev          # 本地起 worker(用 V8 模拟)
wrangler dev --remote # 直接在 CF 边缘 dev

wrangler.toml

name = "my-worker"
main = "src/index.js"
compatibility_date = "2024-05-25"

[vars]
ENVIRONMENT = "production"

[[kv_namespaces]]
binding = "MY_KV"
id = "..."

[[d1_databases]]
binding = "MY_DB"
database_name = "myapp"
database_id = "..."

TypeScript / Hono

// 用 Hono framework
import { Hono } from 'hono';

const app = new Hono();

app.get('/api/users/:id', async (c) => {
    const id = c.req.param('id');
    const user = await c.env.MY_DB.prepare(
        'SELECT * FROM users WHERE id = ?').bind(id).first();
    return c.json(user);
});

export default app;

Hono 是 Workers 上的 Express 替代,类型 + 路由友好。

跟 Lambda@Edge 对比

CF Workers AWS Lambda@Edge
启动 < 5ms (V8 isolate) 100-500ms (cold)
语言 JS / Wasm / Python Node / Python
限制 50ms CPU 5s
存储 KV / D1 / R2 / DO 需调外部
价格 $0.50 / M req $0.60 / M req(更贵)
Region 250+ CF 100+

Workers 现在边缘 compute 几乎事实标准。
AWS 也在推 Lambda@Edge 但慢。

真实 case:API rate limit

我们 API 想全球分布式 rate limit(不只是单 region)。

export default {
    async fetch(request, env) {
        const ip = request.headers.get('cf-connecting-ip');
        const key = `rl:${ip}`;
        const count = (await env.RL_KV.get(key)) || 0;
        if (count >= 100) {
            return new Response('Too Many Requests', { status: 429 });
        }
        await env.RL_KV.put(key, (parseInt(count) + 1).toString(), { expirationTtl: 60 });
        return fetch(request);
    },
};

每 IP 每分钟 100 req,跨 CF region 累计(KV 最终一致,5-10s 同步 →
某 region 用户可能 over limit 一点,可接受)。

强一致 → 用 DO + sliding window。

不适合的场景

  • 长跑 CPU(视频转码 / ML inference):用 Workers AI / cloud function
  • 大文件处理(> 100 MB body):直接 R2
  • 复杂 stateful 流(DB transaction 多):传统 API server

踩过的坑

  1. request body 不能多次读request.body 是 stream,读一次。
    需多用:const body = await request.text() 先 buffer。

  2. fetch subrequest 计数:每 fetch() 算 1 subrequest,超 50
    (free)报错。复杂 worker 谨慎。

  3. KV 写 eventual consistency:刚写完读可能旧值。读 critical 用
    D1 或 DO。

  4. environment binding 没配:本地 dev 跑通,部署后 env.MY_KV
    undefined → 看 wrangler.toml 是否 deploy。

  5. package size 1MB:依赖大(如 lodash 全部 import)→ size 超。
    tree-shake + 小依赖(zod / arktype 替代)。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。