durable-objects

durable-objects

热门

创建和审查 Cloudflare Durable Objects。用于构建有状态协调(聊天室、多人游戏、预订系统)、实现 RPC 方法、SQLite 存储、告警、WebSocket,或审查 DO 代码的最佳实践。涵盖 Workers 集成、wrangler 配置和 Vitest 测试。倾向于从 Cloudflare 文档中检索而非依赖预训练知识。

2012Star
180Fork
更新于 2026/7/1
SKILL.md
只读
名称
durable-objects
描述

创建和审查 Cloudflare Durable Objects。用于构建有状态协调(聊天室、多人游戏、预订系统)、实现 RPC 方法、SQLite 存储、告警、WebSocket,或审查 DO 代码的最佳实践。涵盖 Workers 集成、wrangler 配置和 Vitest 测试。倾向于从 Cloudflare 文档中检索而非依赖预训练知识。

Durable Objects

在 Cloudflare 边缘构建有状态、协调的应用程序,使用 Durable Objects。

检索来源

你对 Durable Objects API 和配置的了解可能已过时。对于任何 Durable Objects 任务,优先检索而非预训练。

资源 URL
文档 https://developers.cloudflare.com/durable-objects/
API 参考 https://developers.cloudflare.com/durable-objects/api/
最佳实践 https://developers.cloudflare.com/durable-objects/best-practices/
示例 https://developers.cloudflare.com/durable-objects/examples/

实现功能时获取相关文档页面。

何时使用

  • 创建新的 Durable Object 类以实现有状态协调
  • 实现 RPC 方法、告警或 WebSocket 处理程序
  • 审查现有 DO 代码的最佳实践
  • 配置 wrangler.jsonc/toml 的 DO 绑定和迁移
  • 使用 @cloudflare/vitest-pool-workers 编写测试
  • 设计分片策略和父子关系

参考文档

  • ./references/rules.md - 核心规则、存储、并发、RPC、告警
  • ./references/testing.md - Vitest 设置、单元/集成测试、告警测试
  • ./references/workers.md - Workers 处理程序、类型、wrangler 配置、可观测性

搜索:blockConcurrencyWhile, idFromName, getByName, setAlarm, sql.exec

核心原则

使用 Durable Objects 的场景

需求 示例
协调 聊天室、多人游戏、协作文档
强一致性 库存、预订系统、回合制游戏
按实体存储 多租户 SaaS、按用户数据
持久连接 WebSocket、实时通知
按实体调度工作 订阅续费、游戏超时

不要用于

  • 无状态请求处理(使用普通 Workers)
  • 需要最大全局分布的场景
  • 高扇出独立请求

快速参考

Wrangler 配置

// wrangler.jsonc
{
  "durable_objects": {
    "bindings": [{ "name": "MY_DO", "class_name": "MyDurableObject" }]
  },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }]
}

基本 Durable Object 模式

import { DurableObject } from "cloudflare:workers";

export interface Env {
  MY_DO: DurableObjectNamespace<MyDurableObject>;
}

export class MyDurableObject extends DurableObject<Env> {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    ctx.blockConcurrencyWhile(async () => {
      this.ctx.storage.sql.exec(`
        CREATE TABLE IF NOT EXISTS items (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          data TEXT NOT NULL
        )
      `);
    });
  }

  async addItem(data: string): Promise<number> {
    const result = this.ctx.storage.sql.exec<{ id: number }>(
      "INSERT INTO items (data) VALUES (?) RETURNING id",
      data
    );
    return result.one().id;
  }
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const stub = env.MY_DO.getByName("my-instance");
    const id = await stub.addItem("hello");
    return Response.json({ id });
  },
};

关键规则

  1. 围绕协调原子建模 - 每个聊天室/游戏/用户一个 DO,而不是一个全局 DO
  2. 使用 getByName() 进行确定性路由 - 相同输入 = 相同 DO 实例
  3. 使用 SQLite 存储 - 在迁移中配置 new_sqlite_classes
  4. 在构造函数中初始化 - 仅用于模式设置的 blockConcurrencyWhile()
  5. 使用 RPC 方法 - 而不是 fetch() 处理程序(兼容日期 >= 2024-04-03)
  6. 先持久化,后缓存 - 在更新内存状态之前始终写入存储
  7. 每个 DO 一个告警 - setAlarm() 替换任何现有告警

反模式(切勿使用)

  • 单个全局 DO 处理所有请求(瓶颈)
  • 在每个请求上使用 blockConcurrencyWhile()(降低吞吐量)
  • 仅在内存中存储关键状态(驱逐/崩溃时丢失)
  • 在相关存储写入之间使用 await(破坏原子性)
  • fetch() 或外部 I/O 期间持有 blockConcurrencyWhile()

Stub 创建

// 确定性 - 大多数情况下首选
const stub = env.MY_DO.getByName("room-123");

// 从现有 ID 字符串
const id = env.MY_DO.idFromString(storedIdString);
const stub = env.MY_DO.get(id);

// 新的唯一 ID - 在外部存储映射
const id = env.MY_DO.newUniqueId();
const stub = env.MY_DO.get(id);

存储操作

// SQL(同步,推荐)
this.ctx.storage.sql.exec("INSERT INTO t (c) VALUES (?)", value);
const rows = this.ctx.storage.sql.exec<Row>("SELECT * FROM t").toArray();

// KV(异步)
await this.ctx.storage.put("key", value);
const val = await this.ctx.storage.get<Type>("key");

告警

// 调度(替换现有)
await this.ctx.storage.setAlarm(Date.now() + 60_000);

// 处理程序
async alarm(): Promise<void> {
  // 处理调度的工作
  // 可选重新调度:await this.ctx.storage.setAlarm(...)
}

// 取消
await this.ctx.storage.deleteAlarm();

测试快速入门

import { env } from "cloudflare:test";
import { describe, it, expect } from "vitest";

describe("MyDO", () => {
  it("应该工作", async () => {
    const stub = env.MY_DO.getByName("test");
    const result = await stub.addItem("test");
    expect(result).toBe(1);
  });
});