浅谈秒杀系统
关于秒杀系统涉及到的东西挺多的,抽空把这个坑填上。。。电商公司使用大促销秒杀方案较多,其实秒杀系统不单单适用于电商抢购场景,涉及到大并发的场景都可以参考秒杀系统方案。
秒杀系统的基本流程
秒杀系统的难点分析
在秒杀场景中最大的问题在于容易产生大并发请求、产生超卖现象和性能问题,下面我们分析下这三个问题:
1)瞬时大并发:
一提到秒杀系统给人最深刻的印象是超大的瞬时并发,这时你可以联想到小米手机的抢购场景,在小米手机抢购的场景一般都会有10w+的用户同时访问一个商品页面去抢购手机,这就是一个典型的瞬时大并发,如果系统没有经过限流或者熔断处理,那么系统瞬间就会崩掉,就好像被DDos攻击一样;秒杀开始的那一瞬间,会有大量用户冲击进来,所以在开始时候会有一个瞬间流量峰值。如何把瞬间的流量峰值变得更平缓,是能否成功设计好秒杀系统的关键因素。实现流量削峰填谷,一般的采用缓存和 MQ 中间件来解决
2)超卖:
秒杀除了大并发这样的难点,还有一个所有电商都会遇到的痛,那就是超卖,电商搞大促最怕什么?最怕的就是超卖,产生超卖了以后会影响到用户体验,会导致订单系统、库存系统、供应链等等,产生的问题是一系列的连锁反应,所以电商都不希望超卖发生,但是在大并发的场景最容易发生的就是超卖,不同线程读取到的当前库存数据可能下个毫秒就被其他线程修改了,如果没有一定的锁库存机制那么库存数据必然出错,都不用上万并发,几十并发就可以导致商品超卖;
3)性能:
当遇到大并发和超卖问题后,必然会引出另一个问题,那就是性能问题,如何保证在大并发请求下,系统能够有好的性能,让用户能够有更好的体验,不然每个用户都等几十秒才能知道结果,那体验必然是很糟糕的;
秒杀系统方案
核心理念还是通过缓存、异步、限流来保证系统的高并发和高可用。
秒杀系统架构设计的要点:
1)对于大促时候的秒杀活动,一般运营会配置静态的活动页面,配置静态活动页面主要有两个目的一方面是为了便于在各种社交媒体分发,另一方面是因为秒杀活动页的流量是大促期间最大的,通过配置成静态页面可以将页面发布在公有云上动态的横向扩展;
2)秒杀活动开始前,其实就有很多用户访问该页面了。如果这个页面的一些资源,比如 CSS、JS、图片、商品详情等,都访问后端服务器,甚至 DB 的话,服务肯定会出现不可用的情况。所以一般我们会把这个页面整体进行静态化,并将页面静态化之后的页面分发到 CDN 边缘节点上,起到压力分散的作用。通过CDN节点的页面缓存来缓解访问压力和公司网络带宽。
【CND的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布更接近用户的网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。一般会缓存图片,css js甚至视频】
3)将活动H5页面部署在公有云的web server上,使用公有云最大的好处就是能够根据活动的火爆程度动态扩容而且成本较低,同时将访问压力隔离在公司系统外部;
4)在提供真正商品秒杀业务功能的app server上,需要进行交易限流、熔断控制,防止因为秒杀交易影响到其他正常服务的提供,我们在限流和熔断方面使用了hystrix,在核心交易的controller层通过hystrix进行交易并发限流控制,当交易流量超出我们设定的限流最大值时,会对新交易进行熔断处理固定返回静态失败报文。
5)服务降级处理,除了上面讲到的限流和熔断控制,我们还设定了降级开关,对于首页、购物车、订单查询、大数据等功能都会进行一定程度的服务降级,例如我们会对首页原先动态生成的大数据页面布局降级为所有人看到的是一样的页面、购物车也会降级为不在一级页面的tabbar上的购物车图标上显示商品数量、历史订单的查询也会提供时间周期较短的查询、大数据商品推荐也会提供一样的商品推荐,通过这样的降级处理能够很好的保证各个系统在大促期间能够正常的提供最基本的服务,保证用户能够正常下单完成付款。
6)上面介绍的都是如何保证能扛住高并发,下面介绍下整个方案中如何防止超卖现象的发生,我们日常的下单过程中防止超卖一般是通过在数据库上实施乐观锁来完成,使用乐观锁虽然比for update这种悲观锁方式性能要好很多,但是还是无法满足秒杀的上万并发需求,我们的方案其实也很简单实时库存的扣减在缓存中进行,异步扣减数据库中的库存,保证缓存中和数据库中库存的最终一致性。
在这个方案中我们使用的分布式缓存是redis,因为redis是单线程写,所以也不用担心线程安全的问题,redis自身就能够保证数据的一致性,在下单的事务中包含了实时扣减缓存中的库存和异步发送队列,由队列处理器再异步从队列中取出订单根据订单信息扣减库存系统数据库中的商品数量。 整个思路还是比较简单的,但是对于像12306这些在高峰时达到几十万用户并发的场景,使用上面的方案可能用户体验方面和系统服务方面就会存在一些问题了,对于每秒几十万并发的场景我们一般除了在技术层面进行优化,更多的会通过其他一些业务手段来进行交易分流来分散整体的高并发访问。
整体架构
在各层中进行优化:
页面层:禁止用户重复提交请求,通过js在一定时间段内只能提交一次请求,对于用户的狂点,可以弹出一个验证码进行验证。
应用层:动静分离,压缩缓存处理(CDN,导流技术)。根据UID限流,页面缓存技术(Web服务器)。反向代理+负载均衡(nginx)
服务层:读写操作基于缓存(redis),请求排队处理,分批放行(队列)。热点分离。
数据层:读写分离,分库分表,数据库集群。
.......
其实优化的要点就是:
尽量将请求拦截在上游
可以根据UID进行限流
最大程度减少请求落到DB
多利用缓存
同步操作异步化
要确保每层都不能出现单点故障风险,
秒杀系统简单介绍至此,不足之处欢迎留言。。。
乐观锁和悲观锁介绍
锁分类 | 概述 | 使用场景 | 样例 |
悲观锁 | 悲观锁对数据被外界修改持保守态度(悲观),因此在整个数据处理过程中,将数据处于锁定状态,往往依靠数据库提供的锁机制实现 | 写多读少,保证数据安全 | 行锁,页锁,表锁,共享锁(读锁)、排它锁(写锁) |
乐观锁 | 乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做或者程序自动去重试 | 读多写少,提高系统吞吐量 | 数据库乐观锁、缓存乐观锁 |
乐观锁和悲观锁到底谁的吞吐量大?
乐观锁只是在更新数据那一刻锁表,其它时间不锁表,所以相对于悲观锁,吞吐量更高!!!
实现乐观锁的三种方法:
1. 基于MySQL通过版本号实现
//通过版本号实现
update tb_goods_info set amout = amout - #{buys},version = version + 1 where code = #{code} and version = #{version}
2. 基于MySQL通过状态实现
//通过状态控制实现
update tb_goods_info set amout = amout - #{buys} where code = #{code} and amout - #{buys}>=0
3. 基于memcached的cas机制实现
日积月累的沉淀终将使我们变得强大,流年笑掷,未来可期!!!欢迎指正留言!
下一篇: Vue学习笔记(关于Vue的介绍)