本文已参与[新人创作礼]活动,一起开启掘金创作之路 持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天.
关键字: Message Queue· 分布式事务 表单设计 Touch System 优惠券 消息队列 索引 SQL Table Design 存预热 过期券通知 触达系统
Scenario 场景
- 设计一个优惠券系统
- Coupon System
- 优惠券的种类
- 优惠券系统的核心流程
电商系统促销手段
- 优惠券
- 拼团
- 砍价
- 老带新
优惠券核心流程
发券
发券的方式:同步发送 异步发送
领券(有一些系统是没有用户主动领取的方式的)
谁能领:所有用户 领取上限:一个优惠券最多能领取多少张? 领取方式:用户主动领取 or 自动发放被动领取
注意:(有一些系统是没有用户主动领取的方式的)
用券
作用范围:商品、商户、类目 计算方式:是否互斥、是否达到门槛等
需求拆解
商家侧 1.创建优惠券- 这里还可以引申 票券模板,票券规则 2.发送优惠券
用户侧 1.领取优惠券 2.下单 3.使用优惠券 4.支付
难点:
- 券的分布式事务,使用券的过程会出现的分布式问题分析
- 如何防止超发
- 如何大批量给用户发圈
- 如何限制券的使用条件 -- 这里考验代码设计能力
- 如何防止用户重复领券
表单设计
1.券批次(券模板) 指一批优惠券的抽象、模板,包含优惠券的大部分属性。
例:商家侧创建了一批优惠券,共1000张,使用时间为202011-11 00:00:00 ~ 2020-11-11 23:59:59,规定只有化妆品类目商品才能使用,满100减50。
2.券 (注意:虚拟券绑定用户,实体券没有,还有,转赠票券) 发放到用户的一个实体,已与用户绑定。 例:将某批次的优惠券中的一张发送给某个用户,此时优惠券属于用户。
3.规则 优惠券的使用有规则和条件限制,比如满100减50券,需要达到门槛金额 100元才能使用。
如何给大量用户发券?
异步发送
优惠券系统 — 触达系统
触达系统:短信,邮件,站内信,公众号,微信卡包
如果系统的用户数量增加到上万人呢?
这样发一条站内信,就要重复插入上万条数据。而且这上万条数据的 content 是一样的,假设一条站内信占 100 个字节,发一次站内信就要消耗十几 MB。因此我们可以将原来的表拆成两个表。
信息表 message Table 信息内容表 message_content Table
如果系统的用户数量增加到上K万人呢?
• 这里会有非活跃用户的问题,假设注册用户一千万,其中活 跃用户只占其中的20%。
• 如果采用上面拆分成两个表的情况,发一封“站内信”,那 得执行一千万个插入操作。可能剩下的80%用户基本都不会 再登录了,我们只需要对其中20%的用户插入数据即可。
系统侧
发站内信的时候,只在 message_content 插入站内信的主体内容,message 里不插入记录。
用户侧
用户登录后,首先查询 message_content 中的那些没有在 message 中有记录的数据,表示是未读的站内信。在查阅站内信的内容时,再将相关的记录插入到 message 中。
问题 高并发导致数据库崩溃
措施:缓存预热
问题 超高并发导致缓存放行量大使数据库崩溃
措施:消息队列异步处理
如何防止用户重复领取或多领?
- 在领券前先查缓存
- 领券后更新缓存
- 领券
Redis数据校验 语法:SADD KEY VALUE1......VALUEN 作用:将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略。 实例:SADD batch_id:1111:user_id 1001
TCC是Try-Confirm-Cancel的简称,是目前分布式事务主流解决方案。
TCC 实现阶段一:Try 对资源进行冻结,预留业务资源 在创建订单时,将优惠券状态改为 “冻结”
TCC 实现阶段二:Confirm 确认执行业务操作,做真正的提交,将第一步Try中冻结的资源,真正扣减 在订单支付成功,将优惠券状态改为 “已使用”
TCC 实现阶段三:Cancel 取消执行业务操作,取消Try阶段预留的业务资源 在支付失败/超时,或者订单关闭情况,将优惠券状态改为 “未使用”
Scale 扩展
快过期券提醒 数据库层面优化 发券接口限流保护
快过期券提醒
定时 扫 描 券 表 缺点:扫描的数据量太大,随着历史数据越来 越多,会影响线上主业务,会出现慢 SQL。
延 时 消 息 缺点:有些券的有效时间太长了(30天)以上,有可能造成大量 MQ 积压
新 增 通知 表 优点:扫描的数据量小,效率高 删除无用的已通知的数据记录
通知信息表 Notify_msg Table 1.在创建优惠券的时候就将需要提醒的记录插入提醒表中notify_msg 2.把用户ID+批次ID+通知日期 作为一个唯一索引,防止同一个批次有重复的记录通知,保证每天只会被通知一次 3.建立notify_time,通知时间索引,每日的通知扫描通过该索引列查询,通过索引列来提高查询的效率 4.通知完成后该表中的数据变失去了意义,通过定时任务将该数据删除