如何设计一个秒杀系统

开篇词 | 秒杀系统架构设计都有哪些关键点?

  • 秒杀主要解决两个问题,一个是并发读,一个是并发写
  • 秒杀的总体架构须要作到:稳、准、快。

01 | 设计秒杀系统时应该注意的5个架构原则

  • 架构原则:“4 要 1 不要”前端

    • 数据要尽可能少
    • 请求数要尽可能少
    • 路径要尽可能短
    • 依赖要尽可能少
    • 不要有单点

架构是一种平衡的艺术,而最好的架构一旦脱离了它所适应的场景,一切都将是空谈。算法

02 | 如何才能作好动静分离?有哪些方案可选?

那到底什么才是动静分离呢?所谓“动静分离”,其实就是把用户请求的数据(如HTML页面)划分为“动态数据”和“静态数据”。数据库

  • 简单来讲,“动态数据”和“静态数据”的主要区别就是看页面中输出的数据是否和URL、浏览者、时间、地域相关,以及是否含有Cookie等私密数据。
  • 你应该把静态数据缓存到离用户最近的地方。静态数据就是那些相对不会变化的数据,所以咱们能够把它们缓存起来。缓存到哪里呢?常见的就三种,用户浏览器里、CDN上或者在服务端的Cache中。你应该根据状况,把它们尽可能缓存到离用户最近的地方。

如何作动静分离的改造浏览器

  • 咱们如何把动态页面改形成适合缓存的静态页面呢?缓存

    • URL惟一。商品详情系自然h就j以作到URL惟一化,好比每一个商品都由
      ID来标识,那么 http://item.xxx.com/item.htm?... 就能够做为惟一的URL标识。
    • 分离浏览者相关的因素。浏览者相关的因素包括是否已登陆,以及登陆身份等,这些相关因素咱们能够单独拆分出来,经过动态请求来获取。
    • 分离时间因素。服务端输出的时间也经过动态请求获取。
    • 异步化地域因素。详情页面上与地域相关的因素作成异步方式获取,固然你也能够经过动态请求方式获取,只是这里经过异步获取更合适。
    • 去掉Cookie,服务端输出的页面包含的Cookie能够经过代码软件来删除,如Web服务器Varnish能够经过unset req.http.cookie命令去掉Cookie.
  • 动态内容如何处理?服务器

    • ESI方案(或者SSI):即在Web代理服务器上作动态内容请求,并将请求插入到静态页面中,当用户拿到页面В已一个完整的页面了。这种方式对服务端性能有些影响,可是用户体验较好。
    • CSI方案。即单独发起一个异步JavaScript请求,以向服务端获取动态内容。这种方式服务端性能更佳,可是用户端页面可能会延时,体验稍差。

03 | 有针对性地处理好系统的热点数据

  • 发现热点数据cookie

    • 经过卖家报名的方式提早筛选出来,经过报名系统对这些热点商品进行打标。
    • 经过大数据分析来提早发现热点商品,好比咱们分析历史成交记录、用户的购物车记录,来发现哪些商品可能更热门、更好卖,这些都是能够提早分析出来的热点。
  • 怎么优化架构

    • 优化热点数据最有效的办法就是缓存热点数据,若是热点数据作了动静分离,那么能够长期缓存静态数据。可是,缓存热点数据更多的是"临时”缓存,即无论是静态数据仍是动态数据,都用一个队列短暂地缓存数秒钟,因为队列长度有限,能够采用LRU淘汰算法替换。
    • 再来讲说限制。限制更多的是一种保护机制,限制的办法也有不少,例如对被访问商品的ID作一致性Hash,而后根据Hash作分桶,每一个分桶设置一个处理队列,这样能够把热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使其余请求始终得不到服务器的处理资源。
    • 最后介绍一下隔离。秒杀系统设计的第一个原则就是将这种热点数据隔离出来,不要让1%的请求影响另外的99%,隔离出也更方便对这1%的请求作针对性的优化。

04 | 流量削峰这事应该怎么作?

对秒杀这个场景来讲,最终可以抢到商品的人数是固定的,就说100人和10000人发起请求的结果都同样,并发度越高,无效请求也越多。并发

可是从业务上来讲,秒杀活动是但愿更多的人来参与的,也就是开始以前但愿有更多的人来刷页面,但真正开始下单,秒请求并非越多越好。所以咱们能够设计一些规则,让并发的请求更多地延缓,并且咱们甚至能够过滤掉一些无效请求。异步

  • 有损方案

    • ip过滤,随机过滤,百分比过滤
  • 无损方案

    • 排队
    • 消息队列
    • 线程池加锁等待
    • 把请求序列化到文件中,而后再顺序地读文件(例如基于MySQL binlog的同步机制)来恢复请求

秒杀系统中的经常使用削峰方法

  • 答题

    • 这个重要的功能就是把峰值的下单请求拉长,从之前的1s以内延长到2s-10s。还能防止机器抢单。
  • 分层过滤

    • 对请求进行分层过滤,从而过滤掉一些无效的请求。
    • 浏览器层面:秒杀是否已经结束,答题是否正确
    • 缓存:商品状态是否正常,用户是否具备秒杀资格,库存判断
    • 数据:扣减库存

05 | 影响性能的因素有哪些?又该如何提升系统的性能?

  • 减小编码

    • 那么如何才能减小编码呢?例如,网页输出是能够直接进行流输出的,即用resp.getOutputstream() 函数写数据,把一些静态的数据提早转化成字节,等到真正往外写的时候再直接用OutputStream() 函数写,就能够减小静态数据的编码转换。
  • 减小序列化

    • 序列化大部分是在RPC中发生的,所以避免或者减小RPC就能够减小序列化,固然当前的序列化协议也已经作了不少优化来提高性能。有一种新的方案,就是能够将多个关联性比较强的应用进行"合并部署",而减小不一样应用之间的RPC也能够减小序列化的消耗。
    • 所谓"合并部署",就是把两个本来在不一样机器上的不一样应用合并部署到一台机器上,固然不只仅是部署在一台机器上,还要在同一个Tomcat容器中,且不能走本机的Socket,这样才能避免序列化的产生。
  • 并发读优化

    • 须要划分红动态数据和静态数据分别进行处理:
      像商品中的“标题"和"描述"这些自己不变的数据,会在秒杀开始以前全量推送到秒杀机器上,并一直缓存到秒杀结束;
    • 像库存这类动态数据,会采用"被动失效"的方式缓存必定时间(通常是数秒),失效后再去缓存拉取最新的数据。

06 | 秒杀系统“减库存”设计的核心逻辑

  • 下单减库存,即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接经过数据库的事务机制控制商品库存,这样必定不会出现超卖的状况。可是你要知道,有些人下完单可能并不会付款。
  • 付款减库存,即买家下单后,并不当即减库存,而是等到有用户付款后才真正减库存,不然库存一直保留给其余买家。但由于付款时才减库存,若是并发比较高,有可能出现买家下单后付т了款的状况,由于可能商品已经被其余人买走了。
  • 预扣库存,这种方式相对复杂一些,买家下单后,库存为其保留必定的时间(如10分钟),超过这个时间,库存将会自动释放,释放后其余买家就能够继续购买。在买家付款前,系统会校验该订单的库存是否还有保留:若是没有保留,则再次尝试预扣;若是库存不足(也就是预扣失败)则不容许继续付款;若是预扣成功,则完成付款并实际地减去库存。

针对“库存超卖”这种状况,在10分钟时间内下单的数量仍然有可能超过库存数量,遇到这种状况咱们只能区别对待:对普通的商品下单数量超过库存数量的状况,能够经过补货来解决;可是有些卖家彻底不容许库存为负数的状况,那只能在买家付款时提示库存不足。

实际使用方案
  • 目前来看,业务系统中最多见就是预扣库存方案,像你在买机票、买电影票时,下单后通常都有个“有效付款时间”,超过这个时间订单自动释放,这都是典型的预扣库存方案。而具体到秒杀这个场景,应该采用哪一种方案比较好呢?
  • 因为参加秒杀的商品,通常都是“抢到就是赚到”,因此成功下单后却不付款的状况比较少,再加上卖家对秒杀商品的库存有严格限制,因此秒杀商品采用“下单减库存”更加合理。另外,理论上因为“下单减库存”比“预扣库存”以及涉及第三方支付的“付款减库存”在逻辑上更为简单,因此性能上更占优点。
  • “下单减库存” 在数据一致性上,主要就是保证大并发请求时库存数据不能为负数,也就是要保证数据库中的库存字段值不能为负数,通常咱们有多种解决方案:

    • 一种是在应用程序中经过事务来判断,即保证减后库存不能为负数,不然就回滚;
    • 另外一种办法是直接设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时会直接执行SQL语句来报错;
    • 再有一就 用CASE WHEN判断语句,例如这样的SQL语句:

      • UPDATE item SET inventory= CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END
      • 相似 update order set inventory = inventory - xxx where inventory >= xxx

07 | 准备Plan B:如何设计兜底方案?

具体来讲,系统的高可用建设涉及架构阶段、编码阶段、测试阶段、发布阶段、运行阶段,以及故障发生时。

  • 架构阶段:架构阶段主要考虑系统的可扩展性和容错性,要避免系统出现单点问题。例如多机房单元化部署,即便某个城市的某个机房出现总体故障,仍然不会影响总体网站的运转。
  • 编码阶段:编码最重要的是保证代码的健壮性,例如涉及远程调用问题时,要设置合理的超时退出机制,防止被其余系统拖垮,也要对调用的返回结果集有预期,防止返回的结果超出程序处理范围,最多见的作法就是对错误异常进行捕获,对没法预料的错误要有默认处理结果。
  • 测试阶段:测试主要是保证测试用例的覆盖度,保证最坏状况发生时,咱们也有相应的处理流程。
  • 发布阶段:发布时也有一些地方须要注意,由于发布时最容易出现错误,所以要有紧急的回滚机制。
  • 运行阶段:运行时是系统的常态,系统大部分时间都会处于运行态,运行态最重要的是对系统的监控要准确及时,发现问题可以准确报警而且报警数据要准确详细,以便于排查问题。
  • 故障发生:故障发生时首先最重要的就是及时止损,例如因为程序问题致使商品价格错误,那就要及B下架商品或者关闭购买连接,防止形成重大资产损失。而后就是要可以及时恢复服务,并定位缘由解决问题。

针对秒杀系统,如何作到高可用?

  • 降级

    • 所谓"降级”,就是当系统的容量达到必定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。它是一个有目的、有计划的执行过程,因此对降级咱们通常须要有一套预案来配合执行。若是咱们把它系统化,就能够经过预案系统和开关系统来实现降级。
    • 降级方案能够这样设计:当秒杀流量达到5w/s时,把成交记录的获取从展现20条降级到只展现5条。“从20改到5"这个操做由一个开关来实现,也就是设置一个可以从开关系统动态获取的系统参数。
  • 限流

    • 客户端限流,好处能够限制请求的发出,经过减小发出无用请求从而减小对系统的消耗。缺点就是当客户端比较分散时,无法设置合理的限流阈值:若是阈值设的过小,会致使服务端没有达到瓶颈时客户端已经被限制;而若是的太大,则起т到限制的做用。
    • 服务端限流,好处是能够根据服务端的性能设置合理的阈值,而缺点就是被限制的请求都是无效的请求,处理这些无效的请求自己也会消耗服务器资源。
  • 拒绝服务

    • 在最前端的Nginx上设置过载保护,当机器负载达到某个值时直接拒绝HTTP请求并返回503错误码,在Java层一样也能够设计过载保护。
    • 拒绝服务能够说是一种不得已的兜底方案,用以防止最坏状况发生,防止因把服务器压跨而长时间完全没法提供服务。像这种系统过载保护虽然在过载时没法提供服务,可是系统仍然能够运做,当负载降低时又很容易恢复,因此每一个系统和每一个环节都应该设置这个兜底方案,对系统作最坏状况下的保护。
相关文章
相关标签/搜索