Cloudflare
Durable Objects
Programação
Estado
Concorrência

持久对象编程模型:与您曾经使用过的任何东西有何不同

Durable Objects 的编程模型与传统无服务器中的任何模型有何不同,其存储、事务和警报 API 改变了可能性。

持久对象编程模型:与您曾经使用过的任何东西有何不同

大多数第一次遇到持久对象的工程师将其视为“具有[嵌入式数据库]的工人”,并开始朝这个方向编写代码。结果是应用程序在测试中正常工作,但在生产中却以微妙的方式失败。这种思维模式是错误的,因为 DO 和有权使用银行的工人之间的区别不是方便性 - 而是国家所在的位置以及谁控制着银行的访问权。

Durable Object 类的结构

DO 是一个 JavaScript 类,具有三个核心元素:构造函数、4 方法和5 访问。当 DO 创建或从休眠状态唤醒时,构造函数运行一次。 6 处理收到的每个请求。 7 是持久存储 API,可在请求之间和休眠之间保留状态。

0

当您需要在处理任何请求之前对存储状态进行水合时,构造函数中的 8 是必需的。如果没有它,在存储 9 解决之前到达的请求将处于不完整状态。 10 将请求排队,直到回调解析 - 这是安全初始化 DO 的方法。

当状态存在于内存中时会发生什么变化

“从存储体读取、计算、写回”操作是服务器端应用程序中最常见的模式,也是最常见竞争条件的来源。在读取和写入之间,另一个进程可能修改了同一条记录。为了通过传统银行解决这个问题,您可以使用带锁的事务、SELECT FOR UPDATE 或某种形式的乐观版本控制。

在 DO 中,这个问题不以同样的方式存在。该状态存在于内存中 — 11 已经具有当前值。没有往返阅读。增量和持久性发生在同一个执行中,而其他请求无法交错。担保不是来自银行:它来自 DO 本身的串行执行。

这有一个需要明确的成本:一致性取决于您将修改的状态保留在内存中。如果 DO 在调用 12 之前休眠,则内存修改将丢失。在请求结束之前,每个重要的更改都需要保存在存储中。正确的模式是修改并保留相同的操作,而不依赖于稍后的刷新。

存储 API 以及何时使用 transaction()

存储API具有直接操作:13、14、15、16。所有内容都是独立原子且持久的——即使 DO 之后立即休眠,所写入的内容也保证位于磁盘上。线性化适用于实例的整个存储:17 之后的任何读取都将看到写入的值,无一例外。

当逻辑运算修改多个键并且它们都需要彼此一致时,18是正确的工具:

1

如果 19 回调抛出异常,则不会保留任何写入操作。存储状态返回到事务开始之前的状态。没有部分提交。这取代了应用程序中影响多个键的操作的补偿事务或回滚逻辑的需要。

20 的操作详细信息:默认情况下最多返回 128 个条目。对于较大的集合,请使用页面响应中返回的光标。忽视这一点就会发现真实数据生产的局限性。

Alarm API:在休眠状态下仍然存在的 setTimeout

工人们有 21 小时,但计时器无法在隔离冬眠后存活。当 Worker 完成处理请求时,任何待处理的计时器都会消失。对于需要在未来某个时刻执行某些操作(清除过期会话、重新发送失败消息、超时后使锁定无效)的 DO,Alarm API 是正确的机制:

2

22 接收以毫秒为单位的绝对时间戳。当 DO 睡眠时,Cloudflare 会维持预定的闹钟。在正确的时间,DO 被唤醒并调用方法23。成本为 0.15 美元/百万次调用 — 与正常请求的价格表相同。

每个 DO 一次只能有一个活动警报。 24 取代了前一个。如果您需要多个计时器,策略是将即将发生的事件列表存储在存储器中,为最近的事件安排警报,并在 25° 运行时处理过期事件并重新安排下一个事件。

路由到正确实例的工作原理

持久对象具有三种 ID 生成策略,选择决定了路由行为。

26 是确定性的:全局范围内相同的名称总是产生相同的 ID。当您希望请求“room-123”的所有客户都到达同一个 DO 时,可以采用此策略。 ID是通过名称散列得出的,同一命名空间中具有相同名称的两个DO是同一个对象。

27 生成一个随机 ID——始终是一个新的 DO。在创建实体时使用,并将存储 ID 以供将来参考,而无需从密钥派生它。

28 从您之前存储的十六进制字符串重建 ID。当 ID 使用 29 生成、保存在外部数据库或 KV 中并且您稍后需要引用相同的 DO 时很有用。

位置提示允许您建议地理管辖区(欧盟、美国),但不保证特定的 PoP。 DO 在距离实例化它的第一个请求最近的 PoP 中创建。对于具有欧盟数据驻留要求的应用程序,30 会将创建定向到欧洲 PoP,但由 Cloudflare 选择哪一个。

它如何连接到路由的 Worker

接收到客户端请求的Worker需要获取DO存根并转发请求:

3

存根与普通工人拥有相同的 31 份合同。路由的 Worker 无权访问 DO 存储,无法读取其内部状态,并且无法调用除 32 和 RPC(当配置为 33 时)以外的方法。通信是通过普通的 HTTP 请求进行的。

持久对象不是数据库抽象。它们是有状态的计算模型。将它们视为也处理逻辑的数据库,而不是具有身份和状态的对象,会导致混乱 - 并且代码在生产中工作是偶然的,而不是设计的。

另请阅读

  • [Cloudflare Durable Objects:边缘的一致状态 - 真正改变的是什么35
  • [生产中的耐用物品:法案将是什么样子以及令人惊讶的限制36
  • [持久对象和 WebSockets:无需专用服务器的多人游戏37
  • [当耐用物品是错误答案时38
  • [Cloudflare KV:当你需要写时,全球分布式意味着什么39
  • [Cloudflare Workers 与 Pages:选择之前最重要的区别40