大型电商网站分布式秒杀系统设计三种姿势

简单来说,秒杀就是在同一时刻大量请求争抢购买同一商品并完成交易的过程 。
从架构视角来看,秒杀系统本质上是一个高性能、高一致、高可用的三高系统 。而打造并维护一个超大流量的秒杀系统需要进行哪些关注,就是本文讨论的话题 。
 
整体思考首先从高维度出发,整体思考问题 。秒杀无外乎解决两个核心问题,一是并发读,一是并发写,对应到架构设计,就是高可用、一致性和高性能的要求 。
关于秒杀系统的设计思考,本文即基于此 3 层依次推进,简述如下:

  • 高性能 。秒杀涉及高读和高写的支持,如何支撑高并发,如何抵抗高 IOPS?核心优化理念其实是类似的:高读就是尽量“少读”或"读少",高写就是数据拆分 。本文将从动静分离、热点优化以及服务端性能优化 3 个方面展开 。
  • 一致性 。秒杀的核心关注是商品库存,有限的商品在同一时间被多个请求同时扣减,而且要保证准确性,显而易见是一个难题 。如何做到既不多又不少?本文将从业界通用的几种减库存方案切入,讨论一致性设计的核心逻辑 。
  • 高可用 。大型分布式系统在实际运行过程中面对的工况是非常复杂的,业务流量的突增、依赖服务的不稳定、应用自身的瓶颈、物理资源的损坏等方方面面都会对系统的运行带来大大小小的的冲击 。如何保障应用在复杂工况环境下还能高效稳定运行,如何预防和面对突发问题,系统设计时应该从哪些方面着手?本文将从架构落地的全景视角进行关注思考 。
高性能动静分离
大家可能会注意到,秒杀过程中你是不需要刷新整个页面的,只有时间在不停跳动 。
这是因为一般都会对大流量的秒杀系统做系统的静态化改造,即数据意义上的动静分离 。
动静分离三步走:
  • 数据拆分
  • 静态缓存
  • 数据整合
①数据拆分
动静分离的首要目的是将动态页面改造成适合缓存的静态页面 。因此第一步就是分离出动态数据,主要从以下 2 个方面进行:
  • 用户 。用户身份信息包括登录状态以及登录画像等,相关要素可以单独拆分出来,通过动态请求进行获取;与之相关的广平推荐,如用户偏好、地域偏好等,同样可以通过异步方式进行加载 。
  • 时间 。秒杀时间是由服务端统一管控的,可以通过动态请求进行获取 。
这里你可以打开电商平台的一个秒杀页面,看看这个页面里都有哪些动静数据 。
②静态缓存
分离出动静态数据之后,第二步就是将静态数据进行合理的缓存,由此衍生出两个问题:
  • 怎么缓存
  • 哪里缓存
怎么缓存:静态化改造的一个特点是直接缓存整个 HTTP 连接而不是仅仅缓存静态数据 。
如此一来,Web 代理服务器根据请求 URL,可以直接取出对应的响应体然后直接返回,响应过程无需重组 HTTP 协议,也无需解析 HTTP 请求头 。
而作为缓存键,URL 唯一化是必不可少的,只是对于商品系统,URL 天然是可以基于商品 ID 来进行唯一标识的,比如淘宝的:
https://item.taobao.com/item.htm?id=xxxx
哪里缓存:静态数据缓存到哪里呢?
可以有三种方式:
  • 浏览器
  • CDN
  • 服务端
浏览器当然是第一选择,但用户的浏览器是不可控的,主要体现在如果用户不主动刷新,系统很难主动地把消息推送给用户(注意,当讨论静态数据时,潜台词是 “相对不变”,言外之意是 “可能会变”) 。
如此可能会导致用户端在很长一段时间内看到的信息都是错误的 。对于秒杀系统,保证缓存可以在秒级时间内失效是不可或缺的 。
服务端主要进行动态逻辑计算及加载,本身并不擅长处理大量连接,每个连接消耗内存较多,同时 Servlet 容器解析 HTTP 较慢,容易侵占逻辑计算资源;另外,静态数据下沉至此也会拉长请求路径 。
因此通常将静态数据缓存在 CDN,其本身更擅长处理大并发的静态文件请求,既可以做到主动失效,又离用户尽可能近,同时规避 JAVA 语言层面的弱点 。
需要注意的是,上 CDN 有以下几个问题需要解决: