用户来问"XX 什么时候有货"是商城客服最重复的工作;做了到货通知又怕同一条消息给同一个人发好几遍、几百人同时到货时把通道打爆。读完你能拿到一套"订阅登记 + 去重 + 批量限流 + 失败补发"的可落地设计,附可直接抄走的表结构与处理流程。
到货通知的第一步是登记订阅关系,核心问题是"一个商品 + 一个用户只能有一条有效订阅"。用唯一索引兜底,状态机区分订阅生命周期:
CREATE TABLE stock_subscribe (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
spu_id BIGINT NOT NULL,
sku_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'WAITING', -- WAITING/NOTIFIED/CANCELLED/EXPIRED
created_at DATETIME NOT NULL,
notified_at DATETIME,
UNIQUE KEY uk_sub (sku_id, user_id, status) -- 唯一键要带上 status 维度
);为什么唯一键要带 status?因为同一用户可能"等到了没买、过几天又没货了再订阅一次",如果不区分状态,第二次订阅会撞唯一键直接失败。正确做法是:新订阅先看是否存在 WAITING 记录,存在则复用,不存在才插入——把"重复订阅"变成"幂等复用"。
到货信号来自两个地方:库存变更的实时事件(入库、退货回仓、调拨到店)和定时扫描兜底(事件丢了、库存手工改数)。实时路径用消息队列解耦:
{ "event": "stock.in", "skuId": 1001, "qty": 50, "source": "purchase_order" }消费端收到事件后按 sku 聚合待通知用户。定时扫描兜底每天跑一次,把"库存大于 0 且存在 WAITING 订阅"的 SKU 捞出来补触发,保证事件链路出问题时通知不会永久漏掉:
SELECT DISTINCT s.sku_id
FROM stock_subscribe s
JOIN sku_stock st ON st.sku_id = s.sku_id
WHERE s.status = 'WAITING' AND st.available_qty > 0;捞出的 SKU 走同一套通知流程,生成独立的批次号与实时事件区分开,靠通知幂等表保证两边不会重复发。
重复通知的源头有两个:同一用户在不同入口重复订阅;同一批用户被事件和扫描各触发一次。订阅侧靠唯一索引 + 幂等复用;通知侧靠"通知流水表 + 唯一键":
CREATE TABLE stock_notice_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
sku_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
batch_no VARCHAR(32) NOT NULL, -- 本次到货批次号
status VARCHAR(16) NOT NULL DEFAULT 'PENDING', -- PENDING/SENT/FAILED/MANUAL
sent_at DATETIME,
UNIQUE KEY uk_notice (sku_id, user_id, batch_no)
);每个 SKU 每轮到货生成一个 batch_no,同一用户对该批次只允许一条通知记录,插入冲突即跳过——事件和扫描即使重复触发,也只会发一次。用户主动取消订阅时把 status 置 CANCELLED,而不是删除行:删除后同一用户再次订阅会新建记录,历史通知关系就断了;保留状态才能让"订阅-通知-取消-再订阅"形成完整闭环。
到货是"一对多"突发流量:一个 SKU 可能几百人订阅,同时触发。逐条调用推送接口会瞬间打爆短信或订阅消息通道。做法是分片批量 + 退避限流:
# 伪代码示意:按 100 一批分片,每批间隔退避
for batch in split(waiting_users, 100):
push_batch(batch) # 走批量接口
sleep(backoff_ms) # 通道限速同一用户同时订阅了多个 SKU、多轮到货撞在一起时,还要做"用户维度合并":把同一用户当轮所有到货商品合并成一条消息推送,而不是一件商品一条通知。合并推送既省通道资源,也避免用户被频繁打扰。
多实例部署时还要防"多个实例同时推同一个批次":通知流水表的 uk_notice 唯一键天然是抢占锁,谁先插入成功谁推送,其余实例插入冲突即跳过,不需要额外引分布式锁。
推送失败要区分对待:通道类失败(限流、超时)可退避重试;权限类失败(用户关闭订阅权限)直接标记不可达。失败记录留在通知流水里,状态置 FAILED 并记录重试次数:
重试策略:失败后按 1 分钟、5 分钟、30 分钟退避重试,至多 3 次;仍失败则标记 MANUAL,运营后台可手动补发;用户主动关闭订阅的标记 CANCELLED,不再进入任何批次。
补发必须按 batch_no 幂等,防止重试成功后又与下一轮批次重复。手动补发的目标用户从通知流水里按批次过滤,避免把已标记取消的用户重复纳入。
到货通知本质是"订阅关系 + 幂等通知 + 批量投递"三件事,不依赖特定框架:订阅关系用唯一索引与状态机管,通知用批号幂等表去重,投递用分片与限流保护通道。先确认自己的订阅模型有没有唯一约束、通知有没有幂等键,再逐步加批量与合并。本文方案仅作技术分享,具体实现按自身业务取舍。
Q:商品缺货后,怎么通知买家到货?
A:先登记"商品 + 用户"订阅关系,库存到货时按 SKU 触发通知,同一用户同一批次只发一次,失败自动退避重试。
Q:同一买家重复订阅缺货提醒,怎么保证只发一次?
A:订阅表按商品与用户建唯一索引,重复订阅幂等复用;通知侧按"商品 + 用户 + 批次号"建唯一记录,重复触发直接跳过。
Q:几百人同时订阅到货,怎么推送不崩不漏?
A:按固定数量分片批量推送,批间加退避限流;同一用户多件到货合并成一条消息;失败按退避重试并记录次数。
Q:到货通知发失败了,用户还能收到吗?
A:通道类失败自动退避重试至多 3 次,仍失败进入手动补发队列;用户关闭订阅的标记取消,不再进入后续批次。
到货通知做不好,要么漏通知得罪买家,要么重复轰炸被投诉。把订阅唯一约束、批次幂等和批量限流立住,两边都稳。仅作技术分享。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。