关于 cloudflare-durable-objects
cloudflare-durable-objects 是面向 Cloudflare Workers 开发者的 Durable Objects 开发指南,涵盖有状态对象的存储、通信、定时任务和调用方式。内容包含项目搭建、代码模式及 SQLite、WebSocket 休眠、Alarms API 等实践说明。
主要功能
- 01搭建 Durable Objects 项目
- 02使用 SQLite 与键值存储
- 03通过 WebSocket 休眠处理连接
- 04使用 Alarms API 安排任务
- 05通过 RPC 或 HTTP 调用对象
适用场景
- 01构建实时协作或聊天服务
- 02实现会话管理与协调逻辑
- 03安排清理、提醒等延迟任务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill cloudflare-durable-objects安装后在 Claude Code 中输入/cloudflare-durable-objects确认已加载。
附带文件README.mdSKILL.json
详细介绍
Cloudflare Durable Objects
状态:已可用于生产环境 ✅ 最后更新:2025-10-22 依赖项:cloudflare-worker-base(推荐) 最新版本:[email protected]+、@cloudflare/[email protected]+ 官方文档:https://developers.cloudflare.com/durable-objects/
什么是 Durable Objects?
Cloudflare Durable Objects 是全局唯一的有状态对象,提供:
- 单点协调 - 每个 Durable Object 实例在 Cloudflare 的网络中全局唯一
- 强一致性 - 支持事务且可串行化的存储(提供 ACID 保证)
- 实时通信 - WebSocket 休眠 API 支持每个实例处理数千个连接
- 持久化状态 - 内置 SQLite 数据库(最高 1GB)或键值存储
- 定时任务 - Alarms API 可用于在未来执行任务
- 全球分发 - 自动路由到最佳位置
- 自动扩缩 - 可创建数百万个独立实例
适用场景:
- 聊天室和实时协作
- 多人游戏服务
- 速率限制和会话管理
- 领导者选举和协调
- 支持休眠的 WebSocket 服务
- 有状态工作流和队列
- 面向每位用户或每个房间的逻辑
快速开始(10 分钟)
选项 1:搭建新的 DO 项目
npm create cloudflare@latest my-durable-app -- \
--template=cloudflare/durable-objects-template \
--ts \
--git \
--deploy false
cd my-durable-app
npm install
npm run dev
此操作会创建:
- 完整的 Durable Objects 项目结构
- TypeScript 配置
- 包含绑定和迁移的 wrangler.jsonc
- Durable Object 类实现示例
- 用于调用 Durable Object 的 Worker
选项 2:添加到现有 Worker
cd my-existing-worker
npm install -D @cloudflare/workers-types
创建 Durable Object 类(src/counter.ts):
import { DurableObject } from 'cloudflare:workers';
export class Counter extends DurableObject {
async increment(): Promise<number> {
// Get current value from storage (default to 0)
let value: number = (await this.ctx.storage.get('value')) || 0;
// Increment
value += 1;
// Save back to storage
await this.ctx.storage.put('value', value);
return value;
}
async get(): Promise<number> {
return (await this.ctx.storage.get('value')) || 0;
}
}
// CRITICAL: Export the class
export default Counter;
配置 wrangler.jsonc:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-10-22",
// Durable Objects binding
"durable_objects": {
"bindings": [
{
"name": "COUNTER", // How you access it: env.COUNTER
"class_name": "Counter" // MUST match exported class name
}
]
},
// REQUIRED: Migration for new DO class
"migrations": [
{
"tag": "v1", // Unique migration identifier
"new_sqlite_classes": [ // Use SQLite backend (recommended)
"Counter"
]
}
]
}
从 Worker 调用(src/index.ts):
import { Counter } from './counter';
interface Env {
COUNTER: DurableObjectNamespace<Counter>;
}
export { Counter };
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Get Durable Object stub by name
const id = env.COUNTER.idFromName('global-counter');
const stub = env.COUNTER.get(id);
// Call RPC method on the DO
const count = await stub.increment();
return new Response(`Count: ${count}`);
},
};
部署:
npx wrangler deploy
Durable Object 类结构
基类模式
所有 Durable Object 都必须扩展 cloudflare:workers 中的 DurableObject:
import { DurableObject } from 'cloudflare:workers';
export class MyDurableObject extends DurableObject {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
// Optional: Initialize from storage
ctx.blockConcurrencyWhile(async () => {
// Load state before handling requests
this.someValue = await ctx.storage.get('someKey') || defaultValue;
});
}
// RPC methods (recommended)
async myMethod(): Promise<string> {
return 'Hello from DO!';
}
// Optional: HTTP fetch handler
async fetch(request: Request): Promise<Response> {
return new Response('Hello from DO fetch!');
}
}
// CRITICAL: Export the class
export default MyDurableObject;
构造函数模式
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env); // REQUIRED
// Access to environment bindings
this.env = env;
// this.ctx provides:
// - this.ctx.storage (storage API)
// - this.ctx.id (unique ID)
// - this.ctx.waitUntil() (background tasks)
// - this.ctx.acceptWebSocket() (WebSocket hibernation)
}
重要规则:
- ✅ 始终首先调用
super(ctx, env) - ✅ 保持构造函数精简 - 繁重操作会阻塞休眠唤醒
- ✅ 使用
ctx.blockConcurrencyWhile()在处理请求前从存储中初始化 - ❌ 切勿使用
setTimeout或setInterval- 这会破坏休眠机制(请改用 alarms) - ❌ 使用 WebSocket 时,不要只依赖内存状态 - 请将状态持久化到存储中
导出类
// Export as default (required for Worker to use it)
export default MyDurableObject;
// Also export as named export (for type inference in Worker)
export { MyDurableObject };
在 Worker 中:
// Import the class for types
import { MyDurableObject } from './my-durable-object';
// Export it so Worker can instantiate it
export { MyDurableObject };
interface Env {
MY_DO: DurableObjectNamespace<MyDurableObject>;
}
状态 API - 持久化存储
Durable Objects 根据后端提供两种存储 API:
- SQL API(SQLite 后端)- 推荐
- 键值 API(KV 或 SQLite 后端)
启用 SQLite 后端(推荐)
在 wrangler.jsonc 的 migrations 中:
{
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyDurableObject"] // ← Use this for SQLite
}
]
}
为什么选择 SQLite?
- ✅ 存储空间最高可达 1GB(KV 后端为 128MB)
- ✅ 原子操作(deleteAll 要么全部执行,要么完全不执行)
- ✅ 支持事务的 SQL 查询
- ✅ 时间点恢复(PITR)
- ✅ 也提供同步 KV API
SQL API
通过 ctx.storage.sql 访问:
import { DurableObject } from 'cloudflare:workers';
export class MyDurableObject extends DurableObject {
sql: SqlStorage;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
// Create table on first run
this.sql.exec(`
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
user TEXT NOT NULL,
created_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_created_at ON messages(created_at);
`);
}
async addMessage(text: string, user: string): Promise<number> {
// Insert with exec (returns cursor)
const cursor = this.sql.exec(
'INSERT INTO messages (text, user, created_at) VALUES (?, ?, ?) RETURNING id',
text,
user,
Date.now()
);
const row = cursor.one<{ id: number }>();
return row.id;
}
async getMessages(limit: number = 50): Promise<any[]> {
const cursor = this.sql.exec(
'SELECT * FROM messages ORDER BY created_at DESC LIMIT ?',
limit
);
// Convert cursor to array
return cursor.toArray();
}
async deleteOldMessages(beforeTimestamp: number): Promise<void> {
this.sql.exec(
'DELETE FROM messages WHERE created_at < ?',
beforeTimestamp
);
}
}
SQL API 方法:
// Execute query (returns cursor)
const cursor = this.sql.exec('SELECT * FROM table WHERE id = ?', id);
// Get single row
const row = cursor.one<RowType>();
// Get first row or null
const row = cursor.one<RowType>({ allowNone: true });
// Get all rows as array
const rows = cursor.toArray<RowType>();
// Iterate cursor
for (const row of cursor) {
// Process row
}
// Transactions (synchronous)
this.ctx.storage.transactionSync(() => {
this.sql.exec('INSERT INTO table1 ...');
this.sql.exec('UPDATE table2 ...');
// All or nothing
});
重要 SQL 规则:
- ✅ 始终使用带
?占位符的参数化查询 - ✅ 为频繁查询的列创建索引
- ✅ 多语句操作应使用事务
- ❌ 不要访问隐藏的
__cf_kv表(KV API 在内部使用) - ❌ 不要在已部署的、由 KV 后端支持的 Durable Objects 上启用 SQLite(不支持)
键值 API
SQLite 和 KV 后端都可通过 ctx.storage 使用:
import { DurableObject } from 'cloudflare:workers';
export class MyDurableObject extends DurableObject {
async increment(): Promise<number> {
// Get value
let count = await this.ctx.storage.get<number>('count') || 0;
// Increment
count += 1;
// Put value back
await this.ctx.storage.put('count', count);
return count;
}
async batchOperations(): Promise<void> {
// Get multiple keys
const map = await this.ctx.storage.get<number>(['key1', 'key2', 'key3']);
// Put multiple keys
await this.ctx.storage.put({
key1: 'value1',
key2: 'value2',
key3: 'value3',
});
// Delete key
await this.ctx.storage.delete('key1');
// Delete multiple keys
await this.ctx.storage.delete(['key2', 'key3']);
}
async listKeys(): Promise<string[]> {
// List all keys
const map = await this.ctx.storage.list();
return Array.from(map.keys());
// List with prefix
const mapWithPrefix = await this.ctx.storage.list({
prefix: 'user:',
limit: 100,
});
}
async deleteAllStorage(): Promise<void> {
// Delete alarm first (if set)
await this.ctx.storage.deleteAlarm();
// Delete all storage (DO will cease to exist after shutdown)
await this.ctx.storage.deleteAll();
}
}
KV API 方法:
// Get single value
const value = await this.ctx.storage.get<T>('key');
// Get multiple values (returns Map)
const map = await this.ctx.storage.get<T>(['key1', 'key2']);
// Put single value
await this.ctx.storage.put('key', value);
// Put multiple values
await this.ctx.storage.put({ key1: value1, key2: value2 });
// Delete single key
await this.ctx.storage.delete('key');
// Delete multiple keys
await this.ctx.storage.delete(['key1', 'key2']);
// List keys
const map = await this.ctx.storage.list<T>({
prefix: 'user:',
limit: 100,
reverse: false
});
// Delete all (atomic on SQLite, may be partial on KV backend)
await this.ctx.storage.deleteAll();
// Transactions (async)
await this.ctx.storage.transaction(async (txn) => {
await txn.put('key1', value1);
await txn.put('key2', value2);
// All or nothing
});
存储限制:
- SQLite 后端:每个 Durable Object 实例最多 1GB 存储空间
- KV 后端:每个 Durable Object 实例最多 128MB 存储空间
WebSocket 休眠 API
WebSocket 休眠 API 允许 Durable Objects:
- 在每个实例中处理 数千个 WebSocket 连接
- 空闲时(没有消息、没有事件)进入休眠,以节省成本
- 在收到消息时自动唤醒
- 在空闲期间维持连接,且不会产生持续时间费用
适用场景: 聊天室、实时协作、多人游戏、实时更新
休眠如何运作
- 活动状态 - DO 位于内存中,正在处理消息
- 空闲状态 - 约 10 秒内没有消息,DO 可以进入休眠
- 休眠 - 内存中的状态被清除,WebSocket 仍保持与 Cloudflare 边缘节点的连接
- 唤醒 - 收到新消息 → 运行构造函数 → 调用处理程序方法
重要: 休眠时,内存中的状态会丢失。使用 serializeAttachment() 持久化每个 WebSocket 的元数据。
WebSocket 服务端模式
import { DurableObject } from 'cloudflare:workers';
export class ChatRoom extends DurableObject {
sessions: Map<WebSocket, { userId: string; username: string }>;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
// Restore WebSocket connections after hibernation
this.sessions = new Map();
ctx.getWebSockets().forEach((ws) => {
// Deserialize attachment (persisted metadata)
const attachment = ws.deserializeAttachment();
this.sessions.set(ws, attachment);
});
}
async fetch(request: Request): Promise<Response> {
// Expect WebSocket upgrade request
const upgradeHeader = request.headers.get('Upgrade');
if (upgradeHeader !== 'websocket') {
return new Response('Expected websocket', { status: 426 });
}
// Create WebSocket pair
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
// Get user info from URL or headers
const url = new URL(request.url);
const userId = url.searchParams.get('userId') || 'anonymous';
const username = url.searchParams.get('username') || 'Anonymous';
// Accept WebSocket with hibernation
// CRITICAL: Use ctx.acceptWebSocket(), NOT ws.accept()
this.ctx.acceptWebSocket(server);
// Serialize metadata to persist across hibernation
const metadata = { userId, username };
server.serializeAttachment(metadata);
// Track in-memory (will be restored after hibernation)
this.sessions.set(server, metadata);
// Notify others
this.broadcast(`${username} joined`, server);
// Return client WebSocket to browser
return new Response(null, {
status: 101,
webSocket: client,
});
}
// Called when WebSocket receives a message
async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
const session = this.sessions.get(ws);
if (typeof message === 'string') {
const data = JSON.parse(message);
if (data.type === 'chat') {
// Broadcast to all connections
this.broadcast(`${session?.username}: ${data.text}`, ws);
}
}
}
// Called when WebSocket closes
async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean): Promise<void> {
const session = this.sessions.get(ws);
this.sessions.delete(ws);
// Close the WebSocket
ws.close(code, 'Durable Object closing WebSocket');
// Notify others
if (session) {
this.broadcast(`${session.username} left`);
}
}
// Called on WebSocket errors
async webSocketError(ws: WebSocket, error: any): Promise<void> {
console.error('WebSocket error:', error);
const session = this.sessions.get(ws);
this.sessions.delete(ws);
}
// Helper to broadcast to all connections
broadcast(message: string, except?: WebSocket): void {
this.sessions.forEach((session, ws) => {
if (ws !== except && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'message', text: message }));
}
});
}
}
WebSocket 处理程序方法:
// Receive message from client
async webSocketMessage(
ws: WebSocket,
message: string | ArrayBuffer
): Promise<void> {
// Handle message
}
// WebSocket closed by client
async webSocketClose(
ws: WebSocket,
code: number,
reason: string,
wasClean: boolean
): Promise<void> {
// Cleanup
}
// WebSocket error occurred
async webSocketError(
ws: WebSocket,
error: any
): Promise<void> {
// Handle error
}
休眠安全模式:
// ✅ CORRECT: Use ctx.acceptWebSocket (enables hibernation)
this.ctx.acceptWebSocket(server);
// ❌ WRONG: Don't use ws.accept() (standard API, no hibernation)
server.accept();
// ✅ CORRECT: Persist metadata across hibernation
server.serializeAttachment({ userId: '123', username: 'Alice' });
// ✅ CORRECT: Restore metadata in constructor
constructor(ctx, env) {
super(ctx, env);
ctx.getWebSockets().forEach((ws) => {
const metadata = ws.deserializeAttachment();
this.sessions.set(ws, metadata);
});
}
// ❌ WRONG: Don't use setTimeout/setInterval (prevents hibernation)
setTimeout(() => { /* ... */ }, 1000); // ❌ NEVER DO THIS
// ✅ CORRECT: Use alarms for scheduled tasks
await this.ctx.storage.setAlarm(Date.now() + 60000);
不会进入休眠的情况:
- 有待执行的
setTimeout或setInterval回调 - 正在进行的
fetch()请求(等待中的 I/O) - 使用了标准 WebSocket API(而非休眠 API)
- 请求或事件仍在处理中
Alarms API - 计划任务
Alarms API 允许 Durable Objects 安排自己在未来的指定时间唤醒。
适用场景: 批处理、清理任务、提醒、定期任务、延迟操作
基本 Alarm 模式
import { DurableObject } from 'cloudflare:workers';
export class Batcher extends DurableObject {
buffer: string[];
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
ctx.blockConcurrencyWhile(async () => {
// Restore buffer from storage
this.buffer = await ctx.storage.get('buffer') || [];
});
}
async addItem(item: string): Promise<void> {
this.buffer.push(item);
await this.ctx.storage.put('buffer', this.buffer);
// Schedule alarm for 10 seconds from now (if not already set)
const currentAlarm = await this.ctx.storage.getAlarm();
if (currentAlarm === null) {
await this.ctx.storage.setAlarm(Date.now() + 10000);
}
}
// Called when alarm fires
async alarm(alarmInfo: { retryCount: number; isRetry: boolean }): Promise<void> {
console.log(`Alarm fired (retry count: ${alarmInfo.retryCount})`);
// Process batch
if (this.buffer.length > 0) {
await this.processBatch(this.buffer);
// Clear buffer
this.buffer = [];
await this.ctx.storage.put('buffer', []);
}
// Alarm is automatically deleted after successful execution
}
async processBatch(items: string[]): Promise<void> {
// Send to external API, write to database, etc.
console.log(`Processing ${items.length} items:`, items);
}
}
Alarm API 方法:
// Set alarm to fire at specific timestamp
await this.ctx.storage.setAlarm(Date.now() + 60000); // 60 seconds from now
// Set alarm to fire at specific date
await this.ctx.storage.setAlarm(new Date('2025-12-31T23:59:59Z'));
// Get current alarm (null if not set)
const alarmTime = await this.ctx.storage.getAlarm();
// Delete alarm
await this.ctx.storage.deleteAlarm();
// Alarm handler (called when alarm fires)
async alarm(alarmInfo: { retryCount: number; isRetry: boolean }): Promise<void> {
// Do work
}
Alarm 行为:
- ✅ 保证至少执行一次 - 失败时会重试
- ✅ 自动重试 - 最多重试 6 次,采用指数退避(从 2 秒开始)
- ✅ 持久化 - 经受 DO 休眠和逐出后仍然保留
- ✅ 成功执行后自动删除
- ⚠️ 每个 DO 只能设置一个 Alarm - 设置新 Alarm 会覆盖上一个
重试模式(幂等操作):
async alarm(alarmInfo: { retryCount: number; isRetry: boolean }): Promise<void> {
if (alarmInfo.retryCount > 3) {
console.error('Alarm failed after 3 retries, giving up');
return;
}
try {
// Idempotent operation (safe to retry)
await this.sendNotification();
} catch (error) {
console.error('Alarm failed:', error);
throw error; // Will trigger retry
}
}
RPC 与 HTTP Fetch
Durable Objects 支持两种调用模式:
- RPC(远程过程调用) - 新项目推荐使用
- HTTP Fetch - 用于 HTTP 请求/响应流程或兼容旧版
RPC 模式(推荐)
使用兼容日期 >= 2024-04-03 启用 RPC:
{
"compatibility_date": "2025-10-22"
}
在 DO 类上定义 RPC 方法:
export class Counter extends DurableObject {
// Public RPC methods (automatically exposed)
async increment(): Promise<number> {
let value = await this.ctx.storage.get<number>('count') || 0;
value += 1;
await this.ctx.storage.put('count', value);
return value;
}
async decrement(): Promise<number> {
let value = await this.ctx.storage.get<number>('count') || 0;
value -= 1;
await this.ctx.storage.put('count', value);
return value;
}
async get(): Promise<number> {
return await this.ctx.storage.get<number>('count') || 0;
}
}
从 Worker 中调用:
// Get stub
const id = env.COUNTER.idFromName('my-counter');
const stub = env.COUNTER.get(id);
// Call RPC methods directly
const count = await stub.increment();
const current = await stub.get();
RPC 优势:
- ✅ 类型安全 - TypeScript 知道方法签名
- ✅ 简单 - 直接调用方法,无需 HTTP 流程
- ✅ 自动序列化 - 处理结构化数据
- ✅ 异常传播 - DO 中抛出的错误会传递到 Worker
HTTP Fetch 模式
在 DO 类上定义 fetch() 处理程序:
export class Counter extends DurableObject {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/increment' && request.method === 'POST') {
let value = await this.ctx.storage.get<number>('count') || 0;
value += 1;
await this.ctx.storage.put('count', value);
return new Response(JSON.stringify({ count: value }));
}
if (url.pathname === '/get' && request.method === 'GET') {
let value = await this.ctx.storage.get<number>('count') || 0;
return new Response(JSON.stringify({ count: value }));
}
return new Response('Not found', { status: 404 });
}
}
从 Worker 中调用:
// Get stub
const id = env.COUNTER.idFromName('my-counter');
const stub = env.COUNTER.get(id);
// Call fetch
const response = await stub.fetch('https://fake-host/increment', {
method: 'POST',
});
const data = await response.json();
如何选择
| 用例 | 建议 |
|---|---|
| 新项目 | ✅ RPC(更简单、类型安全) |
| HTTP 请求/响应流程 | HTTP Fetch |
| 复杂路由逻辑 | HTTP Fetch |
| 类型安全很重要 | ✅ RPC |
| 兼容旧版 | HTTP Fetch |
| WebSocket 升级 | HTTP Fetch(必需) |
创建 Durable Object 存根并进行路由
要从 Worker 与 Durable Object 交互,你需要:
- 获取一个 Durable Object ID
- 根据该 ID 创建一个存根
- 调用存根上的方法
获取 Durable Object ID
创建 ID 的三种方法:
1. idFromName(name) - 命名 DO(最常见)
当你希望根据名称将请求一致地路由到同一个 DO 实例时使用:
// Same name always routes to same DO instance globally
const roomId = env.CHAT_ROOM.idFromName('room-123');
const userId = env.USER_SESSION.idFromName('user-alice');
const globalCounter = env.COUNTER.idFromName('global');
适用于:
- 聊天室(名称 = 房间 ID)
- 用户会话(名称 = 用户 ID)
- 按租户划分的逻辑(名称 = 租户 ID)
- 全局单例(名称 = 'global')
特性:
- ✅ 确定性 - 相同名称 = 相同 DO 实例
- ✅ 易于引用 - 只需要名称字符串
- ⚠️ 首次访问延迟 - 检查全局唯一性约需 100-300 毫秒
- ⚠️ 首次使用后会缓存 - 后续访问速度很快
2. newUniqueId() - 随机 ID
需要新的唯一 DO 实例时使用:
// Creates a random, globally unique ID
const id = env.MY_DO.newUniqueId();
// With jurisdiction restriction (EU data residency)
const euId = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });
// Store the ID for future use
const idString = id.toString();
await env.KV.put('session:123', idString);
适用场景:
- 创建尚不存在的新会话/房间
- 一次性使用的 DO
- 没有自然名称时
特点:
- ✅ 首次使用时延迟较低(无需检查全局唯一性)
- ⚠️ 必须保存 ID,才能在之后访问同一个 DO
- ⚠️ ID 格式不透明,无法从中推断含义
3. idFromString(idString) - 根据已保存的 ID 重新创建
如果你之前保存过 ID,需要重新创建时使用:
// Get stored ID string (from KV, D1, cookie, etc.)
const idString = await env.KV.get('session:123');
// Recreate ID
const id = env.MY_DO.idFromString(idString);
// Get stub
const stub = env.MY_DO.get(id);
以下情况会抛出异常:
- ID 字符串无效
- ID 并非由同一个
DurableObjectNamespace创建
获取存根
方法 1:get(id) - 根据 ID 获取
const id = env.MY_DO.idFromName('my-instance');
const stub = env.MY_DO.get(id);
// Call methods
await stub.myMethod();
方法 2:getByName(name) - 命名 DO 的快捷方式
// Shortcut that combines idFromName + get
const stub = env.MY_DO.getByName('my-instance');
// Equivalent to:
// const id = env.MY_DO.idFromName('my-instance');
// const stub = env.MY_DO.get(id);
await stub.myMethod();
命名 DO 推荐使用此方法(代码更简洁)。
位置提示(地理路由)
使用位置提示控制 Durable Object 的创建位置:
// Create DO near specific location
const id = env.MY_DO.idFromName('user-alice');
const stub = env.MY_DO.get(id, { locationHint: 'enam' }); // Eastern North America
// Available location hints:
// - 'wnam' - Western North America
// - 'enam' - Eastern North America
// - 'sam' - South America
// - 'weur' - Western Europe
// - 'eeur' - Eastern Europe
// - 'apac' - Asia-Pacific
// - 'oc' - Oceania
// - 'afr' - Africa
// - 'me' - Middle East
适用场景:
- ✅ 在用户附近创建 DO(降低延迟)
- ✅ 满足数据驻留要求(例如,欧盟用户 → weur/eeur)
限制:
- ⚠️ 提示仅尽力而为,无法保证
- ⚠️ 仅影响首次创建,之后的访问会使用现有位置
- ⚠️ 无法移动现有 DO,创建后位置即固定
管辖区域限制(数据驻留)
严格执行数据位置要求:
// Create DO that MUST stay in EU
const euId = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });
// Available jurisdictions:
// - 'eu' - European Union
// - 'fedramp' - FedRAMP (US government)
适用场景:
- 法规合规要求(GDPR、FedRAMP)
- 数据主权要求
重要:
- ✅ 严格执行,DO 永远不会离开管辖区域
- ⚠️ 管辖区域限制不能与位置提示组合使用
- ⚠️ 管辖区域外的用户会面临更高延迟
迁移 - 管理 DO 类
在以下情况下,必须进行迁移:
- 创建新的 DO 类
- 重命名 DO 类
- 删除 DO 类
- 将 DO 类转移到其他 Worker
迁移类型:
1. 创建新的 DO 类
{
"durable_objects": {
"bindings": [
{
"name": "COUNTER",
"class_name": "Counter"
}
]
},
"migrations": [
{
"tag": "v1", // Unique identifier for this migration
"new_sqlite_classes": [ // SQLite backend (recommended)
"Counter"
]
}
]
}
对于 KV 后端(旧版):
{
"migrations": [
{
"tag": "v1",
"new_classes": ["Counter"] // KV backend (128MB limit)
}
]
}
重要:
- ✅ 新建 DO 时使用
new_sqlite_classes(最多支持 1GB 存储) - ❌ 无法为已部署且由 KV 提供后端的现有 DO 启用 SQLite
2. 重命名 DO 类
{
"durable_objects": {
"bindings": [
{
"name": "MY_DO",
"class_name": "NewClassName" // New class name
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["OldClassName"]
},
{
"tag": "v2", // New migration tag
"renamed_classes": [
{
"from": "OldClassName",
"to": "NewClassName"
}
]
}
]
}
发生的情况:
- ✅ 现有 DO 实例会保留其数据
- ✅ 旧绑定会自动转发到新类
- ⚠️ 必须在 Worker 代码中导出新类
3. 删除 DO 类
{
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["Counter"]
},
{
"tag": "v2",
"deleted_classes": ["Counter"] // Mark as deleted
}
]
}
发生的情况:
- ✅ 现有 DO 实例会立即删除
- ✅ 所有存储内容都会删除
- ⚠️ 无法撤销,数据会永久丢失
删除前:
- 如有需要,导出数据
- 更新引用此 DO 的 Workers
4. 将 DO 类转移到其他 Worker
// In destination Worker:
{
"durable_objects": {
"bindings": [
{
"name": "TRANSFERRED_DO",
"class_name": "TransferredClass"
}
]
},
"migrations": [
{
"tag": "v1",
"transferred_classes": [
{
"from": "OriginalClass",
"from_script": "original-worker", // Source Worker name
"to": "TransferredClass"
}
]
}
]
}
发生的情况:
- ✅ DO 实例会移至新的 Worker
- ✅ 所有存储内容都会转移
- ✅ 旧绑定会自动转发
- ⚠️ 必须导出目标类
迁移规则
迁移中的重要注意事项:
❌ 迁移是原子操作,无法渐进式部署
- 部署时,所有实例会同时迁移
- 不支持部分发布
❌ 迁移标签必须唯一
- 不能重复使用标签
- 标签只能追加,不能修改或删除
❌ 无法为由 KV 提供后端的现有 DO 启用 SQLite
- 必须改为创建新的 DO 类
✅ 代码更改不需要迁移
- 只有架构更改(新建/重命名/删除/转移)需要迁移
- 可以自由部署代码更新
✅ 全局唯一性按账户计算
- DO 类名称在整个账户中唯一
- 不同 Workers 之间也必须唯一
常见模式
模式 1:限流(按用户)
export class RateLimiter extends DurableObject {
async checkLimit(userId: string, limit: number, window: number): Promise<boolean> {
const key = `rate:${userId}`;
const now = Date.now();
// Get recent requests
const requests = await this.ctx.storage.get<number[]>(key) || [];
// Remove requests outside window
const validRequests = requests.filter(timestamp => now - timestamp < window);
// Check limit
if (validRequests.length >= limit) {
return false; // Rate limit exceeded
}
// Add current request
validRequests.push(now);
await this.ctx.storage.put(key, validRequests);
return true; // Within limit
}
}
// Worker usage:
const limiter = env.RATE_LIMITER.getByName(userId);
const allowed = await limiter.checkLimit(userId, 100, 60000); // 100 req/min
if (!allowed) {
return new Response('Rate limit exceeded', { status: 429 });
}
模式 2:会话管理
export class UserSession extends DurableObject {
sql: SqlStorage;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
this.sql.exec(`
CREATE TABLE IF NOT EXISTS session (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
expires_at INTEGER
);
`);
// Schedule cleanup alarm
ctx.blockConcurrencyWhile(async () => {
const alarm = await ctx.storage.getAlarm();
if (alarm === null) {
await ctx.storage.setAlarm(Date.now() + 3600000); // 1 hour
}
});
}
async set(key: string, value: any, ttl?: number): Promise<void> {
const expiresAt = ttl ? Date.now() + ttl : null;
this.sql.exec(
'INSERT OR REPLACE INTO session (key, value, expires_at) VALUES (?, ?, ?)',
key,
JSON.stringify(value),
expiresAt
);
}
async get(key: string): Promise<any | null> {
const cursor = this.sql.exec(
'SELECT value, expires_at FROM session WHERE key = ?',
key
);
const row = cursor.one<{ value: string; expires_at: number | null }>({ allowNone: true });
if (!row) {
return null;
}
// Check expiration
if (row.expires_at && row.expires_at < Date.now()) {
this.sql.exec('DELETE FROM session WHERE key = ?', key);
return null;
}
return JSON.parse(row.value);
}
async alarm(): Promise<void> {
// Cleanup expired sessions
this.sql.exec('DELETE FROM session WHERE expires_at < ?', Date.now());
// Schedule next cleanup
await this.ctx.storage.setAlarm(Date.now() + 3600000);
}
}
模式 3:领导者选举
export class LeaderElection extends DurableObject {
sql: SqlStorage;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
this.sql.exec(`
CREATE TABLE IF NOT EXISTS leader (
id INTEGER PRIMARY KEY CHECK (id = 1),
worker_id TEXT NOT NULL,
elected_at INTEGER NOT NULL
);
`);
}
async electLeader(workerId: string): Promise<boolean> {
// Try to become leader
try {
this.sql.exec(
'INSERT INTO leader (id, worker_id, elected_at) VALUES (1, ?, ?)',
workerId,
Date.now()
);
return true; // Became leader
} catch (error) {
return false; // Someone else is leader
}
}
async getLeader(): Promise<string | null> {
const cursor = this.sql.exec('SELECT worker_id FROM leader WHERE id = 1');
const row = cursor.one<{ worker_id: string }>({ allowNone: true });
return row?.worker_id || null;
}
async releaseLeadership(workerId: string): Promise<void> {
this.sql.exec('DELETE FROM leader WHERE id = 1 AND worker_id = ?', workerId);
}
}
模式 4:多 DO 协调
// Coordinator DO
export class GameCoordinator extends DurableObject {
async createGame(gameId: string, env: Env): Promise<void> {
// Create game room DO
const gameRoom = env.GAME_ROOM.getByName(gameId);
await gameRoom.initialize();
// Track in coordinator
await this.ctx.storage.put(`game:${gameId}`, {
id: gameId,
created: Date.now(),
});
}
async listGames(): Promise<string[]> {
const games = await this.ctx.storage.list({ prefix: 'game:' });
return Array.from(games.keys()).map(key => key.replace('game:', ''));
}
}
// Game room DO
export class GameRoom extends DurableObject {
async initialize(): Promise<void> {
await this.ctx.storage.put('state', {
players: [],
started: false,
});
}
async addPlayer(playerId: string): Promise<void> {
const state = await this.ctx.storage.get('state');
state.players.push(playerId);
await this.ctx.storage.put('state', state);
}
}
重要规则
务必执行
✅ 从 Worker 导出 DO 类
export class MyDO extends DurableObject { }
export default MyDO; // Required
✅ 在构造函数中调用 super(ctx, env)
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env); // Required first line
}
✅ 为新的 DO 使用 new_sqlite_classes
{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }
✅ 使用 ctx.acceptWebSocket() 实现休眠
this.ctx.acceptWebSocket(server); // Enables hibernation
✅ 将关键状态持久化到存储中(而不只是内存)
await this.ctx.storage.put('important', value);
✅ 使用闹钟,而不是 setTimeout/setInterval
await this.ctx.storage.setAlarm(Date.now() + 60000);
✅ 使用参数化 SQL 查询
this.sql.exec('SELECT * FROM table WHERE id = ?', id);
✅ 尽量减少构造函数中的工作
constructor(ctx, env) {
super(ctx, env);
// Minimal initialization only
ctx.blockConcurrencyWhile(async () => {
// Load from storage
});
}
切勿执行
❌ 创建 DO 却不添加迁移
// Missing migrations array = error
❌ 忘记导出 DO 类
class MyDO extends DurableObject { }
// Missing: export default MyDO;
❌ 使用 setTimeout 或 setInterval
setTimeout(() => {}, 1000); // Prevents hibernation
❌ 使用 WebSocket 时只依赖内存状态
// ❌ WRONG: this.sessions will be lost on hibernation
// ✅ CORRECT: Use serializeAttachment()
❌ 逐步部署迁移
# Migrations are atomic - cannot use gradual rollout
❌ 在现有的、由 KV 支持的 DO 上启用 SQLite
// Not supported - must create new DO class instead
❌ 使用标准 WebSocket API 并期待其支持休眠
ws.accept(); // ❌ No hibernation
this.ctx.acceptWebSocket(ws); // ✅ Hibernation enabled
❌ 假设位置提示一定有效
// Location hints are best-effort only
已知问题预防
此技能可预防 15 个以上的已记录问题:
问题 #1:未导出类
错误:"binding not found" 或 "Class X not found"
来源:https://developers.cloudflare.com/durable-objects/get-started/
原因:未从 Worker 导出 DO 类
预防措施:
export class MyDO extends DurableObject { }
export default MyDO; // ← Required
问题 #2:缺少迁移
错误:"migrations required" 或 "no migration found for class"
来源:https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/
原因:创建 DO 类时未添加迁移条目
预防措施:创建新的 DO 类时务必添加迁移
{
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }
]
}
问题 #3:迁移类型错误(KV 与 SQLite)
错误:架构错误、存储 API 不匹配
来源:https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/
原因:使用了 new_classes,而不是 new_sqlite_classes
预防措施:SQLite 后端使用 new_sqlite_classes(推荐)
问题 #4:构造函数开销阻碍休眠唤醒
错误:休眠唤醒时间过长
来源:https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/
原因:构造函数中执行了繁重的工作
预防措施:尽量精简构造函数,并使用 blockConcurrencyWhile()
constructor(ctx, env) {
super(ctx, env);
ctx.blockConcurrencyWhile(async () => {
// Load from storage
});
}
问题 #5:setTimeout 会破坏休眠
错误:DO 始终无法休眠,产生高额时长费用
来源:https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/
原因:setTimeout/setInterval 会阻止休眠
预防措施:改用闹钟 API
// ❌ WRONG
setTimeout(() => {}, 1000);
// ✅ CORRECT
await this.ctx.storage.setAlarm(Date.now() + 1000);
问题 #6:休眠时内存状态丢失
错误:WebSocket 元数据丢失,状态意外重置
来源:https://developers.cloudflare.com/durable-objects/best-practices/websockets/
原因:依赖休眠时会被清除的内存状态
预防措施:使用 serializeAttachment() 保存 WebSocket 元数据
ws.serializeAttachment({ userId, username });
// Restore in constructor
ctx.getWebSockets().forEach(ws => {
const metadata = ws.deserializeAttachment();
this.sessions.set(ws, metadata);
});
问题 #7:传出的 WebSocket 无法休眠
错误:即使使用休眠 API,费用仍然很高 来源:https://developers.cloudflare.com/durable-objects/best-practices/websockets/ 原因:传出的 WebSocket 不支持休眠 预防措施:仅对服务端(传入)WebSocket 使用休眠
问题 #8:全局唯一性方面的误解
错误:出现意外的 DO 类名称冲突 来源:https://developers.cloudflare.com/durable-objects/platform/known-issues/#global-uniqueness 原因:每个账户中的 DO 类名称都是全局唯一的 预防措施:了解 DO 类名称在账户中的所有 Worker 之间共享
问题 #9:KV 后端的 deleteAll 不会完全删除
错误:存储未完全删除,仍持续计费
来源:https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/
原因:KV 后端的 deleteAll() 可能只完成部分删除
预防措施:使用 SQLite 后端实现原子性 deleteAll
问题 #10:绑定名称不匹配
错误:访问 DO 绑定时发生运行时错误
来源:https://developers.cloudflare.com/durable-objects/get-started/
原因:wrangler.jsonc 中的绑定名称与代码不匹配
预防措施:确保名称一致
{ "bindings": [{ "name": "MY_DO", "class_name": "MyDO" }] }
env.MY_DO.getByName('instance'); // Must match binding name
问题 #11:超出状态大小限制
错误:"state limit exceeded" 或存储错误
来源:https://developers.cloudflare.com/durable-objects/platform/pricing/
原因:超出 1GB(SQLite)或 128MB(KV)的限制
预防措施:监控存储大小,并使用 alarms 实现清理
问题 #12:迁移不是原子操作
错误:渐进式部署受阻 来源:https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/ 原因:尝试将渐进式发布与迁移结合使用 预防措施:迁移会在所有实例上以原子方式部署
问题 #13:位置提示被忽略
错误:DO 在错误的区域中创建 来源:https://developers.cloudflare.com/durable-objects/reference/data-location/ 原因:位置提示尽力而为,不作保证 预防措施:有严格要求时使用 jurisdiction
问题 #14:Alarm 重试失败
错误:Alarm 失败后任务丢失 来源:https://developers.cloudflare.com/durable-objects/api/alarms/ 原因:Alarm 处理程序反复抛出错误 预防措施:实现幂等的 Alarm 处理程序
async alarm(info: { retryCount: number }): Promise<void> {
if (info.retryCount > 3) {
console.error('Giving up after 3 retries');
return;
}
// Idempotent operation
}
问题 #15:Fetch 阻止休眠
错误:即使使用了休眠 API,DO 也从不休眠
来源:https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/
原因:进行中的 fetch() 请求会阻止休眠
预防措施:确保所有异步 I/O 都在空闲期开始前完成
配置参考
完整的 wrangler.jsonc 示例
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-10-22",
// Durable Objects configuration
"durable_objects": {
"bindings": [
{
"name": "COUNTER", // Binding name (use as env.COUNTER)
"class_name": "Counter" // Must match exported class
},
{
"name": "CHAT_ROOM",
"class_name": "ChatRoom"
}
]
},
// Migrations (required for all DO changes)
"migrations": [
{
"tag": "v1", // Initial migration
"new_sqlite_classes": [
"Counter",
"ChatRoom"
]
},
{
"tag": "v2", // Rename example
"renamed_classes": [
{
"from": "Counter",
"to": "CounterV2"
}
]
}
]
}
TypeScript 类型
import { DurableObject, DurableObjectState, DurableObjectNamespace } from 'cloudflare:workers';
// Environment bindings
interface Env {
MY_DO: DurableObjectNamespace<MyDurableObject>;
DB: D1Database;
// ... other bindings
}
// Durable Object class
export class MyDurableObject extends DurableObject<Env> {
sql: SqlStorage;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
}
async myMethod(): Promise<string> {
// Access env bindings
await this.env.DB.prepare('...').run();
return 'Hello';
}
}
官方文档
- Durable Objects:https://developers.cloudflare.com/durable-objects/
- State API(SQL):https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/
- WebSocket 休眠:https://developers.cloudflare.com/durable-objects/best-practices/websockets/
- Alarms API:https://developers.cloudflare.com/durable-objects/api/alarms/
- 迁移:https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/
- 最佳实践:https://developers.cloudflare.com/durable-objects/best-practices/
- 定价:https://developers.cloudflare.com/durable-objects/platform/pricing/
有问题?遇到故障?
- 查看
references/top-errors.md了解常见问题 - 查看
templates/中的可运行示例 - 查阅官方文档:https://developers.cloudflare.com/durable-objects/
- 仔细检查迁移配置