博客参考:
12306 支撑百万 qps的核心秘籍
如何在 100 万人同时抢 1 万张火车票时,系统提供正常、稳定的服务。
负载均衡(3层):
1 OSPF(开放式最短链路优先)
2 LVS (Linux Virtual Server)
3 Nginx
(生成订单、减扣库存)原子操作、用户支付。用户五分钟内不支付,订单就失效了,订单一旦失效,就会加入新的库存。
订单的生成是异步的,一般都会放到 MQ、Kafka这样的即时消费队列中处理。
为了保证扣库存和生成订单的原子性,需要采用事务处理,然后取库存判断、减库存,最后提交事务,整个流程有很多IO,对数据库的操作又是阻塞的。
这种方式根本不适合高并发的秒杀系统。接下来我们对单机扣库存的方案做优化:本地扣库存。
我们把一定的库存量分配到本地机器,直接在内存中减库存,然后按照之前的逻辑异步创建订单。
这样就避免了对数据库频繁的IO操作,只在内存中做运算,极大的提高了单机抗并发的能力。但是百万的用户请求量单机是无论如何也抗不住的。
Linux 系统下,一切资源皆文件,网络请求也是这样,大量的文件描述符会使操作系统瞬间失去响应。
Nginx的加权均衡策略,我们不妨假设将 100W 的用户请求量平均均衡到 100 台服务器上,这样单机所承受的并发量就小了很多。然后我们每台机器本地库存 100 张火车票,100 台服务器上的总库存还是 1 万。
问题接踵而至,在高并发情况下,现在我们还无法保证系统的高可用,假如这 100 台服务器上有两三台机器因为扛不住并发的流量或者其他的原因宕机了。那么这些服务器上的订单就卖不出去了,这就造成了订单的少卖。
要解决这个问题,我们需要对总订单量做统一的管理,这就是接下来的容错方案。服务器不仅要在本地减库存,另外要远程统一减库存。
有了远程统一减库存的操作,我们就可以根据机器负载情况,为每台机器分配一些多余的Buffer库存用来防止机器中有机器宕机的情况。
我们采用 Redis 存储统一库存,因为 Redis 的性能非常高,号称单机 QPS 能抗 10W 的并发。
在本地减库存以后,如果本地有订单,我们再去请求 Redis 远程减库存,本地减库存和远程减库存都成功了,才返回给用户抢票成功的提示,这样也能有效的保证订单不会超卖。
redis只会被扣除1w次,因为只有1w张票。相当于查从redis迁移到了内存,只有改才会去访问redis。
当机器中有机器宕机时,因为每个机器上有预留的Buffer余票,所以宕机机器上的余票依然能够在其他机器上得到弥补,保证了不少卖。
Buffer余票设置多少合适呢,理论上Buffer设置的越多,系统容忍宕机的机器数量就越多,但是Buffer设置的太大也会对Redis 造成一定的影响。
虽然Redis内存数据库抗并发能力非常高,请求依然会走一次网络IO,其实抢票过程中对Redis的请求次数是本地库存和Buffer 库存的总量(设置多了的话就会多请求redis,比如本来10w张票,应该只请求10w次redis的,结果请求了15w次)。
因为当本地库存不足时,系统直接返回用户“已售罄”的信息提示,就不会再走统一扣库存的逻辑。
这在一定程度上也避免了巨大的网络请求量把Redis压跨,所以Buffer值设置多少,需要架构师对系统的负载能力做认真的考量。