Scenerio:
fuctional requirements:
- generate valid coupons
- users can only get one coupon per day
- validate coupon
non-functional requirements: availability, latency, reliability, ACID
- low latency
- high availability
使用次数?多次重复使用还是一次性
使用时间?永久还是活动期间
减价多少?固定还是百分比
先决条件?满多少减还是直接减,要有会员或者subscription
如何拿到?注册还是直接给
核心流程
发券:同步or 异步发送,减价方式
领券:谁能领,领取上限,领取方式
用券:作用范围,计算方式,使用时间,使用次数
synchronous
client: make request, waiting for response, response from server
asynchronous
client: make request, continuing working, get response from server and do something
商家侧
创建优惠券
发送优惠券
用户侧
领取优惠券
下单
使用优惠券
支付
Service:
coupon生成系统
client -> coupon service -> coupon db
coupon使用系统
gateway
payment service order service coupon service notification service
db db db
Storage:
券批次表 coupon_batch table
batch_id INTEGER 1111
batch_name VARCHAR “黑五减价”
coupon_name VARCHAR “prime会员5元代金券“
rule_id INTEGER 1010
total_count INTEGER 10000
assigned_count
used_account
rule table
rule_id INTEGER 1010
name VARCHAR "满减规则“
type INTEGER 0 0满减 1 折扣
rule_content BLOB
{
threshold: 5.01 //使用门槛
amount: 5 // 优惠金额
use_range:3 // 使用范围,0-全场,1-商家,2-类别,3-商品
commodity_id:10 //商品id
is_mutex: true //是否互斥
received_started_at:2020-11-1 00:08:00 // 领取开始时间
received_ended_at:2020-11-6 00:08:00 // 领取结束时间
use_started_at:2020-11-1 00:00:00 // 使用开始时间
use_ ended_at:2020-11-11 11:59:59 // 使用结束时间
}
coupon table
coupon_id INTEGER 66889
user_id INTEGER 1001
batch_id INTEGER 1111
status INTEGER 1 // 0-未使用,1-已使用,2-已过期,3-冻结
order_id VARCHAR 13234242
received_time DATETIME //领取时间
validate_time
used_time
异步
商家 -发券请求->管理服务器->-发送发券消息->消息中间件-消费发券消息->服务器-coupon在用户优惠券表中插入数据->数据库
| <-------插入成功-----------
触达系统 -消息推送->客户端
kafka多个partition, 10w messages/100 partition = 1k 每个queue有1k message
100 consumer
重复消费的问题?一个message分给两个consumer
运营提供满足条件的用户文件,上传到发券管理后台并选择要发送的优惠券。
管理服务器根据用户id,券批次id生成消息,发送到消息中间件中。
优惠券服务器消费信息
INSERT INTO coupon(user_id, coupon_id, batch_id) VALUES(1001,66889,111);
UPDATE coupon_batch set total_count = total_count - 1, assign_count = assign_count + 1
WHERE batch_id = 1111 AND total_count > 0
用事务:
悲观锁:select.. for update数据库自动上锁,commit/roll back时候才会被释放。perf不好
乐观锁:用version
UPDATE coupon_batch set total_count = total_count - 1, assign_count = assign_count + 1, version = version + 1
WHERE batch_id = 1111 AND total_count > 0
领券
商家-发布券->管理服务器-插入券批次表->数据库<-插入优惠券表-优惠券服务器<-领券请求-客户端
<--插入成功-- --插入成功--->
1. 校验优惠券余量
2. 新增优惠券用户表,扣减余量
用户领券出现秒杀情况:
问题:高并发导致数据库崩溃,措施:缓存预热,问题:超高并发导致缓存放行量大使数据库崩溃,措施:消息队列异步处理
如何防止用户重复领取或多领? redis
- 在领券前先查缓存 SISMEMBER batch_id:1111:user_id 1001 判断成员元素是否是集合的成员。
- 领券
- 领券后更新缓存 SAAD batch_id:1111:user_id 1001 将一个或多个成员元素加入到集合中,已经存在于集合到成员元素将被忽略。
用券
确认订单页,对优惠券进行校验
- 判断是否过期
- 判断使用范围
- 判断是否达到门槛
- 判断是否互斥
客户端 ---查询券请求--->优惠券服务器------查询表----->数据库
<-返回是否可用券- <-返回优惠券规则---
阶段 系统
确认订单 券系统
提交订单 券系统,订单系统
支付订单 券系统,订单系统,支付系统
同时操作多个系统,如何保障一致性?coordinator
coupon_opt_record table 优惠券操作记录表
user_id
coupon_id
operating 0-锁定,1-核销,2-解锁
operated_at
TCC try-confirm-cancel分布式事务主流解决方案
try:将资源冻结。创建订单,状态冻结
confirm:确认执行业务操作,做将冻结的资源,真正扣减。订单支付成功,状态已使用。
cancel: 取消执行业务草错,取消try预留的业务资源。支付失败/超时,或者订单关闭,状态未使用。