转载

新商城秒杀系统设计

上次跟大家聊到了商城秒杀系统遇到的问题。后来我的同事 tt 主持设计并实现了新的秒杀系统,用于服务接下来的秒杀活动。新的秒杀系统采用了之前说的异步提交设计,然后在系统的扩展性和抗压性上都做了比较充分的考虑和设计。在双十二的时候,整套系统轻松的应对了整点瞬时高压。这周我找 tt 约了个稿子,给大家聊聊新的秒杀系统的设计。

来,跟老王一起搬板凳听 tt 同学讲讲秒杀系统吧 ~

==== 开始的分割线 ====

秒杀系统的特点:

1 、大量请求同时发起 ( 高并发 ) ,服务器必须快速处理每一个请求,才能保证秒杀请求不超时;

2 、秒杀商品不能超卖,比如某个秒杀商品的数量是 100 个,如果不小心被秒了 1000 个商品出去,程序员的工资都不够赔啊。为了保证不超卖,必须有一个统一的地方排队减库存,比如通过通过数据库事务操作减库存;

3 、秒杀开始的一段时间,服务器请求压力突增会影响商城正常售卖处理。要解决高并发,可以增加机器进行负载分担,但是减库存是数据库单点操作,请求都会卡在数据库减库存操作上,最终导致所有请求处理变慢。

解决方案:

1 、解决高并发的请求接入问题:

通过 http 反向代理 ( 比如 nginx) 进行负载分担,并增加后端服务器的数量;

2 、解决数据库减库存阻塞请求的问题:

增加一个消息队列 (MQ) 服务,将秒杀请求先放到队列里,后端服务再慢慢的处理队列中的秒杀消息,并把抢购成功的请求 ( 队列最前面请求 ) 保存在数据库中备查。 MQ 就像一个巨大的蓄水池,将大量的并发请求先缓存下来,并且基本保持了请求的先后顺序。

而且 MQ 中的秒杀请求可以做优化处理,比如,当抢购商品的库存减为 0 的时候, MQ 中待处理的请求就可以简单的做失败处理,不需要再去数据库中做库存竞争了,数据库的处理压力只和抢购商品数量有关,抢购用户数量的增加并不会带来数据库压力的增加。

引入 MQ 之后用户的秒杀请求变成了异步操作,因此,需要客户端在发送秒杀请求后,再延迟发送结果查询请求,查询到秒杀成功的用户再跳转到购买链接完成购买操作,相当于秒杀的对象是一个购买机会,简化秒杀请求的处理过程。

3 、解决秒杀对商城的冲击将秒杀请求的处理服务和商城正常售卖服务进行物理隔离,增加新的三级域名隔离秒杀请求,就算秒杀系统过载,也不会影响到其他商品的正常售卖。 看一下秒杀系统的设计:

新商城秒杀系统设计

上图的“中间等待结果” 取值和意义如下:

DIRECT_FAILED :直接失败,如:验证失败 ( 用户 token ) ,未能进入 MQ 等;

IN_QUEUE :已经加入消息队列,客户端需要延迟查询秒杀结果。

秒杀结果 取值和意义如下:

DIRECT_FAILED :基本验证失败;

IN_QUEUE :该用户的秒杀请求仍然在队列中,还没有处理完成;

BUY_FAILED :库存竞争失败,没有秒到;

SUCCESS :秒杀成功,继续进行下单和付款操作。

设计解析:

1 秒杀服务器和售卖服务器部署上进行物理隔离,秒杀压力不会影响商城正常的 售卖服务;

2 在秒杀请求入队列之前,通过当前秒杀服务器记录的库存数据,对大量后到达 的请求直接返回秒杀失败,降低进入 MQ 的请求数量。

服务器本地记录的商品库存信息不能和数据库进行实时同步 ( 会处理影响性 ) ,所以不能判断当前实际库存是否大于 0 ,但是可以根据已经进入 MQ 的请求数量,判断后来的请求是否一定没有库存了,这里是一个判伪处理;

3 请求队列处理服务器可以通过适量的并发,在数据库中完成库存竞争并记录秒 杀成功的用户;

4 秒杀请求返回结果为 IN_QUEUE 的用户,延迟几秒后发起结果查询请求,这个查询已经没有并发压力了。秒杀结果的判定过程如下:

1) 数据库中有该用户的秒杀成功记录,则判定为秒杀成功。

2) 如果秒杀商品库存为 0 ,且数据库中还没有该用户的成功记录,则判定为秒杀失败。

3) 如果秒杀商品库存大于 0 ,且数据库中还没有该用户的成功记录,则返回 IN_QUEUE ,客户端稍后查询结果;

5 秒杀结果为成功的用户,跳转到售卖服务器,下单并支付完成购买流程。这个时候已经没有并发压力了;

6 售卖服务器会验证改用户是否抢购成功,防止作弊。

综上,通过 提前判伪,消息队列,延迟查询,结果验真 将并发压力拦截并稀释 掉并保证了结果的正确性。

秒杀系统实现过程中的其他优化措施:

1 在高并发处理过程中,尤其要注意锁的应用,尽量避免锁等待导致的线程切换。根据应用的场景,可以选择原子变量 ( 如计数器 ) ,读写锁,细粒度锁;

2 需要集中式验证的处理,比如验证当前库存是否大于零,可以通过本地缓存 的库存进行验伪;

3 应用本地缓存减少对外部系统 ( 数据库等 ) 的重复调用,比如 用户 token 合法性的验证,可以在秒杀开始之前通过 Ajax 发送预热请求,提前缓存好用 token 的验证结果,结合到负载分担服务器的会话保持,可以很大提高本地缓存的命中率。

==== 结束的分割线 ====

今天感谢 tt 同学的分享,如果大家有好的想法或者文章,都可以找老王哈 ~~

新商城秒杀系统设计

原文  http://mp.weixin.qq.com/s/8RhRZzKjUdQfh7lw95Csug
正文到此结束
Loading...