Gadget Shopify API 自适应限流实战:使用后台队列安全写入 Shopify

文章目录

当 Shopify 应用需要大量读取或写入 Shopify 数据时,很容易遇到 API 限流。

例如,一个应用可能需要:

  • 批量创建产品。
  • 批量更新产品价格。
  • 同步订单和库存。
  • 查询 Shopify 中没有同步到本地的数据。
  • 写入数据后继续处理 Shopify 返回结果。
  • 在多个店铺中同时执行相同任务。

如果应用直接连续调用 Shopify API,就可能收到 429 Too Many Requests,导致任务失败、数据不完整,甚至产生重复写入。

本教程演示如何使用 Gadget 的自适应限流和 Background Actions 处理这类任务。

字幕中的演示创建了两个测试产品:

  • 一个类似 “Cool Hat” 的产品。
  • 一个类似 “Warm Socks” 的产品。

重点不是产品本身,而是如何让 Shopify API 写入任务进入 Gadget 的后台队列,由 Gadget 根据当前店铺的 API 可用容量安排执行。

这份项目实际演示了什么

这不是一个前台购物插件

这个项目不是给消费者使用的主题插件,也不是一个直接显示在店铺页面上的功能。

它是一个 Shopify 应用后端开发示例,主要解决:

应用需要调用 Shopify API
→ Shopify 有访问频率限制
→ 应用把任务放入 Gadget 后台队列
→ Gadget 根据限流情况执行和重试

应用开发者可以通过这种方式处理大量 Shopify API 请求,而不需要在普通 Action 中手动等待、循环重试或自己编写复杂的退避逻辑。

演示了两种后台执行方式

字幕中实际演示了两种模式:

第一种模式是把完整的 Gadget Action 放入后台队列。这个 Action 会调用 Shopify API,获得返回结果,然后继续处理数据。

第二种模式是直接把 Shopify GraphQL 写请求放入后台队列。这个模式适合只需要可靠写入 Shopify,不需要在当前流程中继续处理返回结果的场景。

为什么 Shopify API 需要限流

Shopify API 不是无限调用的

Shopify 会限制应用在一段时间内可以执行的 API 工作量。

限制通常会受到以下因素影响:

  • 使用的 API 类型。
  • GraphQL 查询的复杂度。
  • 请求消耗的 API 成本。
  • 店铺计划。
  • 当前店铺的 API 使用情况。
  • 应用是否在短时间内集中发送大量请求。

因此,不应该在代码中假设所有店铺都有相同的 API 容量,也不应该把某个固定的限流数值永久写死在应用逻辑中。

直接循环调用容易出现 429

下面这种做法风险较高:

循环遍历 1,000 个产品
→ 每个产品立即调用 Shopify API
→ 连续发送大量请求
→ 触发 429
→ 任务中途失败

如果失败发生在写入过程中,还可能出现更麻烦的问题:

  • 前一部分产品已经写入。
  • 后一部分产品没有写入。
  • 应用不知道任务执行到了哪里。
  • 重新运行时可能创建重复产品。
  • 商家无法确认哪些数据成功、哪些数据失败。

因此,批量任务需要具备排队、重试、日志和失败恢复能力。

项目使用的技术栈

Gadget 后端

项目使用 Gadget 作为 Shopify 应用的后端平台。

Gadget 提供:

  • 托管的 Node.js 后端。
  • PostgreSQL 数据库。
  • Shopify 连接。
  • Shopify 数据同步。
  • 自动生成的 API。
  • Background Actions。
  • 后台任务队列。
  • Shopify API 客户端。
  • 日志和任务执行记录。

Shopify API

项目通过 Gadget 调用 Shopify API。

字幕中使用了两类调用方式:

  • 通过 Shopify API 客户端执行产品写入。
  • 通过 Shopify GraphQL API 执行写入操作。

具体查询和写入字段应根据当前 Shopify Admin API 版本和官方文档配置,不应该直接复制旧教程中的 API 版本或字段。

Background Actions

Background Actions 用于把任务从当前请求中移到后台队列。

当前请求只负责把任务提交到队列,不需要一直等待 Shopify API 可用。

后台任务会在适合的时间执行,并根据 Gadget 和 Shopify 的限流机制进行重试。

Gadget 内置的 Shopify 数据同步

数据同步已经使用限流机制

如果你使用过 Gadget 的 Shopify 数据同步功能,就会发现 Gadget 会尽可能快地将 Shopify 数据同步到 Gadget 数据库。

字幕说明,Gadget 的 Shopify 数据同步已经内置了自适应限流机制。

它会根据当前店铺可以使用的 Shopify API 容量调整同步速度:

  • API 容量较高时,提高同步速度。
  • API 容量较低时,降低请求速度。
  • 遇到临时限流时,等待后继续执行。
  • 尽量让数据同步保持稳定。

过去的问题

过去,Gadget 的内置数据同步可以自动处理限流,但开发者自己编写的 Shopify API 查询和写入不一定能够使用同一套限流机制。

这会造成两个问题:

Gadget 数据同步很稳定
但自定义 Shopify 写入可能触发 429

或者:

应用需要查询 Gadget 数据库之外的 Shopify 数据
但普通 Action 没有方便的限流和排队方式

新的 Background Action 集成就是为了解决这类自定义请求。

创建 Shopify 数据处理 Action

创建一个产品处理 Action

在 Gadget 中创建一个用于处理 Shopify 数据的 Action,例如:

processShopData

这个 Action 可以负责:

  1. 获取当前 Shopify 店铺。
  2. 调用 Shopify API。
  3. 创建或更新产品。
  4. 读取 Shopify 返回结果。
  5. 根据结果继续创建其他数据。
  6. 将处理结果写入日志或数据库。

字幕中的示例会创建一个测试帽子产品,并在 Shopify 返回结果后继续处理数据。

在 Action 中调用 Shopify API

Action 内部使用 Gadget 提供的 Shopify API 客户端。

示意结构如下:

export async function run({ api, connections, context }) {
  const shopId = context.shopId;

  const response = await callShopifyProductOperation({
    shopId,
    connections
  });

  // 根据 Shopify 返回结果继续处理
  // 例如创建变体、保存外部 ID 或更新本地记录

  return response;
}

这里的 callShopifyProductOperation 代表当前 Gadget 项目中使用的 Shopify 产品 API 调用。

实际开发时,应使用 Gadget 当前生成的 Shopify API 客户端和 Shopify 官方支持的产品操作,不要手写过时的 API 字段。

将完整 Action 放进后台队列

什么时候使用这种方式

如果 Shopify API 返回结果后还需要继续处理,就应该把完整的 Gadget Action 放入后台队列。

适合以下场景:

  • 创建产品后继续创建变体。
  • 写入 Shopify 后保存返回的产品 ID。
  • 根据 Shopify 返回结果更新本地数据库。
  • 写入成功后发送通知。
  • 根据返回结果触发下一步业务流程。
  • 处理多个相关 Shopify 资源。

提交后台任务

在父级 Action 或触发逻辑中,将 processShopData 加入 Gadget Background Actions 队列。

示意写法如下:

await api.processShopData.enqueue({
  shopId: currentShopId
});

具体方法名和参数格式以当前 Gadget 项目生成的 Action API 为准。

关键点是要把当前 Shopify 店铺的信息作为任务上下文传入。这样后台 Action 执行时,才能知道应该使用哪个店铺的 Shopify 连接。

后台任务如何执行

当任务进入队列后,Gadget 会根据当前 Shopify API 限流状态决定什么时候执行。

任务的执行过程大致如下:

提交 processShopData
→ 任务进入后台队列
→ Gadget 检查当前 Shopify API 容量
→ 有足够容量时执行 Action
→ Action 调用 Shopify API
→ 获取 Shopify 返回结果
→ 继续处理数据
→ 保存任务输出和日志

调用方不需要一直等待 Shopify API 释放容量。

查看执行结果

进入 Gadget 的后台任务队列和日志页面,可以查看:

  • 任务是否进入队列。
  • 任务何时开始执行。
  • Shopify API 是否调用成功。
  • Action 返回了什么结果。
  • 是否发生重试。
  • 是否出现错误堆栈。
  • 最终输出是什么。

字幕中的示例会在 Shopify 店铺中看到新创建的产品,同时在 Gadget 日志中看到 Shopify 返回的数据。

直接将 Shopify GraphQL 写入放进队列

什么时候使用这种方式

如果应用只需要可靠地写入 Shopify,不需要在当前 Action 中继续处理返回结果,可以直接把 Shopify GraphQL 请求放进后台队列。

适合以下场景:

  • 创建一个产品。
  • 更新一个产品标题。
  • 更新产品库存。
  • 写入产品 Metafield。
  • 发布或取消发布资源。
  • 发送一个不需要立即处理结果的 Shopify 写请求。

直接排队 Shopify 请求

这个模式不会先创建一个完整的业务 Action,而是直接将 Shopify GraphQL 请求放入 Gadget 后台队列。

示意结构如下:

await enqueueShopifyGraphQLRequest({
  shopId: currentShopId,
  document: SHOPIFY_WRITE_DOCUMENT,
  variables
});

这里的 SHOPIFY_WRITE_DOCUMENT 代表当前 Shopify API 文档中对应的写入操作。

不要将 Shopify Mutation 内容写死在旧代码中。Shopify 的 API 版本、字段和输入结构可能发生变化,应使用当前版本的官方文档确认具体操作。

使用产品写入作为测试

字幕中的第二个示例创建了一个测试袜子产品。

执行流程是:

提交 Shopify GraphQL 写入任务
→ 任务进入 Gadget 后台队列
→ Gadget 等待合适的 API 容量
→ 执行 Shopify 写入
→ 返回 Shopify 响应
→ 在日志中记录结果

如果写入成功,就可以在 Shopify 后台的产品列表中看到新产品。

两种方式应该怎么选择

选择完整 Action 队列

使用完整 Action 队列,如果 Shopify 返回结果后还需要业务处理。

例如:

创建产品
→ 获取 Shopify 产品 ID
→ 创建产品变体
→ 保存本地数据库记录
→ 发送完成通知

这种方式更适合完整的业务流程。

选择直接 Shopify 请求队列

使用直接请求队列,如果你只关心 Shopify 写入是否最终成功。

例如:

将产品标题更新到 Shopify

更新完成后不需要创建其他记录,也不需要在当前请求中处理返回数据,就可以直接将 Shopify 请求放入队列。

对比表

场景推荐方式
写入 Shopify 后还要处理返回结果将完整 Action 放入队列
需要保存 Shopify 返回的资源 ID将完整 Action 放入队列
只需要可靠地写入 Shopify直接排队 Shopify 请求
需要创建多个相关资源将完整 Action 放入队列
只执行一次简单更新直接排队 Shopify 请求
需要复杂的业务判断将完整 Action 放入队列

处理 429 和自动重试

后台队列会自动重试

如果直接将 Shopify 请求放入 Background Actions 队列,Gadget 会在任务执行失败时处理重试。

当 Shopify 暂时返回限流错误时,后台任务可以等待后再次尝试,而不是立即将整个业务流程判定为失败。

Shopify API 客户端也可能提供重试

字幕还提到,Gadget 使用的 Shopify API 客户端本身也包含重试能力。

因此,应用可以同时获得:

  • Gadget Background Action 的任务重试。
  • Shopify API 客户端的请求重试。
  • 自适应限流。
  • 后台任务日志。
  • 失败任务记录。

不过,不能因为有自动重试就完全忽略错误处理。生产应用仍然应该记录失败原因,并为无法恢复的错误设置人工处理流程。

仍然需要处理 429

即使使用后台队列,仍然可能遇到 429 或其他临时错误。

因此建议:

  • 记录 HTTP 状态码。
  • 保存 Shopify 返回的错误信息。
  • 记录请求对应的店铺。
  • 记录任务输入参数。
  • 使用幂等逻辑防止重复创建。
  • 对无法自动恢复的任务发送通知。
  • 按当前 Shopify API 文档处理重试提示。

Shopify API 的限流容量和请求成本可能因店铺计划以及查询内容不同而变化,不要把某个固定数值当成所有店铺的通用标准。

Shopify API 调用应尽早执行

不要在调用前进行过长处理

字幕中特别提醒,后台 Action 真正开始执行后,应尽快调用 Shopify API。

不建议这样做:

后台任务开始
→ 先处理几分钟本地数据
→ 再调用 Shopify API

因为在这几分钟内,Shopify API 的可用容量可能发生变化。

更合理的顺序是:

后台任务开始
→ 尽快调用 Shopify API
→ 获得结果
→ 再处理返回数据

这样可以更准确地利用当前的 API 状态。

先写入,再处理

对于需要写入 Shopify 并继续处理的流程,可以采用:

调用 Shopify API
→ 获取 Shopify 返回结果
→ 保存返回 ID
→ 创建本地关系
→ 执行后续逻辑

不要在调用 Shopify 之前执行大量不必要的计算。

测试第一个后台 Action

创建测试产品

在开发店铺中运行 processShopData Action。

字幕中的测试产品类似于:

Cool Hat

Action 提交后,当前请求可能不会立即返回完整产品数据,而是快速将任务放入后台队列。

查看 Shopify 产品

进入 Shopify 后台产品页面并刷新。

确认测试产品已经出现,并检查:

  • 产品标题。
  • 产品状态。
  • 产品创建时间。
  • 产品 ID。
  • 是否生成了预期的变体。

查看 Gadget 日志

回到 Gadget,打开:

  • Action 日志。
  • Background Action 队列。
  • 任务执行记录。

确认:

  • 任务成功进入队列。
  • Action 已经开始执行。
  • Shopify 返回了结果。
  • 后续处理逻辑已经完成。
  • 任务最终状态为成功。

测试第二个直接写入任务

直接排队 Shopify 写请求

运行第二种直接写入示例。

测试产品可以使用:

Warm Socks

这个示例不通过另一个业务 Action 处理结果,而是直接将 Shopify GraphQL 写请求放入后台队列。

检查任务结果

在 Shopify 后台确认产品已经创建。

然后在 Gadget 后台检查后台队列,确认可以看到:

  • GraphQL 任务。
  • 执行状态。
  • Shopify 返回结果。
  • 失败时的错误堆栈。
  • 请求使用的变量。

如果任务失败,后台记录应帮助你判断是:

  • API 限流。
  • 权限不足。
  • 输入字段错误。
  • 产品数据不符合 Shopify 要求。
  • Shopify 暂时不可用。
  • 应用连接配置错误。

生产环境的可靠性设计

使用幂等键防止重复创建

后台任务可能因为网络超时或临时错误进行重试。

如果每次重试都直接创建新产品,就可能出现重复产品。

建议为每个业务对象建立自己的唯一标识,例如:

externalProductKey

执行前先检查这个唯一标识是否已经对应 Shopify 产品。

如果已经创建成功,就不要重复创建。

保存 Shopify 资源 ID

当 Shopify 返回产品 ID后,应保存到 Gadget 数据库中。

这样下次执行时可以判断:

是否已经创建 Shopify 产品
是否需要更新现有产品
是否需要重新创建

不要只依赖任务日志判断数据是否已经写入。

记录任务状态

建议为批量任务保存状态,例如:

pending
processing
completed
failed
retrying

同时记录:

  • Shopify 店铺 ID。
  • 业务对象 ID。
  • Shopify 资源 ID。
  • 最近一次错误。
  • 重试次数。
  • 最后执行时间。
  • 完成时间。

控制批量任务大小

如果一次需要处理很多产品,不要把所有数据都塞进一个超大的 Action。

可以拆成:

主任务
→ 创建多个子任务
→ 每个子任务处理一小批数据
→ 分别记录成功和失败

这样更容易重试,也更容易定位问题。

不要硬编码限流数值

Shopify API 限流可能受到店铺计划、API 类型和查询成本影响。

即使某个测试店铺可以快速完成任务,也不能说明所有店铺都拥有相同的容量。

应让 Gadget 和 Shopify API 当前返回的限流信息决定执行节奏。

什么时候不需要使用这个方案

如果应用只是偶尔发送一两个简单请求,就不一定需要复杂的后台队列。

以下情况可以直接使用普通 Action:

  • 只读取一次简单数据。
  • 请求很少。
  • 客户正在等待结果。
  • 业务必须立即返回结果。
  • 请求失败后不需要自动重试。

但以下情况更适合使用 Background Actions:

  • 批量处理。
  • 大量 Shopify API 写入。
  • 需要自动重试。
  • 任务执行时间较长。
  • 不希望前台请求一直等待。
  • 需要在后台逐步处理数据。
  • 需要保存任务执行记录。

总结

这份字幕演示的是 Gadget 如何帮助 Shopify 应用处理 API 限流问题。

项目的核心技术包括:

  • Gadget Shopify 数据同步。
  • Shopify Admin API。
  • Shopify GraphQL API。
  • Gadget Background Actions。
  • 自适应限流。
  • Shopify API 重试。
  • Action 日志。
  • 后台任务队列。
  • 幂等写入。
  • 多店铺 Shopify 应用开发。

实际演示了两种模式。

第一种模式是把完整的 Gadget Action 放入后台队列:

调用 Shopify API
→ 读取返回结果
→ 继续处理数据

第二种模式是直接把 Shopify GraphQL 写请求放入后台队列:

提交 Shopify 写请求
→ Gadget 负责排队、限流和重试

如果 Shopify 应用需要批量创建产品、同步数据或处理大量 API 写入,直接在普通请求中循环调用 Shopify API 很容易触发 429。

更稳妥的做法是让 Gadget Background Actions 管理任务执行,让自适应限流根据当前 Shopify API 容量安排请求,并使用幂等设计避免重复写入。

参考资料

citation:Shopify Plus 计划与高级 API 访问

citation:Shopify Flow 中的 GraphQL 限流错误说明

citation:Shopify Editions Summer 2023