Skip to content

会员表中有 500 万条会员数据,有什么方式可以对即将过期的会员提前 7 天进行提醒? ​

  • 标签:后端、场景题

🏛 业务架构师 ​

会员到期前 7 天提醒,这是典型的低频定时任务场景,500 万数据量说实话不大——我会先警告自己别过度设计,再给方案。

第一档:定时任务 + 索引(500 万量级的最优解)

  • 给会员表建 (expire_date) 或 (expire_time) 索引
  • 每天凌晨跑一次:WHERE expire_date BETWEEN 今天 AND 今天+7天 AND 未提醒,命中索引,扫描量只有几十万
  • 提醒记录打标(remind_flag)防重复发送
sql
SELECT user_id FROM member
WHERE expire_time BETWEEN '2026-10-24' AND '2026-10-31'
  AND remind_status = 0;

机器成本几近为零,一天的窗口完全够。绝大多数团队到这一档就结束了。

第二档:量级再大或提醒规则复杂时

  • 按月分区,扫描只落目标分区
  • 或开会员时就把提醒任务写入延迟队列(时间轮),到期触发——规则变了(比如 7 天改 3 天)要重建任务,灵活性差,所以规则常变的营销类提醒适合扫表,规则固定的适合预生成

真正值得花心思的是业务侧(架构师差异点)

  1. 提醒的目的是续费率:A/B 测试提醒时机(7 天/3 天/当天)、渠道组合(push 免费、短信 4 分一条、电话最贵),算清单条提醒成本 × 续费转化增量的 ROI,别拍脑袋群发
  2. 提醒内容个性化:配合权益使用情况("您的会员还有 3 次免配送费未用")比干巴巴的"即将到期"转化好数倍——这要求提醒系统读得动权益数据
  3. 合规:营销类短信需要用户授权和退订通道,忘记做是监管风险
  4. 量级预期:如果业务告诉我要从 500 万涨到 5000 万,方案切第二档——架构随业务规划演进,不提前支付复杂度

🏗 系统架构师 ​

500 万会员、每天扫描即将过期的——核心思想是把"每天全表扫描"变成"以时间分片的消息/任务",我给三个档位的方案。

方案一:延迟消息(推荐,量级通用) 会员开通/续费成功时,计算 过期时间 - 7天 的时刻,投递一条延迟消息(RocketMQ 延迟级别 / 时间轮 / Redis ZSet 延迟队列)。到点消费,检查会员状态仍是"有效且未续费"再发提醒。

  • 优点:O(1) 精确触发,无需扫描;天然水平扩展
  • 细节:用户提前续费后,旧消息到点要做状态校验(当前到期时间是否仍匹配),防止误提醒——消息里带上预期的到期日,消费时比对

方案二:定时任务按到期日索引扫描(简单可靠) 表上加 expire_time 索引,每天凌晨跑 WHERE expire_time BETWEEN 今天 AND 7天后 的分页查询(游标分页避免深翻页),结果按用户分批推。500 万数据命中索引扫几万行,分钟级跑完。配合漏斗(提前7天/3天/1天)在任务里按日切片。适合不想引入延迟组件的团队。

方案三:Redis ZSet 时间轮ZADD member:expire {到期时间} {userId},定时任务每分钟 ZRANGEBYSCORE 取到窗口的成员。要注意与 DB 的兜底对账,防止 Redis 与 DB 脱钩。

工程共识:提醒这种"最多一次"的业务,我用方案一为主 + 方案二每日兜底对账:延迟消息可能丢(MQ 故障),每日低峰全量扫描补发漏网用户,幂等去重靠"用户+提醒类型+日期"的唯一键。这个组合既有实时性又有最终一致保障。

💻 全栈开发专家 ​

500 万数据量不大,关键是别全表扫。我给三个方案,按场景选。

方案一:索引 + 定时扫描(最简单可靠,推荐)

expire_time 建索引,XXL-JOB 每天跑:

sql
SELECT id, phone FROM member
WHERE expire_time >= NOW() AND expire_time <= DATE_ADD(NOW(), INTERVAL 7 DAY)
  AND remind_status = 0;
  • remind_status 标记已提醒,防重复发送;索引下这条 SQL 毫秒级。
  • 提醒动作(短信/推送)异步化:扫出 ID 后发 MQ,消费者调短信服务。
  • 分页防大事务:一次最多捞 1000 条,循环直到捞空,任务加分布式锁防多实例并发。

方案二:延迟消息(实时性最好)

开通/续费成功时发 RocketMQ 延迟消息,延迟时间 = expire_time - 7天 - now,到期消费发提醒。要点:消息要带 remindStatus 校验,用户中途续费后旧消息作废(消费时查库确认状态)。

方案三:时间轮

Redisson 的 RDelayedQueue 或自研时间轮,适合提醒时间会频繁变化的场景,成本比 MQ 高,500 万会员量级没必要。

我的选型 实际上线用方案一:扫表有兜底性(消息丢了第二天还会扫到),方案二做增强(当天准时触达)。两者结合,扫表任务同时承担"漏发对账"职责,这才是生产里的稳妥组合。