工人在设计上是无国籍的。每个请求都到达一个干净的隔离区,没有之前发生的事情的记忆,也没有与并行运行的其他实例共享状态。这种隔离正是允许您在无需实例之间协调的情况下扩展数百万个请求的原因。耐用物品故意打破了这个契约——了解为什么会发生变化,以及到底发生了什么变化,决定了你是会正确使用它们还是会受到它们的影响。
持久对象实际上是什么
持久对象是一个 JavaScript 类,具有持久存储和改变一切的执行细节:请求连续到达。实例内不存在竞争。当 DO 的方法 0 正在处理请求时,到达同一 DO 的所有其他方法都在外部排队。
这消除了由于并发访问共享状态而产生的一整类错误。 DO 内不存在竞争条件。如果递增计数器、保留该值并响应,则这些操作之间不能交织其他请求。顺序由平台保证。
每个 DO 实例都存在于单个 Cloudflare PoP 中——距离创建它的第一个请求最近的存在点。无论客户端位于何处,对同一 DO 的后续请求都会路由到该特定 PoP。如果您的实例是在法兰克福创建的,并且来自圣保罗的客户端向该实例发出请求,则该请求将传输到法兰克福。这对于地理上分布的用例具有真正的延迟影响,并且在设计之前了解这一点很重要。
为什么KV和D1不能解决同样的问题
自然的比较是与平台上的其他持久性选项 KV 和 D1。区别不是方便性的区别,而是一致性模型的区别。
KV最终是一致的。对一个 PoP 的写入会以可测量的延迟传播到其他 PoP,通常在几毫秒到一分钟之间。不同 PoP 中的读数可能会看到相同值的不同版本。对于读缓存,对于很少改变的设置,KV 工作得很好。对于任何需要“读取、计算、写入”并保证其间没有发生其他写入的操作来说,这是行不通的。
具有事务的 D1 解决了读取和写入的原子性,但引入了写入的跨区域延迟。所有写入都会发送到银行的主要区域,该区域可能与您的 Worker 位于不同的 PoP 中。对于许多应用来说这是可以接受的。对于需要低延迟和高频率修改状态,或者客户端之间的实时协调,每次写入主区域的延迟成本会改变问题。
DO 在内存中维护状态并通过存储 API 以原子方式持久保存。操作 1 是线性化的:对同一 DO 的任何后续请求都将看到写入的值,无一例外。这种保证与串行执行相结合,使得构建原子计数器、分布式锁、有序事件日志以及并发客户端之间的协调成为可能,而无需在应用程序中实现并发控制的复杂性。
串行执行如何影响吞吐量
串行执行既是保证,又是瓶颈。在 5 毫秒内处理每个请求的 DO 每秒可以处理大约 200 个请求。如果您的应用程序将 500 个请求/秒路由到同一 DO,则其中 300 个请求将排队并增加延迟。
这个天花板是设计而存在的。如果单个 DO 成为瓶颈,解决方案就是分片:不使用固定的资源 ID,而是按数字后缀进行分配。对于由2标识的资源,策略3将负载分配给十个实例,每个实例都有自己的吞吐量上限。选择使用哪个分片需要具有确定性,以便来自同一客户端的读取和写入始终到达同一实例。
休眠改变了保持许多实例处于活动状态的成本模型。当 DO 没有待处理的请求时,它会自动休眠 - 休眠时没有计算成本。从休眠状态唤醒只需亚毫秒。对于具有许多稀疏 DO 的应用程序(每个用户一个、每个房间一个、每个会话一个),实际计算成本与活动使用量成正比,而不是与实例总数成正比。
您的价格等级有何要求
耐用物品需要工人付费计划,每月至少 5 美元。从这里开始,成本由三部分组成:请求(0.15 美元/百万,每月 100 万免费)、GB 秒计算(12.50 美元/百万 GB 秒,每月 40 万免费)和存储(每月 0.20 美元/GB,每月 1GB 免费)。
GB秒是令人困惑的部分。使用 128MB 内存运行 1 秒的 DO 消耗 0.125 GB 秒。每月 400,000 GB 秒的免费空间,相当于在最小占用空间上执行 320 万秒。 128MB 的平均 10 毫秒请求消耗 0.00125 GB 秒,因此免费套餐涵盖大约 3.2 亿个 10 毫秒最小内存请求。如果您的 DO 执行繁重的计算、在内存中维护较大的状态或每秒处理许多请求,则 GB 秒消耗会成比例增加。
另一个成本部分:警报 API。 DO 可以安排 4 方法在未来的某个日期运行 - 即使它在中间休眠。成本为 0.15 美元/百万次警报调用,与正常请求相同的表。
何时选择耐用对象
确定 DO 是否是正确工具的问题很简单:您需要管理的状态是否需要来自竞争客户端的序列化访问?如果是,请做。如果没有,还有一个更简单、更便宜的选择。
DO 解决了平台上其他任何东西都无法以相同保证解决的问题的情况:高写入频率的原子计数器、实时存在协调(谁连接到房间)、按到达排序的事件队列、通过 Alarm API 实现超时的分布式锁,以及多个客户端修改同一文档的协作编辑会话。
什么不是 DO 用例:具有即席查询的关系数据存储(D1 更适合)、偶尔写入的读取缓存(KV 更便宜且全局分布)、文件存储 (R2) 以及自然映射到单个写入而无需依赖于先前状态的并发读取的任何状态。
生产中要监控什么
DO 问题早期出现的两个指标:队列延迟增加(表明 DO 已成为瓶颈并需要分片)和 GB 秒消耗超出预期(表明 DO 在内存中维持状态的时间超过必要的时间,或因不必要的工作而保持活动状态)。
存储 API 的操作细节会让您大吃一惊:默认情况下,每次调用5 最多返回 128 个条目。对于较大的数据集,迭代需要使用游标。在开发中忽略这一点并在生产中使用一千个密钥找出这一点会在请求和响应时间方面产生实际成本。
是否使用 DO 的决定与性能无关,更多与应用程序所需的一致性保证有关。在选择工具之前了解这一要求可以节省以后昂贵的迁移费用。
另请阅读
- [持久对象编程模型:与您曾经使用过的任何东西都不一样6
- [生产中的耐用物品:法案将是什么样子以及令人惊讶的限制7
- [持久对象和 WebSockets:无需专用服务器的多人游戏8
- [当耐用物品是错误答案时9
- 【Cloudflare KV:当你需要写10时,全球分布式意味着什么?
- [Cloudflare Workers 与 Pages:选择之前最重要的区别11
