秒杀系统设计(一) 概述和原则

Posted by ZhouJ000 on October 14, 2018
最后更新于:2018-10-15

秒杀系统设计(一) 概述和原则
秒杀系统设计(二) 高性能01-动静分离与热点缓存
秒杀系统设计(三) 高性能02-流量削峰与服务端优化
秒杀系统设计(四) 一致性-库存
秒杀系统设计(五) 高可用-兜底方案

最近被问到几次秒杀系统的设计,对于这块几乎没有经验,所以回来后准备研究一下秒杀系统的设计。说道秒杀,第一印象就是双十一和过年抢票。主要就是一瞬间的大量请求,需要并发完成逻辑处理与读写

秒杀特点

秒杀系统本质上就是一个满足大并发、高性能和高可用的分布式系统

  • 秒杀时大量用户会在同一时间同时进行抢购,网站瞬时访问流量激增
  • 秒杀一般是访问请求数量远远大于库存数量,只有少部分用户能够秒杀成功,也就是读远远大于写
  • 秒杀业务流程比较简单,一般就是下订单减库存
  • 数据库的并发读写冲突以及资源的锁请求冲突非常严重

架构原则

  1. 数据要尽量少
    • 用户请求的数据能少就少,包括上传给系统的数据和系统返回给用户的数据
      • 网络上传输需要时间
      • 需要服务器做处理,而服务器在写网络时通常都要做压缩和字符编码,这些都非常消耗CPU
    • 系统依赖的数据能少就少,包括系统完成某些业务逻辑需要读取和保存的数据
      • 调用其他服务会涉及数据的序列化和反序列化,而这也是CPU的一大杀手,同样也会增加延时
      • 和数据库打交道,数据库本身也容易成为一个瓶颈
  2. 请求数要尽量少
    • 用户请求的页面返回后,浏览器渲染这个页面还要包含其他的额外请求
      • 页面依赖的CSS/JavaScript、图片,以及Ajax请求等等
      • 减少请求数最常用的一个实践就是合并CSS和JavaScript文件,把多个JavaScript文件合并成一个文件,在URL中用逗号隔开
  3. 路径要尽量短
    • 是用户发出请求到返回数据这个过程中,需求经过的中间的节点数。这些节点可以表示为一个系统或者一个新的Socket连接
      • 每经过一个节点,一般都会产生一个新的Socket连接。都会增加新的不确定性
    • 缩短请求路径不仅可以增加可用性,同样可以有效提升性能(减少中间节点可以减少数据的序列化与反序列化),并减少延时(可以减少网络传输耗时)
      • 缩短路径的一种方法就是多个相互强依赖的应用合并部署在一起,把远程过程调用(RPC)变成JVM内部之间的方法调用
  4. 依赖要尽量少
    • 指的是要完成一次用户请求必须依赖的系统或者服务,这里的依赖指的是强依赖
      • 例如要展示秒杀页面,而这个页面必须强依赖商品信息、用户信息,还有其他如优惠券、成交列表等这些对秒杀不是非要不可的信息(弱依赖),这些弱依赖在紧急情况下就可以去掉
    • 要减少依赖,我们可以给系统进行分级,比如0级系统、1级系统、2级系统。0级系统要尽量减少对1级系统的强依赖,防止重要的系统被不重要的系统拖垮
      • 例如支付系统是0级系统,而优惠券是1级系统的话,在极端情况下可以把优惠券给降级,防止支付系统被优惠券这个1级系统给拖垮
  5. 不要有单点
    • 系统中的单点可以说是系统架构上的一个大忌,因为单点意味着没有备份,风险不可控,我们设计分布式系统最重要的原则就是“消除单点”
    • 如何避免单点
      • 关键点是避免将服务的状态和机器绑定,即把服务无状态化,这样服务就可以在机器中随意移动
    • 如何把服务的状态和机器解耦
      • 例如把和机器相关的配置动态化,这些参数可以通过配置中心来动态推送,在服务启动时动态拉取下来,我们在这些配置中心设置一些规则来方便地改变这些映射关系
    • 像存储服务本身很难无状态化
      • 因为数据要存储在磁盘上,本身就要和机器绑定,那么这种场景一般要通过冗余多个备份的方式来解决单点问题

架构是一种平衡的艺术,而最好的架构一旦脱离了它所适应的场景,一切都将是空谈。这里说的几点都只是一个个方向,我们应该尽量往这些方向上去努力,但也要考虑平衡其他因素

不同场景下的不同架构案例

快速构建

只需要把你的商品购买页面增加一个“定时上架”功能,仅在秒杀开始时才让用户看到购买按钮,当商品的库存卖完了也就结束了

请求量升级

随着请求量的加大(比如从1w/s到了10w/s的量级),这个简单的架构很快就遇到了瓶颈,因此需要做架构改造来提升系统性能

  1. 把秒杀系统独立出来单独打造一个系统,这样可以有针对性地做优化
    • 例如这个独立出来的系统就减少了店铺装修的功能,减少了页面的复杂度
  2. 在系统部署上也独立做一个机器集群,这样秒杀的大流量就不会影响到正常的商品购买集群的机器负载
  3. 将热点数据(如库存数据)单独放到一个缓存系统中,以提高“读性能”
  4. 增加秒杀答题,防止有秒杀器抢单

秒杀详情成为了一个独立的新系统,另外核心的一些数据放到了缓存(Cache)中,其他的关联系统也都以独立集群的方式进行部署 seckill-01

请求量继续升级

随着请求量超过100w/s,为了进一步提升秒杀系统的性能,还需要对架构做进一步升级

  1. 对页面进行彻底的动静分离,使得用户秒杀时不需要刷新整个页面,而只需要点击抢宝按钮,借此把页面刷新的数据降到最少
  2. 在服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获取数据,甚至不需要去公共的缓存集群中查询数据
    • 这样不仅可以减少系统调用,而且能够避免压垮公共缓存集群
  3. 增加系统限流保护,防止最坏情况发生

这里我们对页面进行了进一步的静态化,秒杀过程中不需要刷新整个页面,而只需要向服务端请求很少的动态数据。而且最关键的详情和交易系统都增加了本地缓存,来提前缓存秒杀商品的信息,热点数据库也做了独立部署 seckill-02

其实越到后面需要定制的地方越多,也就是越“不通用”。例如,把秒杀商品缓存在每台机器的内存中,这种方式显然不适合太多的商品同时进行秒杀的情况,因为单机的内存始终有限。所以要取得极致的性能,就要在其他地方(比如,通用性、易用性、成本等方面)有所牺牲

针对前端

  1. 扩容: 通过增加前端池的整体承载量来抗峰值
  2. 静态化: 将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素。通过CDN来抗峰值
  3. 限流: 一般都会采用IP级别的限流,即针对某一个IP,限制单位时间内发起请求数量。或者活动入口加入答题进行消峰操作
  4. 有损服务: 在接近前端池承载能力的水位上限的时候,随机拒绝部分请求来保护活动整体的可用性

针对后端

  1. 请求分发与请求预处理: 使用Nginx或Apache将用户的请求分发到不同的机器上;判断商品是不是还有剩余来决定是不是要处理该请求,将请求拦截在系统上游,降低下游压力
  2. 内存缓存: 典型的读多写少的应用场景
  3. 排队: 每次只透过有限的写请求去数据层

参考:
如何设计一个秒杀系统
秒杀系统架构分析与实战
描述一个高性能高可靠的网站架构——如何设计一个秒杀系统