Durable Objects 是强大的计算 API,提供带存储的计算构建块。每个 Durable Object 拥有私有、事务性和强一致性的存储。Durable Objects Storage API 提供对 Durable Object 附加存储的访问。
Durable Object 的内存状态 只要 Durable Object 未被从内存驱逐就会保留。没有传入请求流量的非活动 Durable Objects 可能被驱逐。存在正常操作如代码部署 会触发 Durable Objects 重启并丢失内存状态。因此,您应使用 Storage API 持久化需要在 Durable Objects 驱逐或重启后存活的状态。
Storage API 方法 在传递给 Durable Object 构造函数的 ctx.storage 参数上可用。Storage API 有多种方法,包括 SQL、时点恢复(PITR)、键值(KV)和 alarm API。
只有具有 SQLite 存储后端的 Durable Object 类才能访问 SQL API。
在 Worker 的 Wrangler 文件的迁移中使用 new_sqlite_classes:
{
"migrations": [
{
"tag": "v1", // Should be unique for each entry
"new_sqlite_classes": [ // Array of new classes
"MyDurableObject"
]
}
]
}[[migrations]]
tag = "v1"
new_sqlite_classes = [ "MyDurableObject" ]SQL API 在传递给 Durable Object 构造函数的 ctx.storage.sql 参数上可用。
SQLite 支持的 Durable Objects 还提供时点恢复 API,使用 bookmark 允许您将 Durable Object 的嵌入式 SQLite 数据库恢复到过去 30 天内的任意时点。
常见模式是在首次访问时从持久存储初始化 Durable Object 并设置实例变量。由于后续访问会路由到同一 Durable Object,因此可以返回任何已初始化的值,而无需再调用持久存储。
import { DurableObject } from "cloudflare:workers";
export class Counter extends DurableObject {
value: number;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
// `blockConcurrencyWhile()` ensures no requests are delivered until
// initialization completes.
ctx.blockConcurrencyWhile(async () => {
// After initialization, future reads do not need to access storage.
this.value = (await ctx.storage.get("value")) || 0;
});
}
async getCounterValue() {
return this.value;
}
}如果 Durable Object 关闭时其存储为空,则 Durable Object 将完全停止存在。如果您从未向 Durable Object 的存储写入任何内容(包括设置 alarm),则存储保持为空,因此 Durable Object 一旦关闭将不再存在。
但是,如果您曾使用 Storage API 写入,包括设置 alarm,则必须显式调用 storage.deleteAll() 清空存储,如果配置了 alarm 则调用 storage.deleteAlarm()。仅删除您写入的特定数据(如删除键或删除表)是不够的,因为可能残留一些元数据。移除所有存储的唯一方法是调用 deleteAll()。调用 deleteAll() 确保 Durable Object 不会因存储而产生计费。
export class MyDurableObject extends DurableObject<Env> {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
}
// Clears Durable Object storage
async clearDo(): Promise<void> {
// If you've configured a Durable Object alarm
await this.ctx.storage.deleteAlarm();
// This will delete all the storage associated with this Durable Object instance
// This will also delete the Durable Object instance itself
await this.ctx.storage.deleteAll();
}
}以下 SQL API 示例使用以下 SQL schema:
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;
this.sql.exec(`CREATE TABLE IF NOT EXISTS artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);INSERT INTO artist (artistid, artistname) VALUES
(123, 'Alice'),
(456, 'Bob'),
(789, 'Charlie');`
);
}
}将查询结果迭代为行对象:
let cursor = this.sql.exec("SELECT * FROM artist;");
for (let row of cursor) {
// Iterate over row object and do something
}将查询结果转换为行对象数组:
// Return array of row objects: [{"artistid":123,"artistname":"Alice"},{"artistid":456,"artistname":"Bob"},{"artistid":789,"artistname":"Charlie"}]
let resultsArray1 = this.sql.exec("SELECT * FROM artist;").toArray();
// OR
let resultsArray2 = Array.from(this.sql.exec("SELECT * FROM artist;"));
// OR
let resultsArray3 = [...this.sql.exec("SELECT * FROM artist;")]; // JavaScript spread syntax将查询结果转换为行值数组的数组:
// Returns [[123,"Alice"],[456,"Bob"],[789,"Charlie"]]
let cursor = this.sql.exec("SELECT * FROM artist;");
let resultsArray = cursor.raw().toArray();
// Returns ["artistid","artistname"]
let columnNameArray = this.sql.exec("SELECT * FROM artist;").columnNames.toArray();获取查询结果的第一行对象:
// Returns {"artistid":123,"artistname":"Alice"}
let firstRow = this.sql.exec("SELECT * FROM artist ORDER BY artistname DESC;").toArray()[0];检查查询结果是否恰好有一行:
// returns error
this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;").one();
// returns { artistid: 123, artistname: 'Alice' }
let oneRow = this.sql.exec("SELECT * FROM artist WHERE artistname = ?;", "Alice").one()返回的 cursor 行为:
let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let result = cursor.next();
if (!result.done) {
console.log(result.value); // prints { artistid: 123, artistname: 'Alice' }
} else {
// query returned zero results
}
let remainingRows = cursor.toArray();
console.log(remainingRows); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]返回的 cursor 和 raw() 迭代器遍历相同的查询结果:
let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let result = cursor.raw().next();
if (!result.done) {
console.log(result.value); // prints [ 123, 'Alice' ]
} else {
// query returned zero results
}
console.log(cursor.toArray()); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]sql.exec().rowsRead():
let cursor = this.sql.exec("SELECT * FROM artist;");
cursor.next()
console.log(cursor.rowsRead); // prints 1
cursor.toArray(); // consumes remaining cursor
console.log(cursor.rowsRead); // prints 3您可以使用 TypeScript 类型参数 ↗ 为结果提供类型,在迭代查询结果时受益于类型提示和检查。
您的类型必须符合 TypeScript Record ↗ 类型的形状,表示列的名称(string)和列的类型。列类型必须是有效的 SqlStorageValue:ArrayBuffer | string | number | null 之一。
例如,
type User = {
id: string;
name: string;
email_address: string;
version: number;
};然后可以将此类型作为类型参数传递给 sql.exec() 调用:
// The type parameter is passed between angle brackets before the function argument:
const result = this.ctx.storage.sql
.exec<User>(
"SELECT id, name, email_address, version FROM users WHERE id = ?",
user_id,
)
.one();
// result will now have a type of "User"
// Alternatively, if you are iterating over results using a cursor
let cursor = this.sql.exec<User>(
"SELECT id, name, email_address, version FROM users WHERE id = ?",
user_id,
);
for (let row of cursor) {
// Each row object will be of type User
}
// Or, if you are using raw() to convert results into an array, define an array type:
type UserRow = [
id: string,
name: string,
email_address: string,
version: number,
];
// ... and then pass it as the type argument to the raw() method:
let cursor = sql
.exec(
"SELECT id, name, email_address, version FROM users WHERE id = ?",
user_id,
)
.raw<UserRow>();
for (let row of cursor) {
// row is of type User
}您可以表示任何所需结果类型的形状,包括更复杂的类型。如果您跨多个表执行 JOIN,可以组合反映查询结果的类型。
为您最常查询的表和过滤列创建索引,减少扫描的数据量并同时提高查询性能。如果您有读密集型工作负载(最常见),这可能特别有利。写入索引引用的列将至少增加一行写入以更新索引,但这通常被因索引而减少的读取行数所抵消。
Cloudflare Workers 提供基于 SQLite 的无服务器数据库产品 — D1。如何比较 Durable Objects 中的 SQLite 与 D1?
D1 是托管数据库产品。
D1 适合开发者熟悉的架构,应用服务器通过网络与数据库通信。应用服务器通常是 Workers;不过,D1 还通过 HTTP API 支持外部非 Worker 访问,这有助于为 D1 解锁 第三方工具 支持。
D1 旨在提供"开箱即用"的功能集,包括上述 HTTP API、数据库 schema 管理、数据导入/导出 和 数据库查询洞察。
使用 D1 时,应用代码和 SQL 数据库查询不在同一位置,这可能影响应用性能。如果性能是 D1 的顾虑,Workers 提供 Smart Placement,可动态在最佳位置运行 Worker,以减少 Worker 请求总延迟,同时考虑 Worker 通信的所有对象,包括 D1。
Durable Objects 中的 SQLite 是用于分布式系统的较低级别计算与存储构建块。
按设计,Durable Objects 仅可通过 Workers 访问。
Durable Objects 需要更多工作,但回报是更大的灵活性和控制权。使用 Durable Objects,你必须实现两段在不同位置运行的代码:前端 Worker 将来自 Internet 的传入请求路由到唯一的 Durable Object,以及 Durable Object 本身,它与 SQLite 数据库运行在同一台机器上。你可以选择什么在哪里运行,你的应用可能受益于在数据库旁边运行某些应用业务逻辑。
使用 Durable Objects 中的 SQLite,你可能还需要构建 D1 自带的一些数据库工具。
SQL 查询定价和限制旨在在 D1(定价、限制)与 Durable Objects 中的 SQLite(定价、限制)之间保持一致。