Cloudflare Workers
D1
KV
R2
Bindings

Workers + D1 + KV + R2:在同一服务中组合绑定

单个处理程序中绑定的可组合性是 Workers 与简单代理的区别 - 但子请求的限制会增加每个操作的成本。

Workers + D1 + KV + R2:在同一服务中组合绑定

当您看到单个处理程序中可能实现的功能时,Workers 的价值主张就变得清晰起来:使用 SQL 从 D1 获取记录、检查 KV 中的条目、从 R2 获取对象、通过服务绑定调用内部服务,以及将异步工作排队到队列中 — 所有这些都在同一个调用中,每个绑定都作为 1 的属性注入,并且只需一行代码即可使用。无需实例化单独的 SDK,无需管理网络配置,也无需在环境变量中浮动凭据。 wrangler.toml 声明绑定,运行时交付它们。问题在于,每个操作都会消耗调用预算的子请求,并且账单出现的时间比看起来要早。

绑定模型及其解决的问题

绑定是 Workers 运行时将其代码连接到平台功能而不暴露凭据或网络配置的方式。当您在 wrangler.toml 中使用 3 声明 2 时,运行时会将具有 D1 接口的对象注入到 4 中。当您使用 6 声明 5 时,运行时会将 KV 接口注入到 7 中。没有[身份验证 32 令牌,没有端点 URL,没有第三方 SDK——绑定是直接调用 Cloudflare 基础设施。

这会对安全和操作产生影响。受感染的 Worker 没有可泄露的凭据 - 它只能操作为其声明的绑定以及这些绑定所具有的权限。对于只需要从 KV 读取的 Worker,您将绑定声明为只读,并且 Worker 实际上无法写入,无论代码尝试执行什么操作。这种功能分离比代码中的权限检查更强。

对于登台和生产,每个环境在 wrangler.toml 中都有自己的绑定 ID。代码没有改变——8仍然是9但是在暂存时它指向不同的D1银行、不同的KV命名空间、不同的R2存储桶。由于绑定在物理上是分离的,因此不存在暂存代码接触生产数据的风险。

子请求预算及其消耗方式

免费计划每次调用有 50 个子请求。付费计划有 1000 个。每个留下隔离的操作计数:任意 URL 10、11、12、13、14、15、16。使用 5 个查询调用 17 算作 1 个子请求 - 这个细节至关重要。

返回丰富的用户个人资料的 API 的典型处理程序:通过 ID 在 D1 中搜索用户 (1),在 KV 中搜索首选项 (2),在 R2 中检查个人资料照片 (3),通过服务绑定调用授权服务 (4)。总计:happy 路线中有 4 个子请求。扩展到 1000 个并发用户并且仍在限制内 — 每次调用 4 个子请求没有问题。

问题是由循环引起的。处理项目列表并对每个项目进行 D1 查询(经典的 N+1)的处理程序会迅速爆炸。带有一个查询的 50 个项目在第一次调用时均达到免费计划限制。如果付费计划中有 200 个项目,仍然在 1000 以内,但 200 个连续 D1 查询的累积延迟将达到数百毫秒。达到限制时的错误作为超出的子请求中的网络错误到达 - 在对客户端的响应正文中没有明确的消息,只是在尾部出现异常。

通过绑定进行具体优化

对于 D1,18 是最重要的优化。您不必为多个查询按顺序执行 19 ,而是将一组语句传递给 20 并接收一组结果 - 总共花费 1 个子请求。对于彼此不依赖的查询(例如,搜索不同类型的配置),批处理可以同时消除顺序延迟和子请求的费用。

并行 D1 查询 — 21 — 仍然算作单独的子请求,但它们并行执行,并且延迟由最慢的查询决定,而不是总和。当结果是独立的并且由于其他原因不需要该批次时,请使用22。当您想要合并子请求成本时,请使用23。

对于KV来说,最有价值的模式是模块缓存。 KV 具有网络延迟(即使最好的情况也只有几毫秒),并且很少更改的配置数据不需要在每次调用时重新读取。模块可以在全局范围内声明变量:

0

只要隔离存在于 PoP 中,后续调用就会重用该值,而不会浪费子请求。风险是过时——如果配置发生变化,缓存的隔离仍然使用旧版本,直到被丢弃。对于需要近实时的数据,低24KV或者调用重读比较合适。对于[功能标志 33] 和很少更改的基础设施配置,模块缓存是高效的。

对于 R2,绑定支持范围请求 — 25 — 这允许您仅获取所需对象的一部分,而不是整个文件。对于需要标头、嵌入元数据或 CSV 前几行的大型文件,范围请求可以节省内存和传输时间。答案是 26,可以直接通过管道传输到客户端,无需缓冲。

同一请求中多个绑定的组合

当您需要多种类型的数据来构建答案时,真正的力量就会显现。返回搜索页面的产品 API:在 D1 中对与过滤器匹配的 ID 进行 SQL 查询,在 KV 中并行读取价格(价格经常变化,并以 KV 形式存在以提高读取性能),以及从 R2 中为每个产品的主图像预签名 URL。

这种组合很自然 - 27、28、29 都可以在同一个处理程序中使用。效果不佳的设计是对列表中的每个项目按顺序执行此操作。正确的版本:查询 D1 返回 20 个 ID,30 表示 20 KV 并行读取(20 个子请求,但并行),31 表示 20 个 R2 URL(另外 20 个子请求)。总计:41 个子请求(1 D1 + 20 KV + 20 R2),在 1000 个付费限制内,延迟由 40 个并行提取中最慢的一个决定,而不是 41 个的总和。

监控子请求的使用情况

运行时不会直接在尾部公开每次调用的子请求计数。该方法是进行检测:创建一个简单的包装器,该包装器在每次绑定操作时都会增加一个计数器,并在处理程序末尾记录总数。通过 Logpush 导出,此数字允许您在达到超出生产限制的边缘情况之前检测部署何时增加消耗。

当处理程序在正确的批处理和并行性下可以使用 50 个子请求时,却使用 500 个子请求,这会不必要地浪费延迟和预算。在生产中发生事故后重新设计该处理程序比从一开始就测量消耗并在 N 仍然很小时进行纠正的成本更高。

另请阅读

  • [KV、R2 与 Cache API:何时使用每个 Cloudflare34 存储层
  • [生产中的 Cloudflare Workers:hello world 之后发生了什么变化35
  • [Workers:调试、日志和 Workers Tail — 无需日志服务器即可在边缘进行观察36
  • [工人:CPU 和内存限制 - 文档没有很好地解释37
  • [测试 Workers:单元、集成以及如何在不依赖 Cloudflare 的情况下模拟运行时38
  • [Cloudflare KV:当你需要写时,全球分布式意味着什么39