前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

1、抢座

NBA 季后赛,去现场看球,要抢购球票,体育馆最多容纳 1 万人(1 万张球票)。

体育馆不同距离、不同位置的票,价格和优惠都不相同。有单人位、有双人位,也有 3、4 人位。你约着朋友共 10 个人去看球,要买票,要选位置。这时候抢票就会很尴尬,因为位置连着的可能会被别人抢走,同时买的票越多,与人冲突的概率就越大,会导致抢票特别困难。

同时,这个系统的开发也很头大,抢购(秒杀)的并发非常大,预计在开始的一秒钟会超过 10 万人同时进来,再加上刷票的机器人,接口请求量可能瞬间达到 100 万的 QPS。

1.1、异步实现

较简单的实现方式

1、所有的请求都异步执行,订单全部进入消息队列,下单马上响应处理中,请等待。

2、然后,后端程序再从消息队列中串行化处理每一个订单,把出现冲突的订单直接报错,这样,估计 1 秒钟可以处理 1000 个订单,10 秒钟可以处理 1 万个订单。

3、考虑订单的冲突问题,1 万张球票的 9000 张可能在 30 秒内卖出去,此时只处理了 3 万个订单,第一秒钟进来的 100 万订单已经在消息队列中堆积,又有 30 秒钟的新订单进来,需要很久才可以把剩下的 1000 张球票卖出去啊。同理,下单的用户需要等待太久才知道自己的订单结果,这个过程轮询的请求也会很多很多。

1.2、同步实现

换一种方案,不使用队列串行化处理订单,直接并发的处理每一个订单。那么处理流程中的数据都需要梳理清楚。 这种方案比队列的方案需要的服务器资源更多,但是用户的等待时间很短,体验就好很多

1、针对每一个用户的请求加锁,避免同一个用户的重入;

2、每一个/组座位预生成一个 key:0,默认 0 说明没有下单;

3、预估平均每一个订单包含 2 个/组座位,需要更新 2 个座位 key;

4、下单的时候给座位 key 执行 INCR key 数字递增操作,只有返回 1 的订单才是成功,其他都是失败;

5、如果同一个订单中的座位 key 有冲突的情况下,需要回滚成功 key(INCR key = 1)重置(SET key 0);

6、订单成功/失败,处理完成后,去掉用户的请求锁;

7、订单数据入库到 mysql (消息队列,避免 mysql 成为瓶颈);

综上,需要用到 1 个锁(2 次操作),平均 2 个座位 key(每个座位号 1-2 次操作),这里只有 2 个座位 key 可以并发更新。为了让 redis 不成为数据读写的瓶颈(超过 100w 的 QPS 写操作),不能使用单实例模式,而要使用 redis 集群,使用由 10-20 个 redis 实例组成的集群,来支持这么高的 redis 数据读写。

算上 redis 数据读写、参数、异常、逻辑处理,一个请求大概耗时 10ms 左右,单核至少可以支持 100 并发,由于这里有大量 IO 处理,后端服务可以支持的并发可以更高些,预计单核 200 并发,16 核就可以支持 3200 并发。总共需要支持 100 万并发,预计需要 312 台后端服务器。

1.3、思考

2.5 思考问题

实际情况会是怎样呢?会有 10 万人同时抢票吗?会有 100 万的超高并发吗?订票系统真的会准备 300 多台服务器来应对抢票吗?

ContactAuthor