秒杀系统要解决什么
- 削峰填谷,将大批量的请求分批流向DB,避免DB被冲垮
- 兼顾性能的同时,保证下单时查验库存与扣减库存的原子性,避免超卖少卖问题
- 隔离资源,限流,保护除了秒杀系统外的其他系统正常运行。
秒杀系统怎么设计
前期准备
- 前端防刷。减少用户频繁点击带来的不必要流量。
- 网关限流。根据IP进行限流,阻挡黄牛和恶意用户。
技术选型
- 消息中间件,主要用于削峰填谷,主流的kafka、rabbitmq、rocketmq皆可。我这边选我自己比较熟悉的rabbitmq,实测单机可达10000TPS,可满足绝大多少场景。
- 库存采用Redis,redis本身对请求保证原子性,利用lua脚本可以同时进行校验库存和扣减库存两步操作
- 数据库依旧使用Mysql,做好分库分表提高写入速率。可以按用户纬度进行分库,将写入的负责分摊到不同数据库中。
流程设计
主要由以下三步构成:
- 秒杀参与接口。
检验活动、用户限购、库存之后,将请求打包、生成唯一的traceId,然后放入MQ中。返回traceId给客户端 - 订单消费者。
从MQ中消费秒杀下单请求,利用redis+lua保证原子性,同时原子的执行校验库存、扣减库存后,完成下单,并在redis中缓存下单结果 - 订单轮询接口。
客户端轮询获取下单结果。
问题1:
如何保证不超卖?
利用redis+lua保证原子性,在下单前,原子性的完成库存校验和扣减。
问题2:
如何保证不少卖?
利用两阶段提交。具体流程如下
- begin。在校验和扣减库存的lua脚本中,利用zset 记录一行数据 (value=订单号,score=当前时间+5分钟)。
- commit。下单成功后执行,lua脚本实现,删除zset中 (value=订单号,score=当前时间+5分钟)这个行数据
- rollback。下单失败执行,lua脚本实现,删除zset中 (value=订单号,score=当前时间+5分钟)这个行数据,并且补回库存。
- 在定时器中,利用zrangebyscore取出订单号,检查是否下单成功,下单成功则删除改行,下单失败则补回库存后删除该行。
魏王待续