通常订票系统要处理生成订单、减扣库存、用户支付这三个基本的阶段,我们系统要做的事情是要保证火车票订单不超卖、不少卖,每张售卖的车票都必须支付才有效,还要保证系统承受极高的并发 。这三个阶段的先后顺序改怎么分配才更加合理呢?我们来分析一下:
2.1 下单减库存
文章插图
当用户并发请求到达服务端时,首先创建订单,然后扣除库存,等待用户支付 。这种顺序是我们一般人首先会想到的解决方案,这种情况下也能保证订单不会超卖,因为创建订单之后就会减库存,这是一个原子操作 。
但是这样也会产生一些问题 。
第一就是在极限并发情况下,任何一个内存操作的细节都至关影响性能,尤其像创建订单这种逻辑,一般都需要存储到磁盘数据库的,对数据库的压力是可想而知的;
第二是如果用户存在恶意下单的情况,只下单不支付这样库存就会变少,会少卖很多订单,虽然服务端可以限制IP和用户的购买订单数量,这也不算是一个好方法 。
2.2 支付减库存
文章插图
如果等待用户支付了订单在减库存,第一感觉就是不会少卖 。但是这是并发架构的大忌,因为在极限并发情况下,用户可能会创建很多订单,当库存减为零的时候很多用户发现抢到的订单支付不了了,这也就是所谓的“超卖” 。也不能避免并发操作数据库磁盘IO
2.3 预扣库存
文章插图
从上边两种方案的考虑,我们可以得出结论:只要创建订单,就要频繁操作数据库IO 。那么有没有一种不需要直接操作数据库IO的方案呢,这就是预扣库存 。先扣除了库存,保证不超卖,然后异步生成用户订单,这样响应给用户的速度就会快很多;那么怎么保证不少卖呢?
用户拿到了订单,不支付怎么办?我们都知道现在订单都有有效期,比如说用户五分钟内不支付,订单就失效了,订单一旦失效,就会加入新的库存,这也是现在很多网上零售企业保证商品不少卖采用的方案 。订单的生成是异步的,一般都会放到MQ、kafka这样的即时消费队列中处理,订单量比较少的情况下,生成订单非常快,用户几乎不用排队 。
3. 扣库存的艺术从上面的分析可知,显然预扣库存的方案最合理 。我们进一步分析扣库存的细节,这里还有很大的优化空间,库存存在哪里?怎样保证高并发下,正确的扣库存,还能快速的响应用户请求?
在单机低并发情况下,我们实现扣库存通常是这样的:
文章插图
为了保证扣库存和生成订单的原子性,需要采用事务处理,然后取库存判断、减库存,最后提交事务,整个流程有很多IO,对数据库的操作又是阻塞的 。这种方式根本不适合高并发的秒杀系统 。
接下来我们对单机扣库存的方案做优化:本地扣库存 。我们把一定的库存量分配到本地机器,直接在内存中减库存,然后按照之前的逻辑异步创建订单 。改进过之后的单机系统是这样的:
文章插图
这样就避免了对数据库频繁的IO操作,只在内存中做运算,极大的提高了单机抗并发的能力 。但是百万的用户请求量单机是无论如何也抗不住的,虽然nginx处理网络请求使用epoll模型,c10k的问题在业界早已得到了解决 。
但是linux系统下,一切资源皆文件,网络请求也是这样,大量的文件描述符会使操作系统瞬间失去响应 。
上面我们提到了nginx的加权均衡策略,我们不妨假设将100W的用户请求量平均均衡到100台服务器上,这样单机所承受的并发量就小了很多 。然后我们每台机器本地库存100张火车票,100台服务器上的总库存还是1万,这样保证了库存订单不超卖,下面是我们描述的集群架构:
文章插图
问题接踵而至,在高并发情况下,现在我们还无法保证系统的高可用,假如这100台服务器上有两三台机器因为扛不住并发的流量或者其他的原因宕机了 。那么这些服务器上的订单就卖不出去了,这就造成了订单的少卖 。
要解决这个问题,我们需要对总订单量做统一的管理,这就是接下来的容错方案 。服务器不仅要在本地减库存,另外要远程统一减库存 。有了远程统一减库存的操作,我们就可以根据机器负载情况,为每台机器分配一些多余的“buffer库存”用来防止机器中有机器宕机的情况 。
推荐阅读
- 八角亭amp,八角亭天马小青柑检验报告公示
- 并发编程之定时任务&定时线程池原理解析
- 2021年12306网上购票时间调整 2021年12306售票时间
- 轻松上手 Spring Boot & Kafka 实战
- MP3|生于Win95时代 Winamp播放器25周岁了:重生计划破灭
- 游艇|对标五菱凯捷 东风风行·游艇配置公布:丰富到逆天
- 12306|一号通用!12306上新了:支持购买20省份汽车票
- 电影|著名导演黄蜀芹在沪去世、法国影人雅克·贝汉去世:影迷惋惜 一路走好
- 生科医学|癌症是"省"出来的!医生道破真相 这坏习惯一定要改
- Shell 脚本中 3>&1 1>&2 2>&3 的含义