通用秒杀架构梳理)
本文为「电商秒杀架构」系列第二篇从传统架构讲起逐步引出常见的秒杀系统架构并给出电商项目的技术选型与核心「层层错峰」设计思路最后对关键组件 OpenResty 的简介与原理做补充说明。一、一般性系统架构系统的设计是个由巨入细的过程想去设计好它你首先得去了解清楚它。下面先看一个大家常用的系统功能架构图这种功能结构以及系统架构是我们非常熟悉的。很多时候Nginx 只做反向代理和负载均衡甚至这层对大部分做业务开发的研发人员来说都是无感知的一般运维部门在做生产环境搭建时都会配好。研发人员更多的是在开发 Web 服务和其他 RPC 服务/微服务我们把页面以及页面所依赖的静态资源都放到 Web 服务中同时 Web 服务还提供业务接口RPC 服务提供一些支撑服务。当然像我们商城进行动静分离后VUE 前端部分也会放在 Nginx 上这就变成了页面以及页面所依赖的静态资源也在 Nginx 上Web 服务提供业务接口这种模式相比上面的有所改进。1.1 页面访问商城进行了动静分离商详页实现在product.vue中。可以看到每个商品都会去后端获得商品的详细信息并展示可以想到这种实现的商详页在秒杀高并发的情况下不做任何措施会对后端服务特别是产品服务和数据库造成非常大的访问压力即使产品信息全部缓存依然会消耗大量的后端资源和带宽。1.2 Web 服务器性能问题我们一般部署 Web 服务都是使用 Tomcat 来部署的Tomcat 在处理请求的时候是通过线程去处理的。这样的问题就是如果瞬时的海量请求过来线程池中的线程不够用Tomcat 就会瞬间新建很多线程直至达到配置的最大线程数如果线程数设置过大这个过程可能会直接将机器的 CPU 打满导致机器死掉。即使没有挂掉在高负载下当设置的等待队列也满了之后后面的请求都会被拒绝连接直到有空出的资源去处理新请求。这时候你可能会想我加机器分摊流量不就行了可以是可以但由此增加的活动成本有可能超出预算。除此之外还会伴有类似读写热点、库存超卖等等问题这些我们会一一处理。二、常见的秒杀系统架构结合秒杀各链路层级常见的大厂秒杀功能结构与系统架构图如下看起来似乎和一般的系统架构没什么区别但是仔细研究区别还是很大的一般情况下原先由 Web 服务或 Nginx 服务提供的静态资源放到了CDNCDN 是全国都有的服务器客户端可以根据所处位置自动就近从 CDN 上拉取静态资源速度更快来大大减轻抢购瞬时秒杀域名的负担。同时所做的最大改变就是将Nginx 的职责放大前置用来做 Web 网关承担部分业务逻辑校验并且可能增加黑白名单、限流和流控的功能。这其实也是根据秒杀业务特点所做的调整。这种在 Nginx 里写业务的做法在很多大公司里都很常见像京东是用来做商详、秒杀的业务网关美团用来做负载均衡接入层12306 用来做车票查询等等。这么做的目的就是要充分利用 Nginx 的高并发、高吞吐能力并且非常契合秒杀业务的特点即入口流量大但流量组成却非常混杂。这些请求中一部分是刷子请求一部分是无效请求传参等异常剩下的才是正常请求一般情况下这个比例可能是6 : 1 : 3。所以需要在网关层尽可能多地接收流量进来并做精确地筛选将真正有效的 3 成请求分发到下游剩余的 7 成拦截在网关层。不然把这些流量都打到 Web 服务层Web 服务再新起线程来处理刷子和无效请求这是一种资源的浪费。所以网关层对秒杀系统而言至关重要而 Nginx 刚好可以胜任此项任务因此 Nginx 在主要的秒杀系统设计中扮演着非常重要的角色。三、电商项目技术选型在做具体技术选型时我们会尽量选择大家比较熟悉的技术路线。电商项目整体的秒杀系统技术选型如下核心设计是对巨大的瞬间流量进行层层错峰。错峰 1页面静态化。动静分离的本质是将包含浏览者信息的动态数据和不包含浏览者信息的静态资源区分开。例如在商品单品页商品信息是不包含浏览者信息的这部分就可以抽象出静态资源而用户登录状态、cookie 等这些动态数据也尽可能缓存起来并且使缓存能够离用户更近。具体实现时电商项目采用 Freemarker 模板引擎实现。错峰 2秒杀前答题。秒杀答题的形式可以是多种多样的目的是防止机器刷单以及错开用户的下单时长。在秒杀场景下答题速度靠后的请求自然就没有库存了也可以减少系统的请求量。具体实现时电商项目通过采用 HappyCaptcha 添加动态验证码来实现。错峰 3Redis 扣减库存。Redis 缓存的作用主要有两个一是快速扣减库存保护数据库流量并且库存扣减完成后快速通知 Nginx屏蔽后续请求二是提前识别热点数据并且针对热点数据提供优化处理。处理的方案主要是三个一是优化二是限制三是隔离包括业务隔离、系统隔离、数据隔离。错峰 4Nginx 快速通知秒杀结束。当秒杀的商品抢购完成后在网关层进行流量管控直接拒绝后续的下单请求从而保护后端服务。具体实现时电商项目通过引入 OpenResty 提供对 Nginx 的服务增强。错峰 5引入 MQ 进行流量削峰。通过 MQ 对前端并发请求进行削峰处理从而减少瞬间流量对后端服务器造成的压力。错峰 6引入 MQ 进行下单服务异步化。后端服务完成下单业务后只需要将下单消息发到 MQ而不用再关心其他下游业务这样也能减轻后端下单服务的压力。这个技术路线中大部分技术我们都在课程中进行过分享唯一有些朋友比较陌生的是在 Nginx 网关层我们采用了OpenResty来进行构建。关于电商项目的具体实现我们会在后面章节做具体介绍这里先给大家简单介绍一下 OpenResty。四、OpenResty4.1 简介Nginx 早被发明出来就是来应对互联网高速发展下出现的并发几十万、上百万的网络请求连接场景的传统 Apache 服务器无法有效地解决这种问题而 Nginx 却具有并发能力强、资源消耗低的特性。总的来说Nginx 有 5 大优点即模块化、事件驱动、异步、非阻塞、多进程单线程。前面我们说过Nginx 在主要的秒杀系统设计中扮演着非常重要的角色意味着 Nginx 上要承载很多的业务逻辑。Nginx 的底层模块一般都是用 C 语言写的如果我们想在 Nginx 的基础之上写业务逻辑会很不方便所以这个时候我们还得借助OpenResty——它是 Nginx 的一个社区分支。OpenResty 是中国人章亦春发起最早是雅虎中国的一个公司项目基于 Perl 和 Haskell 实现2007 年开始开源后来章亦春加入淘宝后进行了彻底的设计和重写。按照官网的说法OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。OpenResty 通过汇聚各种设计精良的 Nginx 模块主要由 OpenResty 团队自主开发从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。为什么要使用 Lua 语言来做 Nginx 开发呢这就要说到 Lua 语言的特点了Lua 的线程模型是单线程多协程的模式而 Nginx 刚好是单进程单线程天生的完美搭档。同时 Lua 是一种小巧的脚本语言语法非常简单所以在 Redis 中也是用 Lua 作为脚本语言的。Lua 语言请自行学习推荐两本豆瓣评分 8 分以上的书籍或至于 OpenResty 的安装等知识请参考 OpenResty 中文官网https://openresty.org/cn/4.2 原理既然要使用 OpenResty我们还是要大概了解下 Nginx 和 OpenResty 中的一些基本原理。Nginx 服务器启动后产生一个 Master 进程Master ProcessMaster 进程执行一系列工作后产生一个或者多个 Worker 进程Worker Processes。其中Master 进程用于接收来自外界的信号并向各 Worker 进程发送信号同时监控 Worker 进程的工作状态。当 Worker 进程退出后异常情况下Master 进程也会自动重新启动新的 Worker 进程。Worker 进程则是外部请求真正的处理者。多个 Worker 进程之间是对等的他们同等竞争来自客户端的请求各进程互相之间是独立的。一个请求只可能在一个 Worker 进程中处理一个 Worker 进程不可能处理其它进程的请求。Worker 进程的个数是可以设置的一般我们会设置与机器 CPU 核数一致。同时Nginx 为了更好地利用多核特性具有 CPU 绑定选项我们可以将某一个进程绑定在某一个核上这样就不会因为进程的切换带来 cache 的失效CPU affinity。所有的进程都是单线程即只有一个主线程的进程之间通信主要是通过共享内存机制实现的。OpenResty 本质上是将 LuaJIT 的虚拟机嵌入到 Nginx 的管理进程和工作进程中同一个进程内的所有协程都会共享这个虚拟机并在虚拟机中执行 Lua 代码。在性能上OpenResty 接近或超过 Nginx 的 C 模块而且开发效率更高。Nginx 将 HTTP 请求的处理过程划分为多个阶段这样可以使一个 HTTP 请求的处理过程由很多模块参与处理每个模块只专注于一个独立而简单的功能处理可以使性能更好、更稳定同时拥有更好的扩展性。11 个 HTTP 处理阶段如下阶段名称说明1ngx_http_post_read_phase接收到完整的 http 头部后处理的阶段位于 uri 重写之前2ngx_http_server_rewrite_phaseuri 与 location 匹配前修改 uri 的阶段用于重定向3ngx_http_find_config_phase根据 uri 寻找匹配的 location 块配置项阶段可能被多次执行4ngx_http_rewrite_phaselocation 级别的 uri 重写阶段可能被多次执行5ngx_http_post_rewrite_phase防止重写 url 后导致死循环的阶段6ngx_http_preaccess_phase访问权限控制前的准备阶段一般用于限制访问频率、链接数等7ngx_http_access_phase访问权限控制阶段如基于 IP 黑白名单、用户名密码的权限控制8ngx_http_post_access_phase访问权限控制后的阶段根据权限控制结果进行处理9ngx_http_try_files_phase为访问静态文件资源而设置未配置 try_files 则跳过10ngx_http_content_phase处理 http 请求内容的阶段产生响应并发送到客户端11ngx_http_log_phaselog 阶段记录访问量/统计平均响应时间其中http 无法介入的阶段有 4 个ngx_http_find_config_phase、ngx_http_post_rewrite_phase、ngx_http_post_access_phase、ngx_http_try_files_phase。Nginx 的 content 阶段是所有请求处理阶段中最重要的一个因为运行在这个阶段的配置指令一般都肩负着生成「内容」content并输出 HTTP 响应的使命。标准模块ngx_access、第三方模块ngx_auth_request以及第三方模块ngx_lua的access_by_lua指令就运行在 access 阶段log_by_lua处理完请求后的日志记录阶段。OpenResty 在 HTTP 处理阶段基础上分别在 Rewrite/Access 阶段、Content 阶段、Log 阶段注册了自己的 handler加上系统初始阶段 master 的两个阶段共 11 个阶段为 Lua 脚本提供处理介入的能力。Nginx 的 Lua 挂载点如下常用挂载点说明init_by_luaMaster 进程加载 Nginx 配置文件时运行一般用来注册全局变量或者预加载 Lua 模块。init_worker_by_lua每个 worker 进程启动时执行通常用于定时拉取配置/数据或者进行后端服务的健康检查。set_by_lua变量初始化。rewrite_by_lua可以实现复杂的转发、重定向逻辑。access_by_luaIP 准入、接口权限等情况集中处理。content_by_lua内容处理器接收请求处理并输出响应。header_filter_by_lua响应头部或者 cookie 处理。body_filter_by_lua对响应数据进行过滤如截断或者替换。log_by_lua会话完成后本地异步完成日志记录。参考资料有道云笔记 https://note.youdao.com/s/6Uej1qWF