Taocarts 知识

1688自动采购

📅 2026-02-11 系统功能介绍

1688自动采购的可靠性设计:幂等、限流兜底与状态机

双11那晚,1688开放平台的订单回调延迟了快二十分钟。系统收到回调后自动触发采购单创建,但由于网络抖动,同一个order_id的回调报文被重复发送了三次——结果同一笔订单在1688那边下了三遍。客户只付了一次款,供应商却发了三份货。

这不是段子。1688官方文档写着“回调丢包率高峰期约1%-3%”,但没提的是:重复回调的概率同样不低。对接1688自动采购系统时,如果不做幂等性设计,迟早被重复回调坑一次。

问题定义:三个必须兜底的场景

1688采购链路上,有三个环节最容易被重复或丢失击穿:

  1. 创建采购单:网络超时后客户端重试,同一请求可能被执行多次
  2. 订单状态同步:1688回调或定时拉取,可能重复触发“已发货”状态流转
  3. 库存预扣:用户提交订单时预扣库存,支付失败后释放,释放操作本身也可能重试

每个场景的解决方案不一样。采购单创建需要请求幂等,状态同步需要状态机幂等,库存操作需要原子计数

方案对比:简单重试 vs 幂等+状态机

初期偷懒的做法:收到回调直接处理,处理失败就记日志人工补单。日单量几十的时候还能扛,破百后错单率飙升到5%左右。1688接口偶尔静默升级签名算法,一整天的订单都没同步到仓库(案例3)。

后来改成唯一请求ID + 数据库唯一索引的方案。每次调用1688 API前,生成一个request_id(格式:{订单号}_{时间戳}_{随机数}),写入任务表时利用数据库唯一索引防重。重试时携带相同ID,下游识别已存在则直接返回成功。

// 幂等创建采购任务(PHP + MySQL唯一索引)
function createPurchaseTask($orderId, $requestId) {

DB::beginTransaction();

try {

DB::insert('INSERT INTO purchase_tasks (request_id, order_id, status) VALUES (?, ?, ?)',

[$requestId, $orderId, 'pending']);

} catch (DuplicateKeyException $e) {

DB::commit();

return ['code' => 0, 'msg' => 'already submitted'];

}

// 调用1688 API(带指数退避重试)

$result = call1688WithRetry('placeOrder', $orderData, $requestId);

DB::update('UPDATE purchase_tasks SET status = ? WHERE request_id = ?',

[$result['success'] ? 'done' : 'failed', $requestId]);

DB::commit();

return $result;
}

对于1688 API的超时和限流(429),用指数退避重试:间隔1秒、2秒、4秒,最多3次。重试时保持相同的request_id,幂等层会过滤重复请求。注意1688的限流维度:应用级约1000次/分钟,用户级约5次/秒。重试不能超过用户级QPS,否则会触发封禁。

隐性知识点:回调丢失的兜底方案

1688的回调可靠性是个黑盒。官方说丢包率1%-3%,但大促期间实测更高。唯一的兜底方案是:定时任务全量拉取。每天凌晨跑一次任务,查询1688近三天订单状态,与本地的purchase_tasks表做比对。状态不一致的自动修复,并发送告警。

这个拉取任务的频率不能太高,否则会触发IP限流。按订单号分批拉取,每批间隔1秒。同时,拉取接口也有坑:1688的order.status字段在大促期间可能延迟5-30分钟才更新,所以比对逻辑要留出时间窗口。

// 状态修复任务伪代码
function sync1688OrderStatus() {

$localOrders = DB::select("SELECT order_id, supplier_order_id FROM purchase_tasks WHERE status = 'pending' AND created_at > NOW() - INTERVAL 3 DAY");

foreach (array_chunk($localOrders, 20) as $chunk) {

foreach ($chunk as $order) {

$remoteStatus = call1688Api('getOrderStatus', $order['supplier_order_id']);

if ($remoteStatus != $order['status']) {

updateLocalOrderStatus($order['order_id'], $remoteStatus);

}

usleep(1000000); // 间隔1秒

}

}
}

落地方案:状态机 + 乐观锁

除了幂等,状态机是防止重复流转的第二道防线。订单状态只能单向流转:待支付→采购中→已入库→已发货→已签收。更新状态时,SQL里加上当前状态条件,用乐观锁保证并发安全。

-- 状态流转必须带当前状态条件
UPDATE orders SET status = '采购中' WHERE order_id = ? AND status = '待支付';

这样即使重复收到“已发货”的回调,状态也不会从“采购中”直接跳到“已发货”两次。

在taocarts 的自动采购模块中,这套幂等过滤 + 指数退避重试 + 定时拉取兜底已经封装为一个可配置的管道。日单量从几十涨到几百,没再出现过重复下单或漏单的事故。

回头想想,1688自动采购的稳定性不取决于单次调用的成功率,而在于异常场景下的容错设计。幂等性解决了重复,定时任务兜底解决了丢失,而状态机防止了乱序。下篇我们聊仓库管理和合包策略——当多个客户的几十个包裹混在一起,如何用系统保证不串货、不丢件。

wechat wechat qr