得物二面:500个人抢一瓶茅台,两台服务器(300+200请求),DB只能顶10并发,秒杀系统怎么做?
如何用信号量来搞定你的秒杀系统!
整体系统方案设计的流程图如下:
在这里为什么要用信号量呢?因为要控制数据库并发访问
为什么每台服务器2个信号量? 因为总共4个线程访问数据库,不超过负载10
最终只有1个人能抢到茅台,可以通过数据库原子操作去保证
接下来我们看看怎么去落地?
首先先设计一下数据库,简单一点,一张库存表,一张订单表:
- stock字段记录库存,这里是1瓶茅台
- version字段实现乐观锁,防止并发问题
- 唯一索引防止重复下单
- updateStock是核心SQL,AND stock > 0 确保不会超卖
- 使用乐观锁version字段防止并发问题
- 返回的影响行数来判断是否抢购成功
接下来实现请求分发逻辑,
使用AtomicInteger保证线程安全的计数
3:2的比例分配符合题目要求两台服务器300:200的负载能力
实际生产环境可以使用Nginx等专业负载均衡器
接着我们先定义真正的秒杀接口,这里注意一定要进行风控或者参数校验:
来看秒杀的主流程:
- semaphore.tryAcquire()非阻塞获取信号量,避免用户长时间等待
- 只有获得信号量的线程才能访问数据库,控制并发
- 异常处理要完善,包括重复下单、库存不足等情况
- 日志记录足够详细,便于问题排查
- 后期可以考虑监控告警
只有拿到信号量的请求才能执行具体的秒杀逻辑,如下:
先进行库存的扣减,利用乐观锁去保证不会超卖
只有库存扣减成功的才会去创建订单
同时利用数据库的唯一约束异常的捕获来记录用户是否参与过此次秒杀活动
接下来我们编写测试类,模拟500个线程来进行并发访问,我们来查看控制台输出:
接着我们去看数据库的库存情况,可以看到没有发生超卖,订单也创建成功了
考虑到后期扩容的话或者想动态调整阈值,下面这些值都可以放在nacos或者数据库做动态刷新: