做电商或 SaaS 商品系统时,库存往往是最容易出问题、又最容易被忽视的一环。商品详情页显示“有货”,用户下单却提示“库存不足”;或者某个爆款早已售罄,运营却过了两天才发现。这类问题的根源通常不是库存数据本身,而是缺少一个持续、可靠的库存监控机制。
本文围绕“轮询商品详情 API 监测库存状态”这一思路,讲清楚如何设计一套轻量的库存与缺货监控系统:如何拉取库存、如何判断状态变化、如何触发补货提醒和到货通知,以及如何避免轮询带来的性能与稳定性问题。
一、为什么需要库存监控
库存监控的价值,本质上是把“被动发现”变成“主动感知”。在没有监控的情况下,库存异常通常由用户投诉或订单失败暴露出来,此时损失已经发生。引入监控后,可以在问题扩大前介入。
典型需要监控的场景包括:
低库存预警:库存低于安全水位时提醒运营补货,避免断货。
缺货识别:商品库存归零时及时标记,避免继续售卖导致超卖。
到货通知:商品从缺货恢复有货时,通知此前订阅的用户。
异常波动:库存短时间内大幅下降或异常回补,可能是数据同步问题或恶意刷单。
对于没有开放库存推送(Webhook)能力的第三方平台,轮询商品详情 API 是最通用、最容易落地的方案。
二、整体架构设计
一个完整的库存监控系统可以拆成四个部分:任务调度、数据采集、状态比对、通知分发。整体流程如下:
flowchart TD
A[定时调度器] --> B[读取待监控商品列表]
B --> C[调用商品详情 API]
C --> D{请求成功?}
D -- 否 --> E[记录失败并重试]
D -- 是 --> F[解析库存字段]
F --> G[与上次快照比对]
G --> H{状态是否变化?}
H -- 否 --> I[更新快照时间]
H -- 是 --> J[写入库存变更记录]
J --> K{库存状态}
K -- 低库存 --> L[通知运营补货]
K -- 缺货 --> M[标记缺货并记录]
K -- 恢复有货 --> N[通知订阅用户到货]
E --> I
这套架构的关键在于:采集与判断分离。采集层只负责拿到最新库存,判断层负责对比历史快照并决定是否触发动作,通知层负责把消息送到正确的人。分层之后,每一部分都可以独立扩展和替换。
三、商品详情 API 的调用与解析
不同平台的商品详情接口字段差异较大,但核心信息通常包括商品 ID、SKU 列表、每个 SKU 的库存数量、上下架状态等。下面以一个通用的 JSON 响应为例:
{
"code": 0,
"data": {
"productId": "P10086",
"title": "无线蓝牙耳机",
"status": "on_sale",
"skus": [
{ "skuId": "S1", "name": "黑色", "stock": 12 },
{ "skuId": "S2", "name": "白色", "stock": 0 }
]
}
}
解析时需要注意几个细节:
库存粒度:是商品级库存还是 SKU 级库存?监控应以 SKU 为单位,否则无法准确定位缺货规格。
字段缺失:部分接口在缺货时可能不返回库存字段,需要按 0 处理并记录异常。
状态字段:商品可能因下架、审核中而暂时不可售,这类情况不应误判为缺货。
四、轮询策略:频率、并发与限流
轮询最大的风险是“打爆接口”和“浪费资源”。合理的轮询策略需要在实时性和成本之间取得平衡。
4.1 分级轮询频率
不是所有商品都值得高频监控。可以按商品热度分级:
商品等级 判定依据 轮询间隔
高优先级 近 7 日销量高、收藏多 5 分钟
中优先级 有稳定销量 30 分钟
低优先级 长尾商品 2 小时
4.2 并发控制与限流
调用第三方 API 必须遵守对方的 QPS 限制。建议使用令牌桶或信号量控制并发,并在客户端做退避重试:
public class RateLimiter {
private final Semaphore permits;
private final long intervalMillis;
public RateLimiter(int permitsPerSecond) {
this.permits = new Semaphore(permitsPerSecond);
this.intervalMillis = 1000L / permitsPerSecond;
startRefill();
}
private void startRefill() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
permits.release(permits.availablePermits() == 0 ? 1 : 0);
}, intervalMillis, intervalMillis, TimeUnit.MILLISECONDS);
}
public void acquire() throws InterruptedException {
permits.acquire();
}
}
实际项目中更推荐使用 Guava 的 RateLimiter 或 Resilience4j,避免自己实现带来的边界问题。
4.3 失败重试与熔断
接口偶发超时是常态。建议采用指数退避重试(如 1s、2s、4s),连续失败超过阈值时触发熔断,暂停该商品的轮询并告警,避免无效请求持续堆积。
五、库存状态比对与变更检测
监控的核心是“发现变化”。每次拉取到库存后,需要与上一次快照比对,判断是否发生了有意义的状态迁移。
5.1 状态定义
可以把库存抽象为三种状态:
有货:库存大于安全水位。
低库存:库存大于 0 但小于等于安全水位。
缺货:库存等于 0。
5.2 快照存储
建议为每个 SKU 保存一条最新快照记录,包含库存数量、状态、更新时间。变更记录单独存表,用于追溯和统计:
CREATE TABLE sku_stock_snapshot (
sku_id VARCHAR(64) PRIMARY KEY,
product_id VARCHAR(64) NOT NULL,
stock INT NOT NULL,
stock_status VARCHAR(16) NOT NULL,
updated_at DATETIME NOT NULL
);
CREATE TABLE sku_stock_change_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
sku_id VARCHAR(64) NOT NULL,
from_status VARCHAR(16) NOT NULL,
to_status VARCHAR(16) NOT NULL,
from_stock INT NOT NULL,
to_stock INT NOT NULL,
changed_at DATETIME NOT NULL,
INDEX idx_sku_time (sku_id, changed_at)
);
5.3 变更判定逻辑
public StockChange detectChange(SkuSnapshot oldSnap, int newStock, int safeThreshold) {
StockStatus newStatus = resolveStatus(newStock, safeThreshold);
if (oldSnap == null) {
return new StockChange(null, newStatus, newStock);
}
if (oldSnap.getStatus() == newStatus) {
return null;
}
return new StockChange(oldSnap.getStatus(), newStatus, newStock);
}
private StockStatus resolveStatus(int stock, int safeThreshold) {
if (stock <= 0) {
return StockStatus.OUT_OF_STOCK;
}
if (stock <= safeThreshold) {
return StockStatus.LOW_STOCK;
}
return StockStatus.IN_STOCK;
}
注意:只有状态发生迁移时才触发通知,而不是每次库存数字变化都通知,否则会产生大量噪音。
六、补货提醒与到货通知
6.1 补货提醒
当状态从“有货”变为“低库存”或“缺货”时,向运营发送补货提醒。提醒内容应包含商品名称、SKU、当前库存、建议补货量,并附带商品后台链接,方便一键处理。
6.2 到货通知
当状态从“缺货”恢复为“有货”时,触发到货通知。这里需要一张订阅表,记录用户对某个 SKU 的到货订阅:
CREATE TABLE stock_subscription (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
sku_id VARCHAR(64) NOT NULL,
notified TINYINT DEFAULT 0,
created_at DATETIME NOT NULL,
UNIQUE KEY uk_user_sku (user_id, sku_id)
);
通知发送后需要把 notified 置为 1,避免重复推送。对于订阅量大的热门商品,建议分批发送并做去重,防止消息风暴。
6.3 通知渠道
运营侧:企业微信、钉钉机器人、邮件。
用户侧:站内信、短信、App Push、邮件。
通知渠道建议做成可配置的抽象层,便于后续扩展。
七、工程实践中的注意事项
7.1 幂等与去重
调度任务可能因重试或并发执行导致重复处理。变更记录和通知发送都需要保证幂等,例如用 sku_id + changed_at 做唯一约束,或在发送前检查是否已通知。
7.2 监控系统自身的可观测性
监控系统本身也需要被监控。建议记录:
每轮轮询的成功率、失败率、平均耗时。
接口限流触发次数、熔断次数。
通知发送成功率与积压量。
7.3 数据一致性
轮询拿到的库存是“某一时刻的快照”,与真实库存之间必然存在延迟。对于秒杀等高并发场景,轮询只能作为辅助手段,真正的库存扣减仍需依赖交易链路的原子操作。
7.4 成本控制
商品数量大时,全量轮询成本很高。可以通过“只监控在售且有销量的商品”“动态调整频率”“夜间降低频率”等方式控制调用量。
八、总结
库存与缺货监控并不复杂,难点在于稳定、可控、不打扰。核心思路可以归纳为四点:
用分级轮询控制调用成本,用限流和重试保证稳定性。
以 SKU 为单位保存快照,只在状态迁移时触发动作。
补货提醒面向运营,到货通知面向订阅用户,两者分开设计。
保证幂等、可观测和成本可控,让监控系统长期可靠运行。
把这套机制搭起来之后,库存问题就从“事后救火”变成了“事前预警”,运营效率和用户体验都会有明显提升。如有任何疑问,欢迎留言探讨!