1 秒杀存在的问题
对于一个日常平稳的业务系统,如果直接开通秒杀功能的话,往往会出现很多问题——
2 设计方向的思考
秒杀本质是要求一个瞬时高发下的承压系统,这也是其区别于其他业务的核心场景。对日常系统秒杀产生的问题逐一进行拆解分类,秒杀对应到架构设计,其实就是高可用、一致性和高性能的要求。关于秒杀系统的设计思考,本文即基于此 3 层依次推进,简述如下——
- 高性能。
秒杀涉及高读和高写的支持,如何支撑高并发,如何抵抗高IOPS?核心优化理念其实是类似的:高读就尽量"少读"或"读少",高写就数据拆分。本文将从动静分离、热点优化以及服务端性能优化
3 个方面展开 - 一致性。
秒杀的核心关注是商品库存,有限的商品在同一时间被多个请求同时扣减,而且要保证准确性,显而易见是一个难题。如何做到既不多又不少?本文将从业界通用的几种减库存方案切入,讨论一致性设计的核心逻辑 - 高可用。
大型分布式系统在实际运行过程中面对的工况是非常复杂的,业务流量的突增、依赖服务的不稳定、应用自身的瓶颈、物理资源的损坏等方方面面都会对系统的运行带来大大小小的的冲击。如何保障应用在复杂工况环境下还能高效稳定运行,如何预防和面对突发问题,系统设计时应该从哪些方面着手?本文将从架构落地的全景视角进行关注思考
高性能
1 动静分离
大家可能会注意到,秒杀过程中你是不需要刷新整个页面的,只有时间在不停跳动。这是因为一般都会对大流量的秒杀系统做系统的静态化改造,即数据意义上的动静分离。动静分离三步走:
- 1、数据拆分;
- 2、静态缓存;
- 3、数据整合。
1.1 数据拆分**
动静分离的首要目的是将动态页面改造成适合缓存的静态页面。因此第一步就是分离出动态数据,主要从以下 2 个方面进行:
- 用户。用户身份信息包括登录状态以及登录画像等,相关要素可以单独拆分出来,通过动态请求进行获取;与之相关的广平推荐,如用户偏好、地域偏好等,同样可以通过异步方式进行加载
- 时间。秒杀时间是由服务端统一管控的,可以通过动态请求进行获取
这里你可以打开电商平台的一个秒杀页面,看看这个页面里都有哪些动静数据。
1.2 静态缓存
分离出动静态数据之后,第二步就是将静态数据进行合理的缓存,由此衍生出两个问题:
- 1、怎么缓存;
- 2、哪里缓存
1.2.1 怎么缓存
静态化改造的一个特点是直接缓存整个 HTTP 连接而不是仅仅缓存静态数据,如此一来,Web 代理服务器根据请求 URL,可以直接取出对应的响应体然后直接返回,响应过程无需重组 HTTP 协议,也无需解析 HTTP 请求头。而作为缓存键,URL唯一化是必不可少的,只是对于商品系统,URL 天然是可以基于商品 ID 来进行唯一标识的,比如淘宝的 https://item.taobao.com/item…。
1.2.2 哪里缓存
静态数据缓存到哪里呢?可以有三种方式:
- 1、浏览器;
- 2、CDN ;
- 3、服务端。
浏览器当然是第一选择,但用户的浏览器是不可控的,主要体现在如果用户不主动刷新,系统很难主动地把消息推送给用户(注意,当讨论静态数据时,潜台词是 “相对不变”,言外之意是 “可能会变”),如此可能会导致用户端在很长一段时间内看到的信息都是错误的。对于秒杀系统,保证缓存可以在秒级时间内失效是不可或缺的。
服务端主要进行动态逻辑计算及加载,本身并不擅长处理大量连接,每个连接消耗内存较多,同时 Servlet 容器解析 HTTP 较慢,容易侵占逻辑计算资源;另外,静态数据下沉至此也会拉长请求路径。
因此通常将静态数据缓存在 CDN,其本身更擅长处理大并发的静态文件请求,既可以做到主动失效,又离用户尽可能近,同时规避 Java 语言层面的弱点。需要注意的是,上 CDN 有以下几个问题需要解决:
- 失效问题。任何一个缓存都应该是有时效的,尤其对于一个秒杀场景。所以,系统需要保证全国各地的 CDN 在秒级时间内失效掉缓存信息,这实际对
CDN 的失效系统要求是很高的 - 命中率问题。高命中是缓存系统最为核心的性能要求,不然缓存就失去了意义。如果将数据放到全国各地的 CDN
,势必会导致请求命中同一个缓存的可能性降低,那么命中率就成为一个问题 因此,将数据放到全国所有的 CDN
节点是不太现实的,失效问题、命中率问题都会面临比较大的挑战。
更为可行的做法是选择若干 CDN 节点进行静态化改造,节点的选取通常需要满足以下几个条件:
- 临近访问量集中的地区
- 距离主站较远的地区
- 节点与主站间网络质量良好的地区
基于以上因素,选择 CDN 的二级缓存比较合适,因为二级缓存数量偏少,容量也更大,访问量相对集中,这样就可以较好解决缓存的失效问题以及命中率问题,是当前比较理想的一种 CDN 化方案。部署方式如下图所示:
1.3 数据整合
分离出动静态数据之后,前端如何组织数据页就是一个新的问题,主要在于动态数据的加载处理,通常有两种方案:ESI(Edge Side Includes)方案和 CSI(Client Side Include)方案。
-
ESI 方案:Web
代理服务器上请求动态数据,并将动态数据插入到静态页面中,用户看到页面时已经是一个完整的页面。这种方式对服务端性能要求高,但用户体验较好 -
CSI 方案:Web 代理服务器上只返回静态页面,前端单独发起一个异步 JS 请求动态数据。这种方式对服务端性能友好,但用户体验稍差
1.4 小结
动静分离对于性能的提升,抽象起来只有两点,一是数据要尽量少,以便减少没必要的请求,二是路径要尽量短,以便提高单次请求的效率。具体方法其实就是基于这个大方向进行的。