秒杀业务是电商平台最核心的业务,像我们耳熟能详的双十一、双十二等等,也是商业活动中最重要的促销手段
一、面临挑战
1、热销商品的查询并发量大,用户往往刷不出相应热销商品
热销商品的查询并发量大,给数据库和服务器造成巨大压力,使得服务器的cpu和内存的监控不断报警。而且用户在手机端刷新不出相应的热销商品,给用户造成极差的体验,往往招致用户投诉
2、确保高并发环境下,热销商品不能被超卖。
电商平台对某些热销商品的限购非常严格,不仅对库存有严格的限制,同时对每个用户在设定期限内的购买数量也有严格的限制。在高并发的环境下,怎么保证复杂业务的原子性,既能控制住每个人的购买数量?同时又能控制住整体的库存?而且还能保证下单的效率?
3、瞬间产生大量订单,如果没有缓冲,服务器会宕机
秒杀过程中,会瞬间产生大量订单,由于产生订单的时间远远低于订单写入数据库的时间,这使得大量的订单滞留在服务器的资源中无法及时消费,一旦服务器资源被耗尽,就会面临宕机。
4、产生大量订单后,数据库不但面临着大量的写入,同时还要面临大量的读取,造成死锁,使用户感觉系统非常不流畅,影响用户体验
5、当系统资源无法满足当前流量时,怎样自动在新的服务器资源中拉起相应的秒杀服务,来保证当前的业务顺利进行。当秒杀结束后,又怎样自动释放相应的资源?
二、解决方案
面临以上的挑战和用户的痛点,怎么破?
1、引入缓存,让查询热销商品的速度飞起来
<1>、限制用户查询的频率
比如3秒之内只能刷新一次,这样可以减轻服务器的压力
<2>、本地缓存和分布式缓存相结合(guava+redis)
- 一般情况,使用分布式缓存redis就可以大大商品的查询速度
- 如果并发量非常大,分布式缓存中的某些Key对应的value存储在集群中一台机器(比如冬奥会时的冰墩墩),使得所有流量涌向同⼀机器,成为系统的瓶颈,无法通过增加机器容量来解决
- 引入本地缓存,例如guava,对于每次读请求,将⾸先检查key是否存在于本地缓存中,如果存在则直接返回,如果不存在再去访问分布式缓存的机器
<3>、变化的热销商品信息实时同步到缓存中(canal+rocketmq+dxs+redis)
热销商品的信息有时会发生变化,比如价格,描述信息、图片链接等。而且这些变化,都是存储到数据库。使用canal监控数据库的变化,数据库变化后将变化的消息写入到消息队列ons,dxs通过监听ons,实时将变化的信息同步到缓存中
2、高效下单,同时保证限购规则和防止口罩被超卖
<1>、redis+lua,保证每个用户所有操作的原子性
高并发的情况下,保证每个用户在特定时间内只能购买限购数量的热销商品,而且整体不能被超卖。 这就需要保证每个人所有操作的原子性。使用redis+lua这种分布锁的方式,不仅能保证每个用的所有操作的原子性,并且能大大降低相应的网络开销,节省服务器资源
<2>、高效生成订单的揭秘
- 限购规则生效时,实时同步到redis,lua脚本直接从redis取出相应规则(限购数量、限购天数、整体库存等),效率更高
- 每个人的购买明细(购买日期、已购买数量)也直接写入到redis中,lua脚本会结合限购规则和用户的明细判断该用户是否可以继续购买
- 每个门店整体的库存也直接从redis里判断,lua脚本会根据是否有足够的库存而控制用户是否可以下单
- 由于所有相关操作都是从redis中读取和写入,而非关系型数据库,所有生成订单的效率非常高
3、面对大量订单瞬间生成,使用分库分表和消息队列来保证订单安全高效的写入数据库
<1>、生成订单和存储订单的不平衡
由于订单是通过redis高效生成,生成订单后,需要写入mysql数据库。生成订单的速度远远高于存储订单的速度,如果短时间内大量的订单无法消费积压在服务器内存中,在服务器资源耗尽的情况下,出现服务器宕机
<2>、使用分库分表的方式,提升数据库的并发访问能力
<3>、使用消息队列rabbitmq列作为缓冲,来解决生成订单和存储订单的不平衡
消息队列rabbitmq有两个作用:一是解耦,生成订单后,不需要直接写入mysql,而是发送到rabbitmq,从而完成订单的高效生成。二是削峰,当大量订单瞬间生成的时候,就不会积压在服务器的内存中,从而保证服务器不会被宕机,可以根据消息队列的积压程度,动态调整消息队列消费者的个数,提升消费的效率
4、订单的高效读写
<1>、读写分离+SLB实现高效读写
可以通过一主多从(比如一主六从)实现读写分离,主库只负责将数据写入数据库,同时将数据同步到其他的从库中。而从库只负责读取,同时在所有的从库中引入SLB,通过负载均衡的方式分摊从库读取的压力
<2>、NOSQL冗余,用空间换时间(canal+rocketmq+dxs+es)
使用canal监听主库的数据变化,有新的订单写入,将新订单的日志写入到消息队列ons中,dxs通过监听ons,实时将新的订单写入到es中,用户可以通过查询es查到自己的订单
5、未来,系统可以根据设定指标在集群中自动启动/关闭秒杀服务,增加自身的并发能力,应对突如其来的流量
<1>、使用docker管理秒杀服务,使秒杀服务容器化
<2>、使用容器编排管理平台Rancher管理秒杀容器,同时配置容器扩容和缩容的回调服务
<3>、自动扩容和缩容
当突发流量可以快速扩容,当流量峰值过去可以快速缩容,有两个方法:
- 使用flink监控nginx访问日志的方式,实时监控流量的变化。当流量达到某个峰值时,调相应的容器扩容服务。同时,当流量过后,启动相应的缩容服务
- 使用prometheus实时监控服务器对应的指标。当指标触发报警时,调相应的容器扩容服务。同时当指标恢复时,启动相应的缩容服务
以上就是实现秒杀的整个底层逻辑,这套方案经历了实践的考验。现在,也是用这套方案来抢购很多促销商品。当然还有很多的不足,而且业务的要求也越来越高,相信这套方案会在不断升级中越做越好