六维教程

Cloudflare Workers Durable Objects

普通 Worker 是无状态的,每个请求都可能落在不同的节点,代码里的变量在请求之间无法共享。需要跨请求协调状态的场景,比如计数器、聊天室、协同编辑,就要用到 Durable Objects。这是 Workers 体系里最难理解的一块,值得单独一篇讲透。

无状态的问题

先看一个典型问题。想用普通 Worker 实现一个访问计数器,最直觉的写法是

let count = 0; // 存在哪?每个节点一份,互不相同

export default {
  async fetch(request, env, ctx) {
    count++;
    return Response.json({ count });
  },
};

这个写法有两个致命问题。第一,代码可能被复制到多个节点运行,每个节点都有自己的 count,不同用户访问看到的数字不一样。第二,节点空闲后资源被回收,变量清零,计数全部丢失。

用 KV 可以解决持久化,但 KV 的最终一致性又带来新问题。两个请求同时读旧值再加 1 写回,就丢失了一次计数。

Durable Objects 就是为这类”需要单点协调的状态”而生的。

DO 的核心概念

Durable Objects(简称 DO)可以理解为”有状态且带持久化存储的 Worker 实例”,三个核心特性

特性 说明
唯一 ID 每个 DO 实例有一个唯一 ID,同一 ID 的请求永远路由到同一实例
单线程执行 一个实例同一时间只处理一个请求,天然避免并发竞争
内置持久存储 实例自带 SQLite 存储,数据不随资源回收丢失

用聊天室打比方。一个房间就是一个 DO 实例,房间号就是 ID,所有人都进同一个房间,房间里的数据(消息、成员)由房间自己管理,不会散落在不同节点。

配置绑定

DO 类和普通 Worker 一样写在项目里,需要两步配置

// src/index.js
export class Counter {
  constructor(state, env) {
    this.state = state;
  }
  // 这个实例收到请求时调用
  async fetch(request) {
    const key = "count";
    let value = (await this.state.storage.get(key)) || 0;
    value++;
    await this.state.storage.put(key, value);
    return Response.json({ count: value });
  }
}

wrangler.toml 中注册这个类

name = "my-worker"
main = "src/index.js"
compatibility_date = "2026-01-01"

[durable_objects]
bindings = [{ name = "COUNTER", class_name = "Counter" }]

[[migrations]]
tag = "v1"
new_sqlite_classes = ["Counter"]

durable_objects 里的 bindings 把代码中的类暴露给 Worker,migrations 通知平台创建这个类的实例存储。以后新增 DO 类时,要追加一条 migrations 记录。

获取 DO 实例

Worker 里通过绑定拿到 DO 实例的引用,转发请求过去

export default {
  async fetch(request, env, ctx) {
    // 根据名称获取稳定 ID
    const id = env.COUNTER.idFromName("my-counter");

    // 根据 ID 获取实例引用(stub)
    const stub = env.COUNTER.get(id);

    // 转发请求给 DO 实例执行
    return stub.fetch(request);
  },
};

三种获取 ID 的方式

方法 用途
idFromName(name) 同一名称始终得到同一 ID,适合”每个房间一个”
newUniqueId() 每次生成全新 ID,适合”每次会话一个”
idFromString(id) 从已有 ID 字符串解析,配合持久化使用

stub.fetch() 的用法和普通 fetch 完全一致,DO 实例的 fetch 方法会收到这个请求并处理。

存储 API

DO 实例用 this.state.storage 读写持久化数据,接口风格和 KV 类似

// 读取
const value = await this.state.storage.get("key");

// 写入
await this.state.storage.put("key", value);

// 删除
await this.state.storage.delete("key");

// 列出
const items = await this.state.storage.list();

和 KV 最大的区别是,DO 的存储是强一致的,写入后立刻能读到新值,因为它只有一份,就在这个实例自己身上。

单线程的威力

一个 DO 实例同一时刻只处理一个请求,这是理解 DO 的关键。再回到计数器场景

请求 A 进入实例,读取 count=1,加 1 写入 count=2,响应完成
请求 B 排队等待,A 完成后进入,读取 count=2,加 1 写入 count=3

两个请求不可能同时修改数据,竞争问题从机制上被消除了。代价是同一实例的请求只能串行处理,单实例吞吐有上限,高并发场景需要把用户分散到多个实例(比如按用户 ID 分房间)。

适用场景

场景 用法
计数器 每类数据一个实例,见上文示例
聊天室 每个房间一个实例,见下一篇 WebSocket 实战
会话管理 每个用户一个实例,存储登录态
分布式锁 用单线程特性实现互斥
任务协调 多个 Worker 协作时的状态协调

不适用的是简单读写类数据,KV 和 D1 更便宜也更简单,只有”需要并发协调”时才上 DO。

限制与计费

项目 说明
免费计划 每天 10 万次请求,使用 SQLite 存储后端
单实例内存 128MB
请求时长 连接保持期间无硬上限
密钥管理 和普通 Worker 相同,走 secrets

DO 是 Workers 从”无状态计算”走向”有状态应用”的桥梁。下一篇用 DO 实现 WebSocket 实时通信,是 DO 最典型的应用,也是 Workers 的进阶难点。

上一篇 Cloudflare Workers 调试与线上部署
下一篇 Cloudflare Workers WebSocket

上一篇
Cloudflare Workers 调试与线上部署
下一篇
Cloudflare Workers WebSocket