典型回答 这是一个典型的方案设计类的题目,题目的难点和隐藏的考点有这么几个:
1、百万级数据量
如何快速查询出要到期的数据 如何高效的针对大批量用户做推送 2、快到期的会员
如何知道哪些用户快到期了 3、消息提醒
用具体什么样的提醒方式 如何避免一个用户被频繁提醒 逐一拆解一下这几个问题吧。
首先可以明确的是,**百万级会员,这个量级不算大!!!**才百万级,根本不需要上分库分表、读写分离,直接单表就能抗,稍微有点索引就能扛得住了。
如果你查GPT或者DeepSeek,他会告诉你百万级太大了,需要做分库分表了,甚至有的还告诉你用到期时间做个分区。。。这根本就不靠谱,这个数据量根本不需要,而且到期时间是可能会变的,用一个可变字段做分区,这不是坑人么。
还有的是说针对这些数据做冷热分离,将历史过期会员归档到独立表中,减少主表数据量。。。这也完全是过度设计,百万级的数据量,根本不需要做归档。
需不需要一张消息推送表 针对消息推送的这个场景,我们其实是需要一张表记录下所有的推送的(有的时候可以不建表,可以通过固定的格式打印日志,然后拉取日志之后解析日志。)。
有这样表的好处是可以知道具体的推送的情况,什么时间、用什么渠道、给谁做了推送,推送了什么内容,结果是什么,都比较清晰。
有这样表之后,还可以有一个好处就是可以做幂等控制、疲劳度控制、以及失败的重试、还有数据分析。可以参考以下设计:
字段名 类型 必填 默认值 说明 id BIGINT UNSIGNED 是 自增 主键,唯一标识 user_id BIGINT UNSIGNED 是 - 接收用户ID(与用户表关联) message_type VARCHAR(20) 是 - 消息类型(如:**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">到期提醒</font>**) channel VARCHAR(20) 是 - 推送渠道(如:**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">sms</font>**/**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">email</font>**/**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">app_push</font>**/**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">站内信</font>**) title VARCHAR(200) 否 NULL 消息标题(邮件主题、推送标题) content TEXT 是 - 消息内容(支持模板变量,如**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">{username}</font>**) status TINYINT 是 0 状态(**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">0=待发送</font>**/**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">1=已发送</font>**/**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">2=发送失败</font>**/**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">3=已重试</font>**) send_time DATETIME 否 NULL 实际发送时间 create_time DATETIME 是 NOW() 创建时间 update_time DATETIME 是 NOW() 更新时间 retry_count TINYINT UNSIGNED 是 0 重试次数(超过阈值后标记为失败) third_msg_id VARCHAR(100) 否 NULL 第三方平台消息ID(如短信服务商返回的ID,用于对账) error_info TEXT 否 NULL 错误详情 is_read TINYINT(1) 是 0 是否已读(仅对站内信有效,**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">0=未读</font>**/**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">1=已读</font>**) click_time DATETIME 否 NULL 用户点击时间(用于统计转化率) extra_info JSON 否 NULL 扩展字段(如:模板参数、业务上 如何识别即将到期的用户 首先,我们可以在会员表中增加一个到期时间的字段,如expire_time,然后针对这个字段建立一个索引。这样我们在查询的时候,根据 expire_time < now() 就能一次性的捞出所有已经过期的用户了,如果想要查询还有三天过期的用户,那么可以用以下SQL:
...