Cloudflare KV 与 Durable Objects 深度指南:键值存储与有状态边缘计算

详解 Cloudflare Workers 的两大存储原语:KV(全球分布式键值存储)与 Durable Objects(有状态边缘对象)。覆盖 KV 的最终一致性模型与缓存策略、Durable Objects 的单线程事务保证、WebSocket 协调、计数器/会话/游戏房间等实战场景,以及选择决策矩阵与性能基准。

Cloudflare Workers 默认是无状态的 V8 isolate,但业务几乎都需要存储状态。Cloudflare 提供了两种核心存储原语:**KV(全局分布式键值存储)**和 Durable Objects(有状态边缘对象)。两者在一致性模型、延迟特性和适用场景上截然不同。本文从原理到生产代码,完整覆盖两种存储的选择与实战。


一、KV 存储:最终的全球性

1.1 定位与核心特性

KV 是 Cloudflare 的全球分布式键值存储,设计目标:让配置、元数据、缓存内容在全球任意边缘节点都能以极低延迟读取

特性说明
一致性模型最终一致性(写入后 1 分钟内全局传播)
读取延迟< 10ms(边缘节点内存缓存)
写入延迟< 1s(异步复制到全球数据中心)
键大小限制512KB 键名,25MB 值
存储限额免费 1GB,付费无上限
TTL 支持✅ 键级别过期时间
列表扫描支持前缀匹配,性能随前缀减少提升

1.2 写入流程与一致性

Worker (东京) → 写入 KV (key="user:123")
    ↓
KV 写入到最近的中央存储节点(Core DC)
    ↓
异步复制到全球边缘节点(Edge POP)
    ↓
Worker (纽约) → 读取 KV (key="user:123")
    → 可能读到旧值(复制延迟 < 60s)

关键认知:KV 不是实时同步的。如果你写入后立刻在另一个 Worker 中读取,可能拿到旧数据。不要用它做实时计数器、锁、或要求强一致性的业务

1.3 绑定与使用

// wrangler.toml
[[kv_namespaces]]
binding = "MY_KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

// src/index.ts
export interface Env {
  MY_KV: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // 写入
    await env.MY_KV.put('user:123', JSON.stringify({ name: 'Alice', age: 30 }));
    await env.MY_KV.put('session:abc', 'data', { expirationTtl: 3600 }); // 1 小时后过期

    // 读取
    const user = await env.MY_KV.get('user:123');
    const userWithMetadata = await env.MY_KV.getWithMetadata('user:123');
    // userWithMetadata.value = JSON string
    // userWithMetadata.metadata = { ...custom metadata... }

    // 删除
    await env.MY_KV.delete('user:123');

    // 列表(前缀匹配)
    const list = await env.MY_KV.list({ prefix: 'user:' });
    // list.keys = [{ name: 'user:123', expiration: ... }, ...]

    return Response.json({ user: user ? JSON.parse(user) : null });
  },
};

1.4 缓存策略

KV 会自动在边缘节点缓存热点键值,缓存行为:

操作缓存行为
get()边缘节点缓存,TTL 由 Cloudflare 内部决定(通常 60s)
getWithMetadata()同 get()
put()写入 Core,使旧缓存失效
delete()写入 Core,使旧缓存失效
list()结果不缓存

强制刷新缓存:连续两次相同 key 的 put() 会确保旧缓存被清除,但新值传播仍需要时间。

1.5 KV 最佳场景

场景原因
配置存储配置变更不频繁,最终一致可接受
A/B 测试标志可以容忍几秒的传播延迟
博客文章元数据写少读多,变更后可等几秒
翻译字典几乎只读,完美适合
静态资源缓存HTML/JSON 的 KV 缓存层
GeoIP/黑名单更新后缓慢传播可接受

不适合的场景:实时计数器、库存扣减、会话状态、分布式锁、聊天消息。


二、Durable Objects:有状态的保证

2.1 定位与核心特性

Durable Objects(DO)是 Cloudflare 提供的有状态计算单元:每个 DO 是一个独立的 JavaScript 对象,绑定到一个唯一的 ID,保证单线程执行和 ACID 事务一致性。

特性说明
一致性强一致性(单线程,无并发写入冲突)
状态持久化内存 + 磁盘双保险,Worker 重启后状态恢复
位置首次访问时"创建"在最近的 Core DC
WebSocket✅ 原生支持,可维持长连接
事务内存内状态变更天然原子
计费请求数 + 内存使用时长(GB-秒)

2.2 DO 的生命周期

首次访问 DO (id="room:abc")
    ↓
Cloudflare 路由到最近的 Core DC
    ↓
创建 JavaScript 对象实例(调用 constructor)
    ↓
状态从持久化存储加载到内存
    ↓
处理请求(fetch / WebSocket / alarm)
    ↓
一段时间无请求 → 内存释放(hibernation),但状态保留
    ↓
再次访问 → 从持久化恢复状态,重新创建实例

2.3 两种 ID 生成方式

// 方式一:Name-based ID(推荐)
// 基于字符串生成确定性 ID,相同 name 始终路由到同一个 DO
const id = env.MY_DO.idFromName('room:game-123');
const doStub = env.MY_DO.get(id);

// 方式二:Unique ID(UUID)
// 生成全新的唯一 ID,用于创建全新对象
const id = env.MY_DO.newUniqueId();
const doStub = env.MY_DO.get(id);

2.4 基础 CRUD 示例

// src/index.ts
export interface Env {
  MY_DO: DurableObjectNamespace;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const roomId = url.pathname.split('/')[2]; // /room/:id

    // 获取或创建 DO
    const id = env.MY_DO.idFromName(`room:${roomId}`);
    const room = env.MY_DO.get(id);

    // 转发请求到 DO
    return room.fetch(request);
  },
};
// src/Room.ts
export class ChatRoom implements DurableObject {
  private state: DurableObjectState;
  private sessions: Map<WebSocket, string> = new Map(); // 连接的 WebSocket

  constructor(state: DurableObjectState) {
    this.state = state;
  }

  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    switch (url.pathname) {
      case '/connect': {
        // WebSocket 升级
        const [client, server] = Object.values(new WebSocketPair());
        this.handleSession(server);
        return new Response(null, { status: 101, webSocket: client });
      }

      case '/message': {
        // HTTP 发送消息(广播给所有连接的 WebSocket)
        const { message } = await request.json();
        this.broadcast(message);
        return Response.json({ sent: true });
      }

      case '/state': {
        // 读取持久化状态
        const stored = await this.state.storage.get('messages') || [];
        return Response.json({ messages: stored });
      }

      default:
        return new Response('Not Found', { status: 404 });
    }
  }

  private handleSession(ws: WebSocket) {
    this.state.acceptWebSocket(ws); // 接受 WebSocket
    this.sessions.set(ws, 'anonymous');

    // 发送历史消息
    this.state.storage.get('messages').then((history: any) => {
      if (history) ws.send(JSON.stringify({ type: 'history', data: history }));
    });
  }

  private async broadcast(message: string) {
    // 保存到持久化存储
    const history = (await this.state.storage.get('messages')) || [];
    history.push({ message, time: Date.now() });
    await this.state.storage.put('messages', history);

    // 广播给所有 WebSocket
    for (const [ws] of this.sessions) {
      ws.send(JSON.stringify({ type: 'message', data: message }));
    }
  }
}
# wrangler.toml
[[durable_objects.bindings]]
name = "MY_DO"
class_name = "ChatRoom"
script_name = "my-worker"

2.5 DO Alarms(定时任务)

export class Counter implements DurableObject {
  private state: DurableObjectState;

  constructor(state: DurableObjectState) {
    this.state = state;
  }

  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === '/schedule') {
      // 设置 30 秒后触发的 alarm
      await this.state.storage.setAlarm(Date.now() + 30000);
      return Response.json({ scheduled: true });
    }

    return Response.json({ count: await this.state.storage.get('count') || 0 });
  }

  async alarm() {
    // 定时执行的任务
    const count = (await this.state.storage.get('count') || 0) as number;
    await this.state.storage.put('count', count + 1);
    console.log(`Counter incremented to ${count + 1}`);
  }
}

三、KV vs Durable Objects:选择决策矩阵

维度KVDurable Objects
一致性最终一致强一致
读取延迟< 10ms20-100ms(取决于 DO 所在位置)
写入延迟< 1s< 100ms(同 Core DC)
并发安全❌ 可能冲突✅ 单线程保证
事务✅ 原子操作
WebSocket✅ 原生支持
存储大小25MB/值有限(内存+磁盘,建议 < 1GB)
计费读/写请求数请求 + 内存 × 时长
适用场景配置、缓存、元数据会话、计数器、协作、游戏、锁

决策树

需要强一致性或事务?
  ├── 是 → 需要 WebSocket/长连接?
  │       ├── 是 → Durable Objects ✅
  │       └── 否 → 读多写少且数据小?
  │               ├── 是 → Durable Objects ✅
  │               └── 否 → 考虑 D1 数据库
  └── 否 → 写后立刻从其他地点读取?
          ├── 是 → KV 不满足(可能有旧值)→ Durable Objects
          └── 否 → KV ✅(最终一致可接受)

四、生产实战场景

4.1 全局计数器(DO)

export class Counter implements DurableObject {
  constructor(private state: DurableObjectState) {}

  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    if (url.pathname === '/increment') {
      let count = (await this.state.storage.get('count')) || 0;
      count++;
      await this.state.storage.put('count', count);
      return Response.json({ count });
    }
    return Response.json({ count: await this.state.storage.get('count') || 0 });
  }
}

// 外部调用
const id = env.COUNTER.idFromName('global-counter');
const counter = env.COUNTER.get(id);
const res = await counter.fetch('https://fake-url/increment');

4.2 用户会话(KV)

// 登录时创建会话
await env.SESSIONS.put(
  `session:${sessionId}`,
  JSON.stringify({ userId, role, loginAt: Date.now() }),
  { expirationTtl: 86400 } // 24 小时过期
);

// 验证会话
const session = await env.SESSIONS.get(`session:${sessionId}`);
if (!session) return new Response('Unauthorized', { status: 401 });
const { userId, role } = JSON.parse(session);

4.3 分布式速率限制(DO)

export class RateLimiter implements DurableObject {
  constructor(private state: DurableObjectState) {}

  async fetch(request: Request): Promise<Response> {
    const ip = request.headers.get('CF-Connecting-IP');
    const key = `rate:${ip}`;
    const window = 60 * 1000; // 1 分钟窗口
    const limit = 100; // 100 请求/分钟

    const now = Date.now();
    const data = (await this.state.storage.get(key)) || { count: 0, resetAt: now + window };

    if (now > data.resetAt) {
      data.count = 0;
      data.resetAt = now + window;
    }

    data.count++;
    await this.state.storage.put(key, data);

    if (data.count > limit) {
      return new Response('Rate Limited', { status: 429 });
    }

    return Response.json({ remaining: limit - data.count });
  }
}

4.4 协作白板(DO + WebSocket)

export class Whiteboard implements DurableObject {
  private webSockets = new Set<WebSocket>();

  constructor(private state: DurableObjectState) {}

  async fetch(request: Request): Promise<Response> {
    const upgradeHeader = request.headers.get('Upgrade');
    if (upgradeHeader !== 'websocket') {
      return new Response('Expected websocket', { status: 400 });
    }

    const [client, server] = Object.values(new WebSocketPair());
    this.state.acceptWebSocket(server);
    this.webSockets.add(server);

    server.addEventListener('message', async (msg) => {
      const data = JSON.parse(msg.data as string);

      // 保存历史
      const history = (await this.state.storage.get('strokes')) || [];
      history.push(data);
      await this.state.storage.put('strokes', history);

      // 广播给其他客户端
      for (const ws of this.webSockets) {
        if (ws !== server && ws.readyState === WebSocket.READY_STATE_OPEN) {
          ws.send(JSON.stringify(data));
        }
      }
    });

    return new Response(null, { status: 101, webSocket: client });
  }
}

五、性能基准

5.1 KV 读取延迟

操作延迟(全球平均)
KV get(缓存命中)1-5ms
KV get(首次)5-15ms
KV put200-500ms
KV put + 传播1-60s(最终一致)

5.2 DO 操作延迟

操作延迟
DO fetch(已 warm)20-50ms
DO fetch(cold start)100-300ms
状态读写(storage)5-20ms
WebSocket 消息转发< 10ms

常见问题(FAQ)

KV 写入后多久能全球读取到新值?

通常 1-60 秒。不要依赖 KV 的即时一致性。如果需要强一致,用 Durable Objects。

Durable Objects 能存储多少数据?

建议每个 DO 存储 < 1GB。DO 的状态持久化到磁盘,但内存中保持活跃对象。过大的 DO 会影响恢复速度。

DO 的 WebSocket 连接数有限制吗?

每个 DO 建议维持 < 1000 个并发 WebSocket 连接。如果需要更多,考虑分片(用多个 DO)。

KV 和 DO 能一起用吗?

可以,而且是推荐模式:

  • KV 存储全局配置、A/B 标志、静态内容
  • DO 存储需要强一致的状态、会话、协作数据
  • 在 DO 中读取 KV 配置来驱动业务逻辑

DO 的计费贵吗?

维度费用
请求$0.12/百万次
内存(GB-小时)$12.50/GB-月
WebSocket 消息$0.25/百万条

一个小型聊天室 DO(128MB 内存,24h 运行)约 $0.40/天。大多数应用 DO 成本 <$10/月。

相关阅读

继续阅读

探索更多技术文章

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

全部文章 返回首页

「saas」更多文章