Shopify API 限流问题排查
429 错误,API 请求超过限制导致应用功能中断的排查和解决方案
#type / debug
#status / growing
#tech / dev
#resource / shopify
[!info] related notes
- 前置笔记: Shopify Admin API
- 相关 MOC: Shopify MOC
Shopify API 限流问题排查
现象
应用调用 Shopify API 时返回 429 Too Many Requests 错误:
{
"errors": "Exceeded 2 calls per second for api client. Reduce request rates to resume uninterrupted service."
}
影响:
- 应用功能中断
- 数据同步失败
- 用户体验下降
根本原因
Shopify 限制 API 调用频率,防止滥用:
REST API 限制
Bucket 算法:
- 填充速率:每秒 2 次请求
- 桶容量:40 次请求
- 计算:每次请求消耗 1 个令牌
示例:
初始:桶有 40 个令牌
0 秒:发送 40 个请求 → 桶空
2 秒:桶恢复 4 个令牌
发送第 41 个请求 → 如果桶空,返回 429
GraphQL API 限制
基于查询成本:
- 每秒 50 个成本点
- 桶容量 1000 个成本点
- 不同查询消耗不同成本
示例查询成本:
query {
products(first: 10) { # 成本:12
edges {
node {
id
title
}
}
}
}
解决方案
1. 添加速率限制逻辑
Node.js 示例:
import pLimit from 'p-limit';
// 限制并发请求数
const limit = pLimit(2); // 每秒最多 2 个请求
const promises = products.map(product =>
limit(() => updateProduct(product))
);
await Promise.all(promises);
2. 检测 429 并重试
async function callShopifyAPI(query, retries = 3) {
try {
const response = await admin.graphql(query);
return await response.json();
} catch (error) {
if (error.status === 429 && retries > 0) {
// 指数退避
const delay = Math.pow(2, 3 - retries) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
return callShopifyAPI(query, retries - 1);
}
throw error;
}
}
3. 优化 GraphQL 查询
❌ 低效查询(多次请求):
for (const productId of productIds) {
const product = await admin.graphql(`
query {
product(id: "${productId}") {
id
title
}
}
`);
}
✅ 高效查询(单次请求):
query {
nodes(ids: $productIds) {
... on Product {
id
title
}
}
}
4. 使用批量操作(Bulk Operations)
对于大量数据(10,000+ 产品):
mutation {
bulkOperationRunQuery(
query: """
{
products {
edges {
node {
id
title
}
}
}
}
"""
) {
bulkOperation {
id
status
}
}
}
优势:
- 不受速率限制
- 异步处理
- 适合大量数据导出
5. 缓存数据
const cache = new Map();
async function getProduct(productId) {
if (cache.has(productId)) {
return cache.get(productId);
}
const product = await fetchFromShopify(productId);
cache.set(productId, product);
return product;
}
6. 使用 Webhook 替代轮询
❌ 低效(每分钟轮询):
setInterval(async () => {
const orders = await fetchOrders();
// 处理新订单
}, 60000);
✅ 高效(使用 Webhook):
// 订阅 orders/create Webhook
app.post('/webhooks/orders', (req, res) => {
const order = req.body;
processNewOrder(order);
res.status(200).send('OK');
});
监控与预防
1. 检查响应头
Shopify 返回当前限流状态:
const response = await fetch(shopifyURL);
console.log(response.headers.get('X-Shopify-Shop-Api-Call-Limit'));
// 输出:32/40(已用 32,剩余 8)
2. 设置告警
if (callLimit > 35) { // 接近上限
console.warn('Approaching API rate limit');
// 发送告警通知
}
3. 使用 GraphQL 成本分析
在 GraphQL 查询中请求成本信息:
query {
products(first: 10) {
edges {
node {
id
}
}
}
}
# 查看响应中的 extensions.cost
最佳实践
- 避免循环中调用 API:批量处理
- 使用 Webhook:替代轮询
- 缓存数据:减少重复请求
- GraphQL 优先:比 REST 更高效
- 监控限流状态:提前预警
调试检查清单
- 是否在循环中调用 API?
- 是否可以用单次批量查询替代多次查询?
- 是否可以用 Webhook 替代轮询?
- 是否添加了速率限制和重试逻辑?
- 是否监控了 API 调用频率?