高可用秒杀系统设计
Posted 拍码场
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高可用秒杀系统设计相关的知识,希望对你有一定的参考价值。
说起秒杀,我想大家肯定不陌生,从双十一购物到春节抢红包,再到12306抢火车票,“秒杀”的场景处处可见。简单来说,秒杀就是在同一个时刻有着大量的请求争抢购买同一个产品并完成交易的过程,用技术的话来说就是大量的并发读和并发写。从系统层面来看,秒杀系统本质上就是一个满足大并发、高性能和高可用的分布式系统,下面我们就来了解一下通过怎样的技术实践来设计一个稳定的高、性能的秒杀系统。
秒杀系统的特点
快:响应快,处理请求速度快,秒杀涉及到大量的并发读和并发写,在并发数比较大的情况下,如何保证系统的TPS和QPS是关键。
准:系统的准确性,针对秒杀系统可以理解为数据的一致性,有限的商品同一时刻被多倍的请求同时来扣减库存,在大并发更新的过程中需要保证数据的准确性,库存为100的秒杀商品,最终只能卖100个,控制资损是关键。
稳:系统的稳定性要求高,在大并发的情况下能保证系统的稳定运行,不出现系统运行故障。
了解了秒杀系统的特点之后,我们就可以有针对性的去设计系统的架构,落实具体的技术方案,具体如下:
一、高性能
动静分离。把用户请求的数据分为"动态数据"和"静态数据",不需要让用户每次都去刷新整个页面,用户只需要点击"一键刷宝"等功能按钮就可以看到最新的秒杀详情。针对静态数据我们可以做静态缓存,静态数据缓存主要涉及以下两个重点:
1. 把静态数据缓存在离用户最近的地方
常用的静态缓存主要有三种,用户浏览器里、CDN上或者后台服务器的Cache里,该根据情况把数据缓存在离用户最近的地方
2. 直接缓存HTTP链接
相对于普通的数据缓存而言,HTTP链接缓存则更加的简单粗暴,直接缓存HTTP链接而不仅仅是数据,如下图所示,Web代理服务器根据请求的UTL直接取出HTTP请求对应的响应头和响应体然后直接返回,这个响应过程简单的连HTTP协议都不需要重新组装,甚至连请求头都不需要解析
动静分离系统部署架构图
热点数据分离:热点数据顾名思义就是访问较为频繁的数据,而热点数据又分为"静态热点数据"和"动态热点数据"。比如秒杀的商品列表、商品详情等,这些我们在秒杀之前都是可以提前知道,而且这部分数据是不会修改的,所以针对这部分数据我们可以直接缓存在服务器Cache里。动态热点数据是在交易过程中产生的,比如一个秒杀商品,由于商户打了一个广告,导致商品点击率骤然上升,那么这种数据就属于动态热点数据。获取动态热点数据则需要一个动态热点发现系统,这里给出一个热点发现系统的简单实现:
1. 构建一个异步系统,它可以收集交易链路上各个环节中的中间件产品的热点 Key如 nginx、缓存、RPC 服务框架等这些中间件
2. 建立一个热点上报和可以按照需求订阅的热点服务的下发规范,把上游已经发现的热点透传给下游系统,提前做好保护,比如缓存或者隔离
3. 将上游系统收集的热点数据发送到热点服务台,然后下游系统(如交易系统)就会知道哪些商会被频繁调用,然后做热点区分
热点数据发现系统架构图
业务操作拆分: 将整个交易过程分为多个步骤,用户的一次秒杀抢购涉及到很多后台的流程,比如库存判断、下单、支付和发货等。类似于活动中的抽奖发红包,抽奖和发红包是两个过程,用户一次请求提示中奖,这个时候并没有立刻发放奖品,而是由后台异步程序去发放。同样秒杀也是一样,针对抢购完成之后的一些流程,可能涉及到第三方接口调用,如
果同步去做无疑会使调用链加长,增加响应时间。可以使用MQ消息中间件来完成后续的操作。同时为了保证订单和发货的一致性,可以做定时JOB来保证数据准确性
二、库存扣减实现
针对秒杀系统不超买是前提,如果在高并发扣减库存的情况下保证不超买,同时还要保证用户请求的实时响应速度是关键。一般扣减库存有以下几种方式:
下单减库存,即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机制控制商品库存,这样一定不会出现超卖的情况。但是有些人下完单可能并不会付款,这就会导致一些竞争商家恶意下单的情况。
付款减库存,即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家。但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了,用户体验不好。
预扣库存,买家下单后,库存为其保留一定的时间(如 30 分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买,在买家付款前,系统会校验该订单的库存是否还有
保留:如果没有保留,则再次尝试预扣;如果库存不足(也就是预扣失败)则不允许继续付,如果预扣成功,则完成付款并实际地减去库存。
由于参加秒杀的商品,一般都是“抢到就是赚到”,所以成功下单后不付款的情况比较少,再加上卖家对秒杀商品有严格的库存限制,所以秒杀商品采用"下单减库存"更为合理,在逻辑处理上更加简单,性能也更占优势。同时可以用后台JOB去检测订单时间和订单状态,根据业务需求可以灵活的回补库存,下单和最终的付款步骤都需要强制校验库存。"下单减库存"在数据一致性上,主要就是保证大并发请求时库存数据不能为负数,库存扣减主要有以下几种方案:
1. 数据库实现扣减:通过数据库事务来实现库存扣减,每次扣减update数据库的剩余库存值,如果更新后的值为负数,就回滚事物,返回无库存。由于扣减是在高并发场景下,所以需要保证每次事物执行的数据准确性,一般有两种SQL语法可选择:
数据库悲观锁:利用数据库for update悲观锁,保证每次只有一个线程在查询数据,在更改数据之前一直锁住该条记录,修改完释放
CASE WHEN判断:在更改的时候判断当前数据是否满足条件,如果满足就就更新为新的值
UPDATE t_stock SET inventory = CASE WHEN inventory>=xxx THEN inventory -xxx ELSE inventory END
基于数据库的实时库存扣减实现起来比较简单,不会有超出的风险。但是这样就会导致所有的读写操作全部
在同一时间集中在数据库上,不仅造成了数据的压力,同时也会影响数据库的执行性能,进而影响请求的响
应时间。
2. 库存+消息实现库存扣减:秒杀商品和普通商品的减库存还是有些差异的,例如商品数量比较少,交易时间段也比较短,因此可以考虑把秒杀商品的库存直接放到缓存系统中实现,也就是直接在缓存中实现库存
的扣减。推荐使用带有持久化的缓存系统Redis,利用Redis单线程的特性,实现数据自增自减的原子性。而消息系统(MQ)则是用来同步缓存和数据库,每次在Redis扣减库存,同时发一条扣减库存的消息,异步
去更新数据库。如果担心缓存的可用性,可以设置库存在缓存中的失效时间,隔一段时间去数据库查询(总数-已抢购的)真实库存同步到缓存中。
如果你的秒杀活动对于库存扣减没有复杂的逻辑,采用缓存管理库存是比较好的方案。但是如果有比较复杂的减库存逻辑,或者需要使用事务,你还是必须在数据库中完成库存扣减
三、高可用
在大量请求和并发数比较大的情况下,我们需要采取一些措施来保证系统不会崩坏,能够稳定的运行,下面列举几种主要的方法:
流量削峰:秒杀场景的一个主要特点就是访问集中,但是最终能抢到商品的人数是固定的,也就是说100个人和1000个人来访问结果是一样的。我们希望更多的人来刷页面,但是下单的请求并非是越多越好,这样的话我们就没必要把所有的请求全都放到对应的服务上,可以设计一些规则来延缓请求,这就是所谓的流量削峰,这里提供两种方案:
1. 消息队列拦截请求。在实际操作中我们可以使用消息队列来缓冲瞬时流量,把同步的调用直接转成异步的推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑的将消息推送出去。当处理到某一个时刻(库存已经不足),后面的请求可以直接返回。
2. 并发限制。我们可以通过增加流程的复杂度来降低并发数,比如秒杀系统,在下单之前我们让用户输入验证码或者答题之类的,这样就可以控制并发提交,保证在最终的下单那一步,并发量没有那么高。这种方式同时也可以防止秒杀器来刷商品,增加了系统的安全性 。
用户限流。在某一时间段内只允许用户提交一次请求,比如可以采取IP或者登录手机号限流。或者针对用户请求的唯一标识uid之类的,在服务端控制层需要针对同一个访问uid,限制访问频率。
数据保证最终的一致性:数据保证最终的一致性就好,没必要保证实时的一致性,这样可以将业务操作拆分出来。比如针对回补库存的操作,扣减库存成功后,如果下单过程最终失败需要回补库存,这个时候只需要保证最终库存回补成功即可(消息异步操作),不需要实时操作,这样就增加了接口的响应速度。
兜底方案:没有人能够提前预估所有情况,意外无法避免,具体到秒杀这一场景下,为了保证系统的高可用,我们必须设计一个Plan B 方案来兜底,这样在最坏情况发生时我们仍然能够从容应对。
1)开关系统。当系统容量达到一定程度时,这个时候就需要对请求进行降级、限流或者直接拒绝服务,而这些操作都需要在项目中设置好控制开关,比如什么时候流量限制设置多大,开关的触发都是有一定规则的。它分为两部分,一部分是开关控制台,它保存了开关的具体配置信息,以及具体执行开关所对应的机器列表;另一部分是执行下发开关数据的 Agent,主要任务就是保证开关被正确执行,即使
系统重启后也会生效。来关闭活动,直接提示库存不足。
2)备用接口。一旦请求失败,进入备用数据接口请求备份数据。这里提到的备用接口,主要是数据的硬兜底,也就是说针对非个性化的请求,用户多次访问的结果一样。在系统瘫痪的情况下,接口去请求兜
底数据。至于兜底数据的来源主要有两个:
- 后端向 CDN push 一份通用数据。我们知道个性化都是使用 cookie 去识别用户的,对于没有浏览器记录的新用户就没有 cookie,此时会推一份通用的数据,这个通用的数据也可以作为接口的备份源
四、架构设计
系统架构
五、总结
网站的高可用建设是基础,可以说深入到各个环节,本章所讲的秒杀系统是一个比较典型的用例,只有了解系统的特点、业务上的要求,我们才可以有针对性的去设计系统的架构,实施具体的技术方案。同时在系统的设计上面我们要长期规划并进行系统化的建设,需要在预防、管控、监控和恢复系统等这些地方加强建设,如果要保证系统的完整性,每一个环节都有很多事情需要做。
更多福利请关注官方订阅号“拍码场”
好内容不要独享!快告诉小伙伴们吧!
喜欢请点击↓↓↓
以上是关于高可用秒杀系统设计的主要内容,如果未能解决你的问题,请参考以下文章