gitbook/手把手带你搭建秒杀系统/docs/422016.md
2022-09-03 22:05:03 +08:00

234 lines
17 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 02蓄势待发秒杀系统架构设计和环境准备
你好,我是志东,欢迎和我一起从零打造秒杀系统。
我们知道系统的设计是个由巨入细的过程想去设计好它那你首先得去了解清楚它。就像上节课我们对HTTP请求所走链路的介绍学完后你就会明白做秒杀系统设计时会用到哪些层级系统并且每个层级系统可以做什么事情。
今天我们要做的就是给每个层级系统做最合适的**技术选型和职能边界划分**,最终实现让各系统、技术做它们所擅长的事情,并在最后搭建起我们的开发依赖环境。
那如何给层级系统做技术选型和职能边界划分呢?我们通常都说,没有最好的技术,只有最契合当下业务场景的技术,所以我们得先了解一下,如果使用我们传统的架构系统来支持秒杀业务,可能会出现哪些问题。只有清楚了要面对的问题,我们才能做针对性的思考和优化。
所以这节课我们将重点分析传统架构设计的特点,接着介绍最新的秒杀系统架构,并做好技术选型和环境准备。
## 传统秒杀系统架构
下面先看一个大家常用的系统功能架构图:
![](https://static001.geekbang.org/resource/image/75/10/75c66bf2cb65bdbb125b06765d148c10.jpg?wh=1800x1575)
这种功能结构以及系统架构是我们非常熟悉的。在这种方式下Nginx只做反向代理和负载均衡甚至这层对我们做业务开发的研发人员来说都是无感知的一般运维同事在做生产环境搭建时都会帮我们配好。研发人员更多的是在开发Web服务和RPC服务我们把页面以及页面所依赖的静态资源都放到Web服务中同时Web服务还提供业务接口RPC服务提供一些支撑服务。
如果这是个ToB的运营管理系统这样没有什么问题因为请求量非常低系统基本不会有太大的负载。但是对于ToC且瞬时流量非常大的情况问题就会暴露出来那它究竟会有哪些问题呢
### 域名与带宽问题
我们从最基础的讲起。如果Web服务既提供H5页面、静态资源同时也提供业务接口这就意味着所有的请求使用的都是同一个域名在活动刚开始时大家都点击抢购按钮进结算页而结算页页面拉取静态资源会占用很多带宽资源。
这在活动开始的瞬间,带宽资源很稀缺的情况下,可能会出现用户进不了结算页,或者进了结算页却不能正常渲染页面的问题,导致抢购体验大幅下降。
### Web服务器性能问题
接着讲一个关键问题。我们一般部署Web服务都是使用Apache的Tomcat来部署的Tomcat在处理请求的时候是通过线程去处理的。
这样的问题就是如果瞬时的大量请求过来线程池中的线程不够用Tomcat就会瞬间新建很多线程直至达到配置的最大线程数如果线程数设置的过大这个过程可能会直接将机器的CPU打满导致机器死掉。即使没有挂掉在高负载下当设置的等待队列也满了之后后面的请求都会被拒绝连接直到有空出的资源去处理新请求。这时候你可能会想我加机器分摊流量不就行了可以是可以但由此增加的活动成本不知道你的老板会不会买单
当然了,这个过程中,还会伴有热点数据读写、库存超卖等问题,这些细节也非常重要,我会在后面的课程中一一给你展开说明。
## **新的秒杀系统架构**
上面我们谈到了传统系统架构的局限性,那么我们新的系统该如何设计才能避免出现以上问题呢?结合上节课对于各链路层级的介绍,我画了一张新的功能结构与系统架构图:
![](https://static001.geekbang.org/resource/image/6e/8e/6eaca4f202c11ca5fc4954512cda858e.jpg?wh=1800x1671)
首先新系统我们依然保留了HTTP服务常用的层级调用关系**Nginx->Web服务->RPC服务**,这也是绝大部分公司都会使用的一种系统结构。
其次将原先由Web服务提供的静态资源放到了CDNCDN是全国都有的服务器客户端可以根据所处位置自动就近从CDN上拉取静态资源速度更快来大大减轻抢购瞬时秒杀域名的负担。
最后,同时也是我们所做的最大改变,就是**将Nginx的职责放大**前置用来做Web网关承担部分业务逻辑校验并且增加黑白名单、限流和流控的功能这其实也是考虑到我们的秒杀业务特点所做的调整。这种在Nginx里写业务的做法在很多大公司里都是很常见的像京东是用来做商详、秒杀的业务网关美团用来做负载均衡接入层12306用来做车票查询等等他们的共同特点都是要面对高并发的业务场景这也说明在这种业务场景下我们的设计是得到了真实实践和广泛认可的。
而这么做的目的就是要充分利用Nginx的高并发、高吞吐能力并且非常契合我们秒杀业务的特点即入口流量大。但流量组成却非常的混杂这些请求中一部分是刷子请求一部分是无效请求传参等异常剩下的才是正常请求这个的比例可能是613所以需要我们在网关层尽可能多地接收流量进来并做精确地筛选将真正有效的3成请求分发到下游剩余的7成拦截在网关层。不然把这些流量都打到Web服务层Web服务再新起线程来处理刷子和无效请求这是种资源的浪费。
所以网关层对秒杀系统而言至关重要而Nginx刚好可以胜任此项任务。由此可见Nginx在我们的系统设计中扮演着非常重要的角色但你对Nginx也许没那么了解别急接下来我就给你简单介绍一下Nginx并带你解开Nginx在高并发下仍具有高性能的秘密。
### **Nginx介绍**
Nginx最早被发明出来就是来应对互联网高速发展下出现的并发几十万、上百万的网络请求连接场景的传统Apache服务器无法有效地解决这种问题而Nginx却具有并发能力强、资源消耗低的特性。
总的来说Nginx有5大优点即**模块化、事件驱动、异步、非阻塞、多进程单线程**。以下是Nginx的架构原理图
![](https://static001.geekbang.org/resource/image/3a/9b/3a811fe3cec72e5487e48a5f236f739b.jpg?wh=2406x1084)
Nginx是由一个master进程和多个worker进程可配置来配合完成工作的。其中master进程负责Nginx配置文件的加载和worker进程的管理工作而worker进程负责请求的处理与转发进程之间相互隔离互不干扰。同时每个进程中只有一个线程这就省去了并发情况下的加锁以及线程的切换带来的性能损耗。
**但一个线程能支持高并发的业务场景吗?**
这就要说到Nginx的工作模型。以Linux为例其采用的是epoll模型即事件驱动模型该模型是IO多路复用思想的一种实现方式是异步非阻塞的什么意思呢就是一个请求进来后会由一个worker进程去处理当程序代码执行到IO时比如调用外部服务或是通过upstream分发请求到后端Web服务时IO是阻塞的但是worker进程不会一直在这等着而是等IO有结果了再处理在这期间它会去处理别的请求这样就可以充分利用CPU资源去处理多个请求了。
这里你还可以思考这样一个问题Linux支持的以IO多路复用思想来实现的模型还有select和poll为什么选择了epoll呢**因为epoll的效率更高。**
举个例子刚刚我们上面说到worker在处理请求到IO时不会阻塞等待而是去干其他事情等IO有结果了再回头处理那worker进程怎么知道刚刚的IO处理完毕了呢
假设一个work process处理了1000个连接但其中只有10个IO完成了并可以继续往下执行select/poll的做法是遍历这1000个FDFile Description可以理解成每个建立了连接的一个标识找到那10个就绪状态的并把没做完的事情继续做完这样检索的效率明显很低。所以epoll的做法是当这10个IO准备就绪时通过系统的回调函数将FD放到一个专门的就绪列表中这样系统只需要去找这个就绪列表就可以了这大大提高了系统的响应效率。当然这只是epoll的其中一个优点具体三种模型的对比你可以自行去了解一下网上相关的资料有很多或者我们在留言区讨论也是可以的。
正是**多进程+事件驱动**的工作原理才使得Nginx具有非常良好的性能表现同时Nginx的模块化也能够支撑强大的第三方自定义工具模块让你的开发更加灵活自由。
### **OpenResty介绍**
我们知道Nginx的底层模块一般都是用C语言写的如果我们想在Nginx的基础之上写业务逻辑还得借助OpenResty它是Nginx的一个社区分支。这里我也简单介绍一下它。
OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台它使我们具备在Nginx上使用Lua语言来开发业务逻辑的能力并充分利用 Nginx 的非阻塞 IO 模型,来帮助我们非常方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
这里插一句,**为什么要用Lua语言来做Nginx开发呢**这就要说到Lua语言的特点了Lua的线程模型是单线程多协程的模式而Nginx刚好是单进程单线程天生的完美搭档。同时Lua是一种小巧的脚本语言语法非常的简单很容易学习掌握所以对于新语言你先不要有排斥心理我会在后面的课程中慢慢向你展示讲解。
## **Web/RPC服务技术选型**
以上我介绍完了Nginx服务层的技术选型同时也讲解了为什么这么选的原因下面就轮到Web服务和RPC服务了。
这里大的框架选择其实就没有太多要求了只要能提供我们需要的能力即可比如基础框架是使用SpringMVC还是SpringBoot持久层是喜欢用MyBatis还是JPA数据库是用MySQL还是Oracle这些都可以根据你的个人使用习惯或者所在公司的技术栈做灵活变通。
同时对于已经有秒杀系统但是想要做优化的情况也完全不用担心跟着我学习之后你只需将旧系统中的部分轻业务逻辑迁移到Nginx层来体量最大的业务逻辑代码基本都不用动的并且旧系统中的一些优化点也都有单独的技术来实现而这些都不需要太多的学习成本和迁移成本。
那么为了更好的本地开发教学,这里介绍一下**我所使用的技术栈**Web服务和RPC服务的基础框架都是使用SpringMVCRPC框架使用的是Dubbo数据库使用免费开源的MySQL分布式缓存数据库使用Redis这应该也是大多数公司会使用的技术栈。
在技术选型和层级系统职能划分都确定了之后,接下来就让我们动起手来,先把开发的依赖环境准备好。
### **环境准备**
以下是我在本地Mac上的安装方式如果你是其他系统可以找对应的安装方法对于程序员来说应该是没难度的。如果Mac上没有安装过Homebrew可以安装一下这是一个Mac软件的工具包很好用。以下操作都是在Mac的终端里输入相关命令来完成操作的。
### **Homebrew安装**
ruby -e “$(curl -fsSL [https://raw.githubusercontent.com/Homebrew/install/master/install)](https://raw.githubusercontent.com/Homebrew/install/master/install))”
### **OpenResty安装**
1. brew install openresty/brew/openresty
从Homebrew安装OpenResty。
2. export PATH=/usr/local/Cellar/openresty/1.19.3.2\_1/nginx/sbin:$PATH
安装完成之后,默认的安装位置在 /usr/local/Cellar/openresty/1.19.3.2\_1这时我们设置下环境变量即告诉终端输入的命令去哪里找
3. Nginx -V
然后就可以查看OpenResty是否安装成功。如下图执行红框内的命令出现绿框的输出内容即表示安装成功了。
![图片](https://static001.geekbang.org/resource/image/2a/d8/2a9d383d17932088c11281ac8d4605d8.png?wh=1920x668)
4. 然后我们就可以测试下Nginx是否好用所以我们在本地新建了个nginx.conf配置文件就放在/Users//Documents/seckillproject/nginx/conf下并且在nginx文件夹下新建logs文件夹用于log日志的输出新建后的文件目录结构如下
![图片](https://static001.geekbang.org/resource/image/93/69/934cb93ab4c052081a921ffb84966169.png?wh=1920x515)
nginx.conf的内容就用官方的模板输出个hello world
```plain
worker_processes  1;
error_log logs/error.log;
events {
    worker_connections 1024;
}
http {
    server {
        listen 8080;
        location / {
            default_type text/html;
            content_by_lua_block {
                ngx.say("<p>hello, world</p>")
            }
        }
    }
}
```
5\. cd /Users/wangzhangfei5/Documents/seckillproject/nginx
进入到我们新建的nginx文件夹下。
6. nginx -p \`pwd\`/ -c conf/nginx.conf
启动Nginx服务这时输入 \`ps -ef|grep nginx\` 可以查看起来Nginx进程有两个一个master 一个worker。
![图片](https://static001.geekbang.org/resource/image/6c/27/6c25ac6529f8afe884a2yyce84326427.png?wh=1920x182)
7. curl [http://localhost:8080](http://localhost:8080)
访问本地8080端口可以看到输出了"<p>hello, world</p>",也可以在浏览器输入 [http://localhost:8080](http://localhost:8080),看到 hello,world 的返回。
![图片](https://static001.geekbang.org/resource/image/af/25/afbf1ed0c3fyyb8bd759eb9d21d7c925.png?wh=1920x287)
8. nginx -p \`pwd\` -s stop
停止Nginx服务。
### **MySQL安装**
brew install [mysql@5.7](mailto:mysql@5.7)
也可以通过[官网](https://downloads.mysql.com/archives/community/)下载,根据系统版本,下载好对应包之后直接安装,完成安装之后可以测试下。
1. export PATH=[/usr/local/opt/mysql@5.7/bin:$PATH](mailto:/usr/local/opt/mysql@5.7/bin:$PATH)
默认的安装位置在 [/usr/local/opt/mysql@5.7](mailto:/usr/local/opt/mysql@5.7) 这时我们设置下环境变量(即告诉终端输入的命令去哪里找)。
2. mysql.server start
启动MySQL。
3. mysql\_secure\_installation
设置数据库密码按照对应的提示让选择Y/N时输入Y然后会让选择密码等级一共三个级别 0,1,2 强度由低到强选择后设置密码并记住刚设的密码。
4. mysql -uroot -p
登录数据库,这时会提示你输入密码,输入刚设置的密码即可进入。
5. show databases;
查看下当前的所有库到这里也说明我们的MySQL准备好了可以建库建表了。
![图片](https://static001.geekbang.org/resource/image/03/a5/034609767df8ecbe23bc6a74f1c4f6a5.png?wh=1920x979)
6. mysql.server stop
control+z退出MySQL后执行该命令关闭MySQL。
### **Redis安装**
brew install redis
安装完成之后 ,依次执行以下命令进行测试。
1. /usr/local/opt/redis/bin/redis-server /usr/local/etc/redis.conf
启动Redis服务端如下图绿框在安装好后提示我们如何启动Redis按照提示输入命令便可以看到启动成功等待连接。
![图片](https://static001.geekbang.org/resource/image/d3/3d/d3c99f1a374cd99635567c7c91a2083d.png?wh=1920x1223)
2. redis-cli -h 127.0.0.1 -p 6379
这时新建一个终端窗口模拟客户端连接Redis服务如下图所示可以set一个值并get查询出来说明Redis也正常安装成功并可以使用。
![图片](https://static001.geekbang.org/resource/image/11/51/117820fcf5f023d46312dae5530da851.png?wh=1920x502)
3. 服务端停止可以直接control+z退出也可以 sudo pkill redis-server 客户端断开连接 redis-cli shutdown。
到这我们主要的依赖环境都已经搭好了,而项目的搭建与开发,我会在下节课为你讲解。
## **小结**
在这节课里针对秒杀系统我们将传统的架构设计与我们新的架构设计做了一个对比可以看出传统架构设计的局限性其中仅列举了域名带宽问题和Tomcat服务器性能问题这也是我们从宏观上做技术选型时就需要去认真思考的问题。
而针对这两点我们也给出了我们的答案即利用Nginx在高并发下仍具有高性能的特性将Web网关职能前置尽量在流量入口处拦截掉风险流量以及缩短请求链路保护下游系统并提高服务的响应速度。
有了大的方向指导,我们便可以针对一些更细的技术点去做优化和设计,比如哪种限流算法更好,怎么能在高并发下保证库存不超卖等等这些。
同时在做技术选型时我也尽可能地使用了多数公司都在使用的技术栈以降低你的学习成本。但是用Lua语言来做Nginx业务开发或许还有不少同学是第一次用不过不着急后面跟着我慢慢学相信你自己一定可以或许这还将成为你比别人厉害的法宝
## 思考题
关于Tomcat的思考为什么Tomcat也支持NIO但性能却比Nginx差那么多呢
以上就是这节课的全部内容,欢迎你在评论区和我讨论问题,交流想法!