高并发
如果需要做一个秒杀系统,最重要的是要解决高并发问题。因为在那一刻肯定有很多顾客过来抢。
秒杀活动一定是高并发的,在那一秒,无数的用户抢购商品,如果我们每个请求过来,都要与数据库进行交互,那么MySQL一定是扛不住的,所以我们的秒杀要基于redis来进行。
Redis单机支撑每秒几万的写入。这样性能不是问题。而且Redis还可以做各种的水平扩展和集群支持。这样,性能就能完全满足我们的秒杀需要。
那么这种情况下,在我们发布秒杀活动的时候,要将我们的商品与库存写入redis。当用户进行抢购的同时,我们直接对redis的库存进行操作,例如扣减。
扣减redis内库存数量后,发送扣减成功的消息到消息队列,由后续的订单服务,支付服务之类的进行处理。最后再由mysql进行库存扣减。
这样的话,比如说一开始,我们有两万个用户来进行秒杀,但我们可能只有十个商品。那么其实最终和mysql进行交互的只有十个商品,大大减轻mysql的压力。那么这种是可以支持高并发的。
解决超卖
超卖的情况指的是如库存只有十个,但是卖了二十个这样的情况。
因为我们已经将库存名额加载到了Redis,那么我们一定要保证精确的计数。如果redis的库存数量错误,就会导致超卖的问题产生。
在秒杀的时候分为两步:
- 判断库存名额是否充足。
- 减少库存名额,扣减成功就是抢到。
假如说,这两个操作不是原子性,那么在高并发情况下可能就会出现问题。比如,第一步所有的人都过来发现库存名额都充足,然后去全部扣减,那么扣减的数额高于我们redis里面存的。
所以说,我们的关键是保证两种操作的原子性。每一个请求一过来,我们都使用lua脚本来进行支持,先判断库存名额是否充足,然后再进行减少。
一定保证两个请求是原子的,这样就能解决我们的超卖问题。
解决少卖
库存扣减成功,但是实际订单没有生成或者是其他的没有处理好的情况。那么就会引发出少卖问题。
这种情况就是扣减了库存,但是消息没发出,订单生成失败等各种各样的情况,导致我们没有卖成功。
我们扣减库存后,发送消息队列,这里要有重试策略,如果发送消息失败进行重试,超过重试次数后,则要持久化磁盘由补偿服务来进行扫描,进行后续业务处理。类似于mysql的日志持久化。
比如我们配置三次重试,那么就要把消息做一个持久化磁盘的操作,一定要把消息保存起来。
使用保存服务或定时服务等来进行扫描。扫描之后再进行补偿,再进行支付,再进行一个订单的处理,这种情况下就能实现少卖情况的处理。保证我们的用户抢到了,是一定可以正常处理的。
这种持久化的方式类似于mysql的日志持久化,可以类比。