火车票秒杀系统如何在高并发情况下提供正常、稳定的服务呢?
我们知道用户秒杀流量通过层层的负载均衡,均匀到了不同的服务器上,即使如此,集群中的单机所承受的QPS也是非常高的。如何将单机性能优化到极致呢?要解决这个问题,我们就要想明白一件事: 通常订票系统要处理生成订单、减扣库存、用户支付这三个基本的阶段,我们系统要做的事情是要保证火车票订单不超卖、不少卖,每张售卖的车票都必须支付才有效,还要保证系统承受极高的并发。这三个阶段的先后顺序改怎么分配才更加合理呢?我们来分析一下:
当用户并发请求到达服务端时,首先创建订单,然后扣除库存,等待用户支付。这种顺序是我们一般人首先会想到的解决方案,这种情况下也能保证订单不会超卖,因为创建订单之后就会减库存,这是一个原子操作。但是这样也会产生一些问题,第一就是在极限并发情况下,任何一个内存操作的细节都至关影响性能,尤其像创建订单这种逻辑,一般都需要存储到磁盘数据库的,对数据库的压力是可想而知的;第二是如果用户存在恶意下单的情况,只下单不支付这样库存就会变少,会少卖很多订单,虽然服务端可以限制IP和用户的购买订单数量,这也不算是一个好方法。
如果等待用户支付了订单在减库存,第一感觉就是不会少卖。但是这是并发架构的大忌,因为在极限并发情况下,用户可能会创建很多订单,当库存减为零的时候很多用户发现抢到的订单支付不了了,这也就是所谓的“超卖”。也不能避免并发操作数据库磁盘IO
从上边两种方案的考虑,我们可以得出结论:只要创建订单,就要频繁操作数据库IO。那么有没有一种不需要直接操作数据库IO的方案呢,这就是预扣库存。先扣除了库存,保证不超卖,然后异步生成用户订单,这样响应给用户的速度就会快很多;那么怎么保证不少卖呢?用户拿到了订单,不支付怎么办?我们都知道现在订单都有有效期,比如说用户五分钟内不支付,订单就失效了,订单一旦失效,就会加入新的库存,这也是现在很多网上零售企业保证商品不少卖采用的方案。订单的生成是异步的,一般都会放到MQ、kafka这样的即时消费队列中处理,订单量比较少的情况下,生成订单非常快,用户几乎不用排队。
开启服务,我们使用ab压测工具进行测试:
Server Software:
Server Hostname: 127.0.0.1
Server Port: 5555
Document Path: /grabTicket
Document Length: 26 bytes
Concurrency Level: 200
Time taken for tests: 21.156 seconds
Complete requests: 10000
Failed requests: 5000
(Connect: 0, Receive: 0, Length: 5000, Exceptions: 0)
Total transferred: 1475000 bytes
HTML transferred: 395000 bytes
Requests per second: 472.68 [#/sec] (mean)
Time per request: 423.119 [ms] (mean)
Time per request: 2.116 [ms] (mean, across all concurrent requests)
Transfer rate: 68.09 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.5 0 5
Processing: 293 413 30.9 418 516
Waiting: 10 409 44.2 418 515
Total: 293 414 30.9 419 517
Percentage of the requests served within a certain time (ms)
50% 419
66% 422
75% 424
80% 426
90% 430
95% 435
98% 465
99% 500
100% 517 (longest request)