微立顶科技

新闻资讯

创新 服务 价值

  秒杀系统设计探析

发布日期:2022/9/25 23:51:49      浏览量:

一、秒杀是什么?

秒杀系统的业务逻辑和复杂程度都被大众熟知,其实主要解决的问题有两个——高并发和一致性。其中高并发又分为读和写,要减少用户从服务端读取数据,控制数据的传输大小;写则需要独立处理数据库。一致性是指保证库存数据的准确,超卖和超买都是不能出现的。至于高可用会在最后介绍,通过高可用可以保证流量超出预期或其他外情况发生时,秒杀活动的顺利完成。

 综上所述,秒杀系统是一个要满足高并发、强一致、高可用的分布式系统。

二、秒杀架构的设计要素

针对秒杀系统的架构设计需要考虑以下几个要素:

1.减少读的次数

这里提到的读不止是数据,还有静态资源的读取。

为什么要减少读的次数呢,因为网络传输是需要时间的,无论是请求、处理、响应都需要服务器处理。如果存在必须读取的情况,要减小数据请求和响应的大小,数据在数据库和各种服务之间调用的过程中存在序列化和反序列化的过程,这是很消耗资源的。

静态资源的读取比较常见的优化手段就是合并CSS和JS文件,要保证请求的静态文件在统一域名中,并减少访问次数。每次HTTP请求都存在网络传输,减少耗时是很有必要的。

2.减少服务调用节点

这里提到的节点是指用户请求后,直至响应完成所经历的服务节点。不同节点间,尤其是不同机架,不同机房节点间的服务调用不仅仅耗时,还会降低秒杀的可靠性。

3.减少不必要的秒杀非核心业务

秒杀业务可以根据重要程度赋予权重,减少低权重服务的调用,以防止高权重系统被拖垮。秒杀活动进行时,有很多不是秒杀核心业务的数据可以通过降低优先级进行优化,例如支付、通知等功能。

以上的原则并不是绝对的,只是在设计中努力优化的方向。 

三、秒杀架构的搭建思路

对于并发量访问较大的秒杀架构,为了提高架构性能和稳定性,需要注意几点:秒杀要独立于其他系统,无论在研发和部署环节都要独立,这样有助于整体平台的稳定性,也有利于秒杀系统的优化。秒杀的数据,尤其是核心数据单独放到缓存系统中,提高并发,对于非核心数据可以放到本地,减少请求服务的次数。秒杀的页面要实现动静分离,把页面刷新频率降低。

另外,从安全性角度考虑,秒杀的验证必不可少,限流保护更是重中之重。


这次要解决的是秒杀活动中的动静分离方案。分离后的数据处理就简单了许多。


 

一、动静分离是什么?

动静分离就是并不是传统意义的静态页面和动态页面,而是指是否需要根据发送请求的用户进行个性化数据的推送。举个例子,在访问一些商城时,会针对不同用户进行商品的推荐,那么这样的数据就是动态数据;商城中有一些热门的活动,会针对所有用户进行推送,那么这样的数据就是静态数据,尽管这里的数据是动态生成的。

二、优化静态数据:

首先,我们要做的就是优化静态数据,那就是通过缓存来提高静态数据的读取可以有效提高数据的访问,除此之外还要考虑将缓存服务根据业务需要放到用户本地或CDN上。如何实现缓存将会在后续提到,我们现在可以认为这里的缓存是一个Redis集群或其他缓存系统。

三、优化动态数据:

动态数据主要包括服务端的时间,用户数据等。我常用的方法是通过发起一个Ajax的异步请求,获取动态数据,这样做的缺点是页面存在延迟的话,体验不好。为了解决这个问题,可以通过提前在页面中的动态数据展示部分做一个插槽,类似vue中的slot,缓解数据加载中的突兀感。

四、选择适合的分离架构:

接下来要介绍两种分离架构,一种是秒杀活动不频繁,秒杀商品数量少的场景,一种是秒杀活动频繁,秒杀商品数量多的场景。这里说的两种架构需要结合自身业务作取舍,不要一味的追求高并发。

1.单机部署

这里提到的单机是将缓存在一台性能较高的实体机中进行分组,采用Hash算法。由于商品数量不多,缓存的命中率会大大提高。这既是有点也是缺点,提高命中的同时可能会存在缓存击穿的风险。在访问量较大时,可以通过修改缓存算法或添加相同的缓存分组来缓解。

单机部署有效的回避了分布式网络传输的问题,提高了缓存的命中率。这样做有效的降低了运维的复杂程度,对于没有分布式缓存系统的业务来说,是一个很好的选择。

2.缓存集群

在一些业务复杂、数据庞大的机构中,可以考虑建立独立的缓存系统,减少运维成本,提高复用。这样做的可以使开发人员将重心放在业务中,不需要考虑缓存数据的处理;同时可以通过zookeeper实现配置自动化;在不同秒杀活动中,共享内存,降低资源浪费。

除此之外,CDN的优化以后也会介绍。

五、缓存中的数据:

数据处理除了以上提到的动静分离,还需要进行隔离。这么做的原因是不能让秒杀活动影响了其他业务,在秒杀期间减少对其他业务资源的占用。要做到这一点,除了要进行系统隔离,还需要进行数据的隔离,就像我们上一篇中提到的,要有独立的数据存储。实现数据隔离的方式有很多种,可以根据区域划分、可以根据URL划分等。

秒杀请求在高度集中在某一个时间点。这样一来,就会导致一 个特别高的流量峰值,它对资源的消耗是瞬时的 。能够抢到商品的人数是有限的,也就是说10人和1000人发 起请求的结果都是一样的。也就是说真正开始下单时,秒杀请求并不是越多越好。


一、秒杀中的削峰

由于服务器的处理资源是恒定的,用或者不用它的处理能力都是一样的,出现峰值的话,很容易导致忙到处理不过来,闲的时候却又没有什么要处理。为了保证服务质量,很多处理资源只能按照忙时预估,而这会导致资源浪费。 削峰可以让服务端处理变得更加平稳,还可以节省服务器的资源成本。针对秒杀这一场景,削峰从本质上来说就是更多地延缓用户请求的发出,以便减少和过滤掉一些无效请求。

常见秒杀流量削峰的一些操作思路:消息队列、答题器、数据过滤。

1.消息队列

其中最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送,通过队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去,在这里,消息队列就像“水库"一样,拦蓄上游的洪水,削减进入下游的洪峰流量。

但是,如果流量峰值持续时间达到了消息队列的处理上限,消息队列同样也会被压垮,这样虽然保护了下游的系统,但是和直接把请求丢弃也没多大的区别。就像遇到洪水爆发时,即使是有水库恐怕也无济于事。在这种情况下,我们要把“一步的操作”变成“两步的操作”,其中增加的操作用来起到缓冲的作用,例如利用线程池加锁等待、采用先进先出、先进后出等常用的内存排队算法。

2.答题器

添加答题器第一个目的是防止部分买家使用秒杀器在参加秒杀时作弊,第二个目的就是延缓请求,起到削峰的作用。把请求的时间从瞬时延长到了几秒,这样会大大减轻对服务器的压力。而且后续请求到达服务器时已经没有库存了,真正的并发处理就很有限了。

答题器生成的题目不需要很复杂,为了防止被破解可以添加图片噪点。同时在CDN上缓存图片,避免成为秒杀活动中的短板,影响用户体验。

3.数据过滤

这里提到的数据过滤有点像某些企业在招聘时,把简历随机抽出一部分扔掉一样,只不过抽取的过程可以设置一定的规则,过滤掉那些无效的请求。在不同的处理层根据不同的规则有效的过滤,例如对写数据进行基于时间的合理分片,过滤掉过期的失效请求;对写数据进行强一致性校验,只保留最后有效的数据。

这么做的目的是在读系统中,尽量减少由于一致性校验带来的系统瓶颈,但是尽量将不影响性能的检查条件提前,如用户是否具有秒杀资格、商品状态是否正常、用户答题是否正确、秒杀是否已经结束、是否非法请求等;在写数据系统中,主要对写的数据做一致性检查,最后在数据库层保证数据的最终准确性。

二、秒杀中的服务性能优化

服务端性能, 一般用QPS来衡量, 还有一个和QPS息息相关的是响应时间, 它可以理解为服务器处理响应的耗时。

正常情况下响应时间越短, 一秒钟处理的请求数(QPS) 自然也就会越多, 这在单线程处理的情况下看起来是线性的关系,即我们只要把每个请求的响应时间降到最低,那么性能就会最高。

这个两个因素到底会造成什么样的影响?首先, 我们先来看看响应时间和QPS的关系,对于大部分的Web系统而言响应时间一般都是由CPU执行时间和线程等待时间组成,也许你会说为什么不去减少这种等待时间,其实减少线程等待时间对提升性能的影响没有我们想象得那么大, 这点在很多代理服务器上可以做验证,如果代理服务器本身没有CPU消耗, 我们在每次给代理服务器代理的请求加个延时, 即增加响应时间,这对代理服务器本身的吞吐量并没有多大的影响,因为代理服务器本身的资源并没有被消耗。

真正对性能有影响的是CPU的执行时间, 因为CPU的执行真正消耗了服务器的资源, 我们应该致力于减少CPU的执行时间。

对于Java系统可优化的地方很多,除了常见的代码优化外,以下的内容值得注意。

Java和通用的Web服务器相比,在处理大并发的HTTP请求时要弱一点, 所以一般我们都会对大流量的Web系统做静态化改造,让大部分请求和数据直接在Nginx服务器或者Web代理服务器上直接返回 , 而Java层只需处理少量数据的动态请求。

针对这些请求, 我们可以使用以下手段进行优化:

1.直接使用Servlet处理请求, 避免使用传统的MVC框架, 这样可以绕过一大堆复杂且用处不大的处理逻辑, 直接输出流数据。使用resp.getOutputStream)而不是resp.get Writer函数, 可以省掉一些不变字符数据的编码, 从而提升性能。

2.数据输出时推荐使用JSON而不是模板引擎来输出页面。

3.集中式缓存为了保证命中率一般都会采用一致性Hash, 所以同一个key会落到同一台机器上。那么,该如何彻底解决单点的瓶颈呢? 答案是采用应用层的Local Cache。你需要划分成动态数据和静态数据。

像商品中的标题和描述这些本身不变的数据,会在秒杀开始之前全量推送到缓存直到到秒杀结束。

像库存这类动态数据的方式缓存一定时间,失效后再去缓存拉取最新的。你可能还会有疑问:像库存这种频繁更新的数据,一旦数据不一致,会不会导致超卖? 这就要用到前面介绍的读数据的分层原则了,读的场景可以允许一定的脏数据,因为这里的误判只会导致少量原本无库存的下单请求被误认为有库存,可以等到真正写数据时再保证最终的一致性。 

如果你第一次接触秒杀,可能还不太理解,库存100件就卖100件,在数据库里减到0就好了,这有什么麻烦的?理论上是这样,但是具体到业务场景中就没那么简单了。今天就聊聊减库存的设计,之后以高可用方案来结束秒杀设计的全部内容。


一、秒杀中的减库存

减库存操作一般有如下几个方式:

1.下单减库存:下单后,在商品的总库存中减去购买数量,下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机制控制商品库存,这样一定不会出现超卖的情况。

2.付款减库存:下单后,并不立即减库存,而是等到付款后才真正减库存,否则库存一直保留给其他买家,但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,可能商品已经被其他人买走了。

3.预扣库存:下单后,库存为其保留一定的时间, 超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买,在买家付款前,系统会校验该库存是否还有保留,如果没有保留,则再次尝试预扣;如果库存不足则不允许继续付款;如果预扣成功,则完成付款并实际地减去库存,这种方式相对复杂一些。

以上这几种减库存的方式都会存在一些问题。  假如我们采用“下单减库存”的方式,正常情况下,买家下单后付款的概率会很高,所以不会有太大问题,但是有一种场景例外,就是当卖家参加某个活动时,此时活动的有效时间是商品的黄金售卖时间,通过恶意下单的方式将该卖家的商品全部下单,那么这款商品就不能正常售卖了。要知道,这些恶意下单的人是不会真正付款的。

既然“下单减库存”可能导致恶意下单,从而影响卖家的商品销售,那么有没有办法解决呢?你可能会想,采用“付款减库存”的方式是不是就可以了?的确可以,但是 “付款减库存”又会 导致另外一个问题:库存超卖。假如有10件商品,因为下单时不会减库存,就可能出现100人下单成功的情况,这样一 来,就会导致很多买家下单成功但是付不了款,购物体验自然比较差。

既然“下单减库存”和“付款减库存”都有缺点,我们能否采用“预扣库存”这种方式呢? 这种方案确实可以在一定程度上缓解上面的问题,但是否就彻底解决了呢?针对恶意 下单这种情况,虽然把有效的付款时间设置为10分钟,但是恶意买家完全可以在10分钟后再次下单。

针对这种情况,解决办法还是要结合反作弊的措施来制止, 例如,设置最大购买件数,对重复下单不付款的操作进行次数限制等。针对“库存超卖”这种情况,在10分钟时间内下单的数量仍然有可能超过库存数量,遇到这种情况只能区别对待:对普通的商品下单数量超过库存数量的情况,可以通过补货来解决;但是有些卖家完全不允许库存为负数的情况,那只能在买家付款时提示库存不足。

由于参加秒杀的商品成功下单后却不付款的情况比较少,再加上卖家对秒杀商品的库存有严格限制,所以秒杀商品采用“下单减库存”更加合理。一般我们有多种解决方案:一种是在应用程序中通过事务来判断,即保证减后库存不能为负数,否则就回滚;另一种办法是直接设置数据库的字段数据为 无符号整数, 这样减后库存字段值小于零时会直接执行SQL语句来报错。 

二、秒杀中的高可用

高可用涉及架构阶段、编码阶段、测试阶段、运行阶段。

1.架构阶段:架构阶段主要考虑系统的可扩展性和容错性,要避免系统出现单点问题,例如多机房部署,即使某个机房出现整体故障,仍然不会影响整体网站的运转。

2.编码阶段:编码最重要的是保证代码的健壮性,例如涉及远程调用问题时,要设置合理的超时退出机制,防止被其他系统拖垮。

3.测试阶段:测试主要是保证测试用例的覆盖度,保证最坏情况发生时,我们也有相应的处理流 程。

4.运行阶段:系统大部分时间都会处于运行态,运行态最重要的是对系统的监控要准确及时,发现问题能够准确报警并且报警数据要准确详细,以便于排查问题。

 为什么系统的高可用建设要放到整个生命周期中全面考虑?因为我们在每个环节中都可能犯错, 而有些环节犯的错是无法弥补的。例如在架构阶段,没有消除单点问题,那么系统 上线后,遇到突发流量把单点给挂了,加机器都加不进去。

那么针对秒杀系统,我们重点介绍在遇到大流量时,应该从哪些方面来保障系统的稳定运行,所 以更多的是看如何针对运行阶段进行处理,这就引出了接下来的内容:降级、限流

降级:就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。降级方案可以这样设计:当秒杀流量达到5w/s时,把成交记录的获取从展示20条降级到只展示5条。 

执行降级无疑是在系统性能和用户体验之间选择了前者,降级后肯定会影响一部分用户的体验,。 所以降级的核心目标是牺牲次要的功能和用户体验来保证核心业务流程的稳定,是一个不得已而 为之的举措。

限流: 如果说降级是牺牲了一部分次要的功能和用户的体验效果,那么限流就是更极端的一种保护措施 了。限流就是当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。

首先,来分别说下客户端限流和服务端限流的优缺点。 客户端限流,好处可以限制请求的发出,通过减少发出无用请求从而减少对系统的消耗,缺点 就是当客户端比较分散时,没法设置合理的限流阈值。如果阈值设的太小,会导致服务端没有 达到瓶颈时客户端已经被限制;而如果设的太大,则起不到限制的作用。 服务端限流,好处是可以根据服务端的性能设置合理的阈值,而缺点就是被限制的请求都是无效的请求,处理这些无效的请求本身也会消耗服务器资源。



  业务实施流程

需求调研 →

团队组建和动员 →

数据初始化 →

调试完善 →

解决方案和选型 →

硬件网络部署 →

系统部署试运行 →

系统正式上线 →

合作协议

系统开发/整合

制作文档和员工培训

售后服务

马上咨询: 如果您有业务方面的问题或者需求,欢迎您咨询!我们带来的不仅仅是技术,还有行业经验积累。
QQ: 39764417/308460098     Phone: 13 9800 1 9844 / 135 6887 9550     联系人:石先生/雷先生