当前位置: 首页 > 科技观察

理论-秒杀系统设计笔记

时间:2023-03-20 21:26:56 科技观察

在秒杀场景中,对系统的要求其实就是三个字:快、准、稳。本文主要内容:五个架构原则数据应该尽可能少首先,它意味着用户请求的数据尽可能少。请求的数据包括上传到系统的数据和系统返回给用户的数据(通常是网页)。请求的数量应尽可能少。返回用户请求的页面后,浏览器将渲染该页面并包含其他附加请求。比如这个页面依赖的CSS/JavaScript、图片、Ajax请求,都定义为“额外请求”,这些额外的请求应该保持在最低限度。路径越短越好,也就是从用户发出请求到返回数据的过程中需要经过的中间节点的数量。依赖越少越好,是指完成用户请求必须依赖的系统或服务。这里的依赖指的是强依赖。高可用系统中的单点可以说是系统架构中的大忌,因为单点意味着没有备份,风险不可控。我们设计分布式系统最重要的原则就是“消除单点”。另一个叫做法律是“高度可用的”。建筑是一门平衡的艺术,再好的建筑一旦脱离了它所适应的场景,一切都将是空谈。我们需要记住的是,这里提到的要点只是一个方向。我们应该尽量往这些方向努力,但也要考虑平衡其他因素。动静分离怎么做,什么是动静数据,什么是动静分离?所谓“动静分离”,其实就是将用户请求的数据(如HTML页面)分为“动态数据”和“静态数据”。简单来说,“动态数据”和“静态数据”的主要区别在于看页面输出的数据是否与URL、查看者、时间、地区相关,是否包含cookie等隐私数据。在很多媒体网站上,一篇文章的内容,无论你看还是我看,都是一样的。所以它是一个典型的静态数据,但它是一个动态页面。如果我们现在访问淘宝首页,每个人可能会看到不同的页面。淘宝首页包含了很多根据访问者的特点推荐的信息,这些个性化的数据可以理解为动态数据。如何缓存静态数据?首先,您应该将静态数据缓存到离用户最近的地方。静态数据是相对不会发生变化的数据,所以我们可以对其进行缓存。缓存在哪里?常见的有三种,在用户的浏览器中,在CDN中,或者在服务器的Cache中。您应该根据情况将它们缓存在尽可能靠近用户的地方。第二,静态改造就是直接缓存HTTP连接。相对于普通的数据缓存,你一定听说过系统的静态改造。静态转换是直接缓存HTTP连接,而不只是缓存数据。如下图,Web代理服务器直接根据请求URL获取对应的HTTP响应头和响应体,直接返回。这个响应过程非常简单,连HTTP协议都没有用到。重新组装,连HTTP请求头都不需要解析。第三,谁来缓存静态数据也很重要。不同语言编写的缓存软件处理缓存数据的效率不同。以Java为例,由于Java系统本身也有弱点(例如不擅长处理大量的连接请求,每次连接消耗内存较多,Servlet容器解析HTTP协议慢),所以可以不在Java层做缓存,而是直接在webserver层做,这样可以屏蔽Java语言层面的一些弱点;相比之下,Web服务器(如Nginx、Apache、Varnish)也更擅长处理大型并发静态文件请求。如何改造URL动静分离独特的浏览器相关因素分离时间因素分离地域因素异步化cookies动静分离的几种架构方案根据架构的复杂程度,有以下三种方案:实体机站-单独部署:统一缓存层:加CDN层:基于CDN的部署方案还具有以下特点:整个页面缓存在用户浏览器中;如果强制刷新整个页面,也会请求CDN;实际有效的请求只是让用户“刷新”点击“抢宝”按钮。秒杀系统的热点数据如何处理?什么是“热点”热点分为热点操作和热点数据。所谓的“热点操作”,如大量刷新页面、大量添加购物车、双十一半夜大量下单等,都属于此类操作。对于系统来说,这些操作可以抽象为“读请求”和“写请求”。这两个热请求的处理方式有很大的不同。读请求的优化空间更大,而写请求的瓶颈一般在存储层。优化的思路是按照CAP理论进行平衡,我会在《库存减少》一文中详细介绍。而“热点数据”更容易理解,即用户热点请求对应的数据。热点数据分为“静态热点数据”和“动态热点数据”。所谓“静态热点数据”,是指可以提前预测的热点数据。比如我们可以通过卖家的注册提前筛选出来,通过注册系统标记这些热销产品。此外,我们还可以通过大数据分析,提前发现爆款产品。例如,我们分析历史交易记录和用户购物车记录,找出哪些产品可能更受欢迎,卖得更好。这些是可以提前分析的热点。所谓“动态热点数据”,是指无法提前预测,在系统运行过程中临时产生的热点。比如卖家在抖音上做广告,产品瞬间走红,导致短时间内大量购买。由于热点操作是用户的行为,我们无法改变,但可以做一些限制和保护。因此,在本文中,我主要介绍如何优化热点数据。发现热点数据发现静态热点数据发现动态热点数据处理热点数据优化优化热点数据最有效的方法就是缓存热点数据。如果热数据与静态和动态数据分离,静态数据可以长期缓存。但是,缓存热点数据更多的是一种“临时”缓存,即不管是静态数据还是动态数据,都会在队列中临时缓存几秒。由于队列长度有限,可以用LRU淘汰算法代替。RestrictionRestriction更多的是一种保护机制,限制的方法有很多,比如对访问的商品ID做一个一致的Hash,然后根据Hash做bucket,为每个bucket设置一个处理队列,这样热点商品就可以被限制在一个请求队列中,防止其他请求因为某些热点商品占用过多的服务器资源而无法从服务器接收处理资源。隔离秒杀系统设计的首要原则就是隔离这个热点数据,让1%的请求不影响其他99%的请求。隔离之后,更方便优化那1%的请求。其中,隔离又可以分为:业务隔离、系统隔离、数据隔离。交通如何调峰就像城市的道路,因为有早高峰和晚高峰的问题,所以有错峰和限行的解决方案。调峰的存在,首先可以让服务器处理更加稳定,其次可以节省服务器的资源成本。对于秒杀的场景,削峰本质上是将用户请求的发送延迟更多一些,以减少和过滤掉一些无效请求,遵循“请求数量越少越好”的原则。流量削峰排队的思路是为了削峰填谷。最容易想到的方案就是用消息队列缓冲瞬时流量,将同步直接调用转为异步间接推送,用队列在一端承担瞬时流量高峰。,以便顺利地将消息推送到另一端。除了消息队列之外,还有很多类似的排队方式,例如:使用线程池加锁等待也是一种常见的排队方式;先进先出、先进后出等常用内存排队算法的实现;将请求序列化到文件中,然后顺序读取文件(比如基于MySQLbinlog的同步机制)还原请求等。可以看出,这些方法都有一个共同的特点,就是改“一”-stepoperation”变成“two-stepoperation”,增加的一步操作作为缓冲。性能优化减少编码减少序列化Java极度优化并发读优化“库存减少”核心逻辑这个很重要,其他都是辅助。如果你有100件库存,你可以卖掉100件,然后在数据库中将其减少为0。有什么问题?是的,理论上是这样,但在具体的业务场景下,“去库存”并不是那么简单。减少库存的方法有哪些?点击商品页面的“立即购买”按钮,核对信息后点击“提交订单”。此步骤称为下订单。下单后,真正完成支付操作,才算是真正的购买,也就是俗话说的。减库存操作一般有以下几种方式:下单减库存,即买家下单后,从商品总库存中减去买家的采购数量。下单减库存是最简单的减库存方式,也是最精准的控制方式。下单时,通过数据库的交易机制直接控制商品的库存,不会出现超卖的情况。但是你要知道,有的人可能下单后不付款。付款后减库存是指买家下单后不会立即减库存,而是等到用户付款后才真正减库存,否则库存会一直留给其他买家。但是因为只是在付款的时候减少了库存,如果并发量比较高,可能会出现买家下单后无法付款的情况,因为商品可能已经被别人买了。预扣库存的方法相对复杂。买家下单后,库存会保留一定时间(如10分钟)。过了这个时间,库存会自动释放。发布后,其他买家可以继续购买。买家付款前,系统会检查订单库存是否有预留:如果没有预留,则再次尝试预留;存货不足(即扣缴不合格)的,不得补缴;如果预扣成功,则进行付款并实际减去库存。高可用建设应该从哪里开始说到系统的高可用建设,其实是一个系统工程,需要考虑系统建设的各个阶段,也就是说,其实贯穿于系统的整个生命周期构建,如下图所示:架构阶段架构阶段主要考虑系统的可扩展性和容错性,避免系统出现单点问题。比如多个机房的单元化部署,即使某个城市的某个机房出现整体故障,仍然不会影响整个网站的运行。编码阶段编码最重要的是保证代码的健壮性。比如远程调用,需要设置合理的超时退出机制,防止被其他系统拖累。还需要对调用的返回结果集有预期,防止返回结果超出程序处理的范围,最常用的方法是捕获错误异常,对不可预见的错误有默认的处理结果。测试阶段测试主要是为了保证测试用例的覆盖率,保证在最坏情况发生的时候,我们也有相应的处理流程。在发布阶段还有一些需要注意的地方,因为发布的时候最容易出错,所以必须要有紧急回滚机制。运行阶段是系统的正常状态。系统大部分时间会处于运行状态。在运行状态下最重要的是准确及时地监控系统。当发现问题时,可以准确上报告警,告警数据要准确、详细,便于排除故障。当出现故障时,首先也是最重要的是及时止损。例如,由于程序问题导致商品价格错误,需要及时下架商品或关闭购买链接,防止造成重大资产损失。那么就需要能够及时恢复服务,定位原因解决问题。遇到流量大的时候,我们应该如何最大程度的保证我们系统的正常运行呢?所谓“降级”,就是当系统的容量达到一定程度时,限制或关闭系统的一些非核心功能,从而将有限的资源预留给更多的核心业务。是一个有目的、有计划的执行过程,所以我们一般需要有一套计划来配合降级的执行。如果我们将其系统化,我们可以通过计划系统和开关系统实现降级。限流限流是指当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,实现手动切换和自动保护两种措施。客户端限流和服务端限流的优缺点:客户端限流,优点是可以限制请求的发送,通过减少无用的请求来降低系统的消耗。缺点是当client比较分散时,无法设置合理的限流阈值:阈值设置过小,会在server未达到瓶颈时限制client;如果设置太大,则起不到限制的作用。服务器端限流的优点是可以根据服务器的性能设置合理的阈值,缺点是被限制的请求都是无效请求,处理这些无效请求本身就会消耗服务器资源。常见限流算法counter(固定窗口)算法counter算法是通过计数器累加一段时间内的访问次数,当达到设置的限流值时,触发限流策略。在下一个周期开始时,它被清零并重新计数。该算法无论在单机还是分布式环境下实现起来都非常简单,利用redis的incr原子自增和线程安全就可以轻松实现。滑动窗口算法滑动窗口算法就是把时间段分成N个小周期,记录每个小周期的访问次数,按照时间滑动删除过期的小周期。该算法很好地解决了固定窗口算法的关键问题。漏桶算法漏桶算法是当访问请求到达时,直接将其放入漏桶中。如果当前容量已经达到上限(限流值),将被丢弃(触发限流策略)。漏桶以固定的速率(即请求通过)释放访问请求,直到漏桶为空。令牌桶算法令牌桶算法是程序以r(r=时间段/限流值)的速度向令牌桶中添加令牌,直到令牌桶满为止,当请求到达时,从令牌桶中请求令牌。如果获得token,则请求通过;否则,将触发限速策略拒绝服务。如果限速不能解决问题,最后的办法就是直接拒绝服务。当系统负载达到一定阈值时,比如CPU使用率达到90%或者系统负载值达到2*CPU核时,系统直接拒绝所有请求。这种方法是最暴力但也是最有效的系统保护方法。例如在秒杀系统中,我们在以下环节设计了过载保护:在前端Nginx上设置过载保护,当机器负载达到一定值时,直接拒绝HTTP请求并返回503错误码,并过载也可以在Java层设计Protect。拒绝服务可以说是防止最坏情况发生的不得已的方案,防止服务器因不堪重负而长时间完全无法提供服务。这种系统过载保护虽然在过载时不能提供服务,但系统仍然可以运行,并且在负载下降时很容易恢复。所以每一个系统,每一个环节都应该配备这个来回的解决方案,为系统做最坏的情况。受到保护。缓存问题CacheAvalanche数据没有加载到缓存中,或者缓存同时大面积失效,导致所有请求都去查找数据库,导致数据库、CPU和内存过载,甚至宕机。一个简单的雪崩过程:1)Redis集群大规模失效;2)缓存失效,但仍有大量请求访问缓存服务Redis;3)大量Redis请求失败后,请求转向数据库;4)数据库请求激增,导致数据库被kill掉;5)由于你的大部分应用服务依赖于数据库和Redis服务,很快就会导致服务器集群雪崩,最后整个系统彻底崩溃。解决方案:事前:高可用缓存高可用缓存是为了防止整个缓存失效。即使个别节点、机器甚至机房宕机,系统仍然可以提供服务,RedisSentinel和RedisCluster都可以实现高可用。进行中:缓存降级(临时支持)当访问量急剧增加导致服务出现问题时,我们如何确保服务仍然可用。Hystrix在国内应用广泛,通过熔断、降级、限流三种手段减少雪崩后的损失。只要数据库没死,系统总能响应请求。我们每年春节12306不就是这样过来的吗?只要还能反应过来,至少还有抢票的机会。事后:Redis备份和快速预热1)Redis数据备份和恢复2)快速缓存预热缓存击穿缓存击穿是指当热数据存储过期时,多个线程同时请求热数据。因为缓存刚过期,所有的并发请求都会去数据库查询数据。解决方案:其实在大部分实际业务场景中,缓存击穿是实时发生的,但是不会对数据库造成太大的压力,因为一般公司业务的并发没有那么高。当然,如果你不幸出现这种情况,你可以将这些热键设置为永不过期。另一种方法是通过互斥锁来控制查询数据库的线程访问,但这会导致系统吞吐量下降,需要在实际情况下使用。缓存穿透缓存穿透是指查询一个一定不存在的数据,因为缓存中没有数据的信息,所以会直接到数据库层去查询,从系统层面看,好像是穿透了缓存层,直接到达db,所以叫缓存穿透。如果没有缓存层的保护,这种对一定不存在的数据的查询可能会对系统造成危险。如果有人恶意利用这个本来不存在的数据来频繁请求系统,那是不会的,准确的说是对系统的攻击,请求会到达数据库层,导致db瘫痪,造成系统失败。解决方案:缓存穿透行业的解决方案比较成熟,常用的主要有:会在查询前用于过滤,如果不在里面就直接过滤,从而减轻数据库层面的压力。空值缓存:一种相对简单的解决方案。第一次查询到不存在的数据后,将key和对应的空值(null或者object中只有key)放入缓存,但是设置的过期时间要短一些,比如几分钟,这样就可以了可以在短时间内应对针对该密钥的大量攻击。之所以设置较短的过期时间是因为这个值可能与业务无关,意义不大,而且本次的查询也可能不是攻击者发起的,不需要长期存储,所以它可以提前失效。小结由于本文是一篇理论文章,整篇文章没有一行代码,但是文中提出的基本都是秒杀系统发生的事情,每个系统可能出现的问题都不一样。本文转载自微信公众号《Java后端技术全栈》,可通过以下二维码关注。转载本文请联系Java后端技术全栈公众号。