Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说前端抢饭碗系列之深入Nginx[通俗易懂],希望能够帮助你!!!。
在大部分童鞋的印象里,Nginx应该都是属于后台工作的范畴,前端只要写好页面就好了;然后随着大前端范围的不断扩展,前端也在不断的进军服务器领域,而Nginx就是进军服务器领域必备的技能之一;以前我们都需要“低声下气”的让后端的同事给我们配置页面域名,但是学会了Nginx配置,域名配置、代理转发什么的完全就可以我们自己来了,这样抢来的饭碗它。。。。它难道不香吗?
本文首发于公众号
【前端壹读】
,更多精彩内容敬请关注公众号最新消息。
那么Nginx到底是什么,首先我们来看一下百度百科对Nginx的定义:
Nginx是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。
这里有三个词很关键,我们来拆解一下,分别是是高性能、反向代理和web服务器;首先这个web服务器自不用多说,像我们熟知的Apache、IIS、Tomcat等都是web服务器;然后是高性能,一个服务器的性能自然是网站开发者最为关心的,那么服务器的性能如何来进行衡量呢?一般可以通过CPU和内存的使用量来进行衡量。经过笔者简单的并发测试,在20000个并发链接时,CPU和内存占用也非常低,CPU仅占5%,内存占用也才2MB不到。
我们可以通过一个web压力测试工具Apache Bench
,对Nginx进行简单的压力测试;通过在命令行ab -n 20000 -c 10000 [url]
,我们对Nginx的首页发起请求总数为20000,并发数为10000的请求测试,测试结果如下:
我们看到总的请求时间(Time taken for tests)是25秒,平均每个请求耗时(Time per request)1.25毫秒,在这么高的并发量下面,服务器响应性能还是挺不错的。
然后是反向代理,与之对应的就是正向代理,这两者的区别也是面试中经常被问到的。我们先来看一下什么是正向代理,一个正向代理最典型的例子就是我们常用的“梯子”。
我们直接访问Google,是访问不到的,但是如果我们使用了代理服务器,那么通过访问代理服务器就可以浏览Google,这里的代理服务器就属于正向代理
;通过正向代理我们可以访问原来无法访问的资源。
那么什么是反向代理呢?反向代理最典型的例子就是我们的Nginx服务器了;比如我们在访问某个网站时,由代理服务器去目标服务器获取数据后返回给客户端,这样就能够隐藏真实服务器的IP地址,只对外开放代理服务器,以防止外网对内网服务器的恶性攻击。
理解了上面两个典型的案例,相信大家对正向反向代理也了解了,我们总结一下:
Nginx安装程序分为Linux版和Windows版,Windows版本的Nginx下载解压后就可以直接运行了,而Linux版本的需要make、configure等命令编译安装,好处是可以方便灵活的编译不同的模块到Nginx;网上也有很多的安装教程,这里就不再赘述了,可以从官网下载适合自己的版本,下载好后我们来看一下他的目录结构:
├── conf #所有配置文件的目录
├── nginx.conf #主配置文件
├── mime.types #媒体类型控制文件
├── contrib #存放一些实用工具
├── docs #文档资料
├── html #默认解析的静态文件目录
├── logs #日志目录
├── sbin #启动运行程序
我们经常用到的就是conf目录和html目录;而在根目录可以运行常用的一些命令对Nginx进行操作控制:
nginx -s reopen #重启Nginx
nginx -s reload #重新加载Nginx配置文件,然后以优雅的方式重启Nginx
nginx -s stop #强制停止Nginx服务
nginx -s quit #优雅地停止Nginx服务(即处理完所有请求后再停止服务)
nginx -h #打开帮助信息
nginx -v #显示版本信息并退出
nginx -V #显示版本和配置选项信息,然后退出
nginx -t #检测配置文件是否有语法错误,然后退出
nginx -T #检测配置文件是否有语法错误,转储并退出
nginx -q #在检测配置文件期间屏蔽非错误信息
nginx -p prefix #设置前缀路径(默认是:/usr/share/nginx/)
nginx -c filename #设置配置文件(默认是:/etc/nginx/nginx.conf)
nginx -g directives #设置配置文件外的全局指令
killall nginx #杀死所有nginx进程
我们看前四个命令会发现,这四个命令可以分为两种,重启和停止Nginx,不过一种是强制的方式,另一种是优雅的方式;强制的方式就是让Nginx立即停止当前处理的所有请求,丢弃链接,停止工作;而优雅的方式是允许Nginx将当前正在处理的请求处理完成,但是不再接收新的请求,所有处理完成后再停止工作。
我们再来看一下主要配置文件nginx.conf的基本结构:
# nginx进程数,建议设置为等于CPU总核心数
worker_processes 1;
# 进程文件
pid logs/nginx.pid;
# 单个进程最大连接数
events {
worker_connections 1024;
}
http {
# 文件扩展名与类型映射表
include mime.types;
# 默认文件类型
default_type application/octet-stream;
# 开启gzip压缩
gzip on;
sendfile on;
keepalive_timeout 65;
server {
# 监听端口
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
}
配置文件中主要可以分为以下几个块:
很多时候,我们不会将所有的配置全都写在一个主配置文件,因为这样会显得冗长,也不知道每个模块是做什么用的;而是会根据项目来拆分多个配置文件,每个配置文件彼此独立,互不干扰,然后在主配置文件中引入;我们在conf目录下新建一个projects目录,然后可以新建多个.conf配置文件:
# /conf/projects/home.conf
server {
listen 8080;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
server {
listen 8081;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
然后在主配置nginx.conf中将projects目录下的所有配置文件引入:
http {
include mime.types;
default_type application/octet-stream;
gzip on;
sendfile on;
keepalive_timeout 65;
## 引入projects目录下所有的配置文件
include projects/*.conf;
}
这样我们可以直接在projects目录下新增.conf后缀的配置文件,而不用修改主配置文件;但是我们修改完还不能确定是否会有错误,可以通过命令对配置文件进行检测:
nginx -t
#nginx: the configuration file nginx/conf/nginx.conf syntax is ok
#nginx: configuration file nginx/conf/nginx.conf test is successful
通过检测发现没有任何报错,就可以优雅的重启服务器了:
nginx -s reload
作为一个web服务器,最重要的就是能够对静态资源提供访问服务,我们的Nginx服务器可以用来托管一些静态的资源,比如js、css、图片等,访问某一特定的静态资源路径时会转发到本地目录文件上;那么我们就来看Nginx是如何一步一步的通过域名配置、URI配置以及目录配置来命中请求的。
在上面的配置中,我们主要是将server_name
设置为localhost
,但是这样仅能让局域网内的主机访问到;我们想要让广域网上的其他主机访问,可以将server_name
匹配域名,它的参数值可以是以下几种:
*.my.com或者www.my.*
~
作为正则表达式字符串的开始标记,如~^www\d+\.my\.com$
在上面正则表达式中,^
表示以www开头,紧跟一个或多个数字(\d+),然后跟上域名my.com,最后以$
结尾;因此上面的表达式可以匹配的域名比如www1.my.com,但是www.my.com就不行。
正则表达式还支持字符串捕获功能,即将正则表达式匹配成功的名称中的一部分字符串截取出来,放在变量中供后面使用;比如将server_name进行如下设置:
server {
listen 80;
server_name ~^(.+)?\.my\.com$;
location / {
root /usr/share/nginx/html/$1;
index index.html index.htm;
}
}
这样,通过二级域名home.my.com到达Nginx时,被server_name
正则表达式捕获,将其中的home
字符串存入$1
变量中,我们在/usr/share/nginx/html/home
目录下的静态资源就能通过home.my.com域名来访问了;我们服务器的目录就可以是这样的:
/usr/share/nginx/html/
|- home
|- index.html
|- blog
|- index.html
|- mail
|- index.html
|- photo
|- index.html
这样就只需要一个server块来完成多个站点的配置。
nginx允许一个虚拟主机有多个域名,因此我们可以给server_name同时配置多个域名,多个之间以空格分隔:
server {
listen 80;
server_name a.com b.com c.com;
# ...其他配置
}
由于server_name支持以上三种配置方式,如果出现多个server块同时匹配了相同的域名,那么这个请求交给哪个server呢?因此优先级顺序如下:
如果我们想让局域网内的设备访问nginx,可以将server_name
设置ip地址的方式:
server {
listen 80;
server_name localhost 192.168.1.101;
}
如果还不能访问,可以查看下是否是防火墙的原因,在防火墙允许通过的应用中将Nginx勾选(没有找到Nginx可以点击允许其他应用
进行新增):
有时候我们还会见到将server_name设置为_
(下划线),意味着server_name为空,即匹配全部的主机;我们可以配置host,将a.com、b.com和c.com都指向本机,然后配置nginx:
server {
listen 80;
server_name _;
location / {
root html;
index index.html index.htm;
}
}
这样我们不仅可以通过域名a.com、b.com、c.com来访问,也能通过ip的方式。
location用于匹配不同的URI请求,它的语法如下:
location [ = | ~ | ~* | ^~ ] uri { ... }
location @/name/ { … }
这里的uri
就是待匹配的请求字符串,可以是不含正则的字符串,比如/home
,称为标准URI
;也可以是包含正则的字符串,比如\.html$
(表示以.html结尾),称为正则URI
。而方括号中的四种匹配符都是可选的,用来改变请求字符串与URI的匹配方式,我们来看下四种匹配符的解释:
匹配符 | 解释 |
---|---|
不填 | location后没有参数,直接跟着标准URI,表示前缀匹配,代表跟请求中的URI从头开始匹配 |
= | 用于标准URI前,要求请求字符串与其精准匹配,成功则立即处理,nginx停止搜索其他匹配 |
^~ | 用于标准URI前,要求一旦匹配就会立即处理,不再去匹配其他正则URI,一般用来匹配目录 |
~ | 用于正则URI前,表示URI包含正则表达式,区分大小写 |
~* | 用于正则URI前,表示URI包含正则表达式,不区分大小写 |
@ | 定义一个命名的location,@定义的location名字一般用在内部定向 |
我们来看下每种匹配规则能匹配的url,首先不填代表的话表示前缀匹配,如果我们有多个相似的前缀匹配:
location /pre/fix {
# ...
}
location /pre {
# ...
}
对于请求/pre/fix/home
,根据最大匹配原则,匹配第一个location。
然后是=
,要求路径完全匹配:
location = /abc {
# ...
}
# /abc 匹配
# /abcde 不匹配
# /abc/ 不匹配,带有结尾的/
# /cde/abc不匹配
其次是^~
最佳匹配,它的优先级高于正则表达式:
location ^~ /login {
# ...
}
# /login 匹配
# /loginss 匹配
# /login/ 匹配
# /home/login 不匹配
接着是~
正则表达式匹配,它区分大小写匹配(注意:windows版本nginx不区分):
location ~ \.(gif|jpg|png|js|css)$ {
# ...
}
# /bg.png 匹配
# /bg.PNG 不匹配
# /bg.png?a=1 匹配
# /bg.jpeg 不匹配
~*
同样也是正则匹配,只不过它不区分大小写,这里就不再演示。
如果我们的URI匹配到了多个location,其并不完全按照在配置文件中出现的顺序来进行匹配,URI会按照如下规则进行匹配:
~
和~*
的指令,如果找到相应的匹配,则nginx停止搜索其他匹配;当没有正则表达式或者没有正则表达式被匹配的情况下,那么匹配程度最高的逐字匹配指令会被使用。 在location匹配URI后,就需要在服务器指定的目录中寻找请求资源,而root
和alias
就是用来指定目录的两种指令,两者主要的区别在于如何解析location后面的路径;我们首先来看下root的用法,假如我们需要将/data/
下面的所有路径转发到html/roottest
下面:
location /data/ {
root html/roottest;
}
当location接收到/data/index.html
的请求时,会在html/roottest/data/
目录下找到index.html文件并进行相应,root会将root路径和location路径进行拼接。
而alias指令则改变location接收到的请求路径,假如我们需要将/data1/
下面的所有路径转发到html/aliastest
下面:
location /data1/ {
alias html/aliastes/;
}
当location接收到/data1/index.html
的请求时,会在html/aliastes/
目录下查找index.html文件并响应。
需要注意的是:alias指令后面的路径
必须以/结束
,否则会找不到文件,而root则可有可无。
针对一些静态资源,我们可能会设置一些用户访问权限,比如和js一起打包产出的.map
文件,会对源码进行映射;但是我们想让它只能针对公司的ip进行开放,对外网的ip禁止访问,这时就需要用到allow
和deny
命令了。
假如局域网还有两个设备,我们只能让这两个设备的ip通过访问:
location / {
alias html/aliastes/;
allow 192.168.1.102;
allow 192.168.1.103;
deny all;
}
deny和allow指令是由ngx_http_access_module模块提供,Windows版本的Nginx并不包含该模块。
还可以对前端的.map文件进行访问权限控制,打包后的map文件一般会放在服务器上,但是如果能对所有人开放,别人就能查看到对应源码;因此我们可以控制只有公司的ip才有访问权限:
前端在配置路由时经常会用到history路由模式,因此后台就需要映射对应的路由到index.html;但是如果我们给每个路由都配置一个location就会比较繁琐,因此可以通过try_files
指令来进行尝试解析;try_files
的语法规则如下:
# 格式1:
try_files file ... uri;
# 格式2:
try_files file ... =code;
假设我们打包出来的单页面位于/html/my/index.html
,我们想要将/login、/regisrer等路由指向index.html,我们可以配置try_files:
server {
listen 8080;
server_name localhost;
location / {
try_files $uri /my/index.html;
}
}
对于多页面的应用,假设我们的页面都放在/html/pages/
目录下,我们想要访问/login
时响应/html/pages/login.html
页面,可以通过$uri
:
server {
listen 8080;
server_name localhost;
location / {
index index.html index.htm;
root html/pages;
try_files $uri /$uri.html $uri/index.html /index.html;
}
}
这里我们设置root目录为html/pages,当我们访问/login
路由时,这里的$uri就是/login,try_files会去尝试在根目录下找/login.html
;如果找不到就尝试/login/index.html
,最后找不到则会默认返回index.html。
我们都知道在服务端开启gzip压缩能够使得js、css、html等文件在传输时大幅提高访问速度,优化网站性能;gzip压缩后的文件大小可以变为原来的30%甚至更小;而对于图片、视频、音频等其他多媒体文件,因为压缩效果不好,所以不会开启压缩。
gzip压缩本质上是服务器端压缩,传输到浏览器后解压解析,我们来看下gzip的原理示意:
可以看到在请求和相应头上分别加了accept-encoding和content-encoding来进行传输;我们可以通过一个js的请求数据来查看:
既然gzip有这么多的好处,我们来看下nginx如何进行配置,gzip的配置可以在http块或者server块中:
# 开启gzip
gzip on;
# 设置gzip申请内存的大小
gzip_buffers 32 4K;
# 设置gzip压缩等级
# 压缩级别 1-9,级别越高压缩率越大但耗CPU
gzip_comp_level 6;
# 正则匹配User-Agent中的值,匹配上则不进行gzip
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
# 设置允许压缩的页面最小字节数
gzip_min_length 1024;
# 设定进行gzip压缩的最小http版本
gzip_http_version 1.0;
# 需要压缩哪些响应类型的资源
gzip_types application/javascript text/css text/xml;
# 添加“Vary: Accept-Encoding”响应头
gzip_vary on;
对于一些简单的页面,我们想要通过密码来限制其他用户的访问,但是又不想接入复杂的账号体系,Nginx提供了简单的账号密码控制;首先我们通过Linux的工具创建一个密码本存放账号密码:
sudo yum install httpd-tools -y
sudo htpasswd -c passwd/passwd admin
passwd/passwd
文件就是生成的密码文件,运行后会要求连续两次输入密码,成功后为admin用户添加了密码;然后我们就修改nginx的配置文件,对站点开启密码验证:
server {
listen 8000;
server_name localhost;
auth_basic "请输入账号密码";
auth_basic_user_file /etc/nginx/conf/passwd/passwd;
location / {
# .....
}
}
重启nginx,再次访问站点就会出现需要身份验证的弹框了。
上面我们介绍了正向代理和反向代理的区别,反向代理功能是nginx的三大主要功能之一(静态web服务器、反向代理、负载均衡)。反向代理不需要额外的模块,默认自带proxy_pass和fastcgi_pass指令,通过在location块中配置即可实现:
server {
listen 80;
server_name a.com;
location / {
proxy_pass http://192.168.1.102:8080;
}
}
在配置proxy_pass时,我们需要注意url后面的/`;当我们通过下面几种情况访问
/proxy/home.html``时:
location /proxy/ {
proxy_pass http://192.168.1.102:8080/;
}
第一种情况url后面带上/,则会被代理到http://192.168.1.102:8080/home.html
。
location /proxy/ {
proxy_pass http://192.168.1.102:8080;
}
第二种情况url后不带/,则会被代理到http://192.168.1.102:8080/proxy/home.html
location /proxy/ {
proxy_pass http://192.168.1.102:8080/doc/;
}
第三种情况代理/doc/,则会被代理到http://192.168.1.102:8080/doc/home.html
location /proxy/ {
proxy_pass http://192.168.1.102:8080/doc;
}
第四种情况代理/doc,则会被代理到http://192.168.1.102:8080/dochome.html
在配置反向代理时,我们还可以修改代理请求的请求参数:
location /proxy/ {
proxy_pass http://192.168.1.102:8080/;
# 修改请求的method
proxy_method GET;
# 修改请求的http协议版本
proxy_http_version 1.1;
# 将原来host字段放到转发请求中
proxy_set_header Host $host;
#获取真实ip
proxy_set_header X-Real-IP $remote_addr;
# 代理服务器每成功收到一个请求,就把请求来源IP地址添加到右边
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
}
经过反向代理后,由于客户端和web服务器之间增加了一个代理层,因此web服务器无法拿到客户端请求的host和真实ip,我们通过proxy_set_header指令修改代理请求的头部;
remote_addr是用户真实的host和ip,这里作为变量传入Host和X-Real-IP字段,因此我们在客户端服务器想要获取真实ip就可以通过request.getAttribute("X-real-ip")的方式。
随着互联网的发展,用户规模的增加,服务器的压力也越来越大,如果只使用一台服务器有时候不能承受流量的压力,这时我们就需要将部分流量分散到多台服务器上,使得每台服务器都均衡的承担压力。
nginx负载均衡目前支持六种策略:轮询策略、加权轮询策略、ip_hash策略、url_hash策略、fair策略和sticky策略;六种策略可以分为两大类,内置策略(轮询、加权轮询、ip_hash)和扩展策略(url_hash、fair、sticky);默认情况下内置策略自动编译在Nginx中,而扩展策略需要额外安装。
既然是负载,那么我们需要启用多台服务器;这里为了方便演示,我们在一台电脑上运行node脚本来模拟3台服务器;同时为了方便看到每台服务器有多少流量,每访问一次就计数一次:
const express = require("express");
const app = express();
const PORT = 8080;
const path = require("path");
let count = 0;
app.get("*", (req, res) => {
count++;
res.sendFile(path.resolve(__dirname, "./index.html"));
});
app.listen(PORT);
然后我们修改端口号,这样我们就有8080、8081、8082三个服务器了。
轮询策略,顾名思义,就是按照请求顺序,逐一分配到不同的服务器节点;如果某台服务器出现问题,会自动剔除。
upstream myserver {
server 192.168.1.101:8080;
server 192.168.1.101:8081;
server 192.168.1.101:8082;
}
server {
listen 8070;
server_name _;
location / {
proxy_pass http://myserver;
}
}
我们还是通过测试工具Apache Bench
来并发100个请求到Nginx:
ab -n 100 -c 10 http://localhost:8070/
最后统计每台服务器的结果,每台服务器的请求还是很平均的:
8080:34个请求
8081:33个请求
8082:33个请求
加权轮询在基本轮询策略上考虑各服务器节点接受请求的权重,指定服务器节点被轮询的权重,主要用于服务器节点性能不均的情况。
通过在server节点后配置weight来设置权重,weight的大小和访问比率成正比(weight的默认值为1);我们给三台服务器设置访问比是1:3:2
。
upstream myserver {
server 192.168.1.101:8080;
server 192.168.1.101:8081 weight=3;
server 192.168.1.101:8082 weight=2;
}
压力测试后统计服务器的请求结果,和我们配置的比率还是几乎相同的:
8080:16个请求
8081:51个请求
8082:33个请求
注:由于weight是内置,所以可以直接和其他策略配合使用。
ip_hash策略是将前端访问的ip进行hash操作后,然后根据hash的结果将请求分配到不同的节点上,这样使得每个ip都会固定访问服务节点;这样做的好处是用户的session只在一个后端服务器节点上,不必考虑一个session存在多台服务器节点出现session共享问题。
upstream myserver {
ip_hash;
server 192.168.1.101:8080;
server 192.168.1.101:8081 weight=3;
server 192.168.1.101:8082 weight=2;
}
压力测试后统计服务器的请求结果,我们发现所有的请求都到固定一台服务器上了:
8080:0个请求
8081:0个请求
8082:100个请求
url_hash策略是将url地址进行hash操作,根据hash结果请求定向到同一服务器节点上;url_hash的优点是能够提高后端缓存服务器的效率。
upstream myserver {
hash $request_uri;
server 192.168.1.101:8080;
server 192.168.1.101:8081;
server 192.168.1.101:8082;
}
压力测试后统计服务器的请求结果:
8080:0个请求
8081:0个请求
8082:100个请求
如果我们切换不同的url,/home、/list等,都会分配到不同的服务器节点。
fair策略请求转发到负载最小的后端服务器节点上。Nginx通过服务器节点对响应时间来判断负载情况,响应时间最短的节点负载就相对较轻,Nginx就会将前端请求转发到此服务器节点上。
注:fair策略默认不被编译进nginx内核,需要额外安装
upstream myserver {
fair;
server 192.168.1.101:8080;
server 192.168.1.101:8081;
server 192.168.1.101:8082;
}
压力测试后统计服务器的请求结果:
8080:33个请求
8081:33个请求
8082:34个请求
sticky策略是基于cookie的一种负载均衡解决方案,通过分发和识别cookie,使来自同一个客户端的请求落在同一台服务器上,默认cookie标识名为route。
sticky策略看起来和ip_hash策略类似,但是又有一定区别。假设在一个局域网内有3台电脑,他们有3个内网IP,但是他们发起请求时,却只有一个外网IP,如果使用ip_hash方式,则Nginx会将请求分配到同一服务器;如果使用sticky策略,则会把请求分配到不同服务器上,这是ip_hash无法做到的。
注:sticky策略默认不被编译进nginx内核,需要额外安装
upstream myserver {
sticky name=sticky_cookie expires=6h;
server 192.168.1.101:8080;
server 192.168.1.101:8081;
server 192.168.1.101:8082;
}
sticky默认的cookie的名称是route
,我们可以通过name修改,还有一些其他的cookie参数可以进行修改:
我们通过浏览器来访问,在cookie中可以看到sticky下发的cookie
注:由于cookie最初由服务器端下发,如果客户端禁用cookie,则cookie不会生效。
upstream还有一些参数我们可以配合负载均衡:
参数 | 描述 |
---|---|
fail_timeout | 与max_fails结合使用 |
max_fails | 设置在fail_timeout参数设置的时间内最大失败次数,如果在这个时间内,所有针对该服务器的请求都失败了,那么认为该服务器会被认为是停机了 |
fail_time | 服务器会被认为停机的时间长度,默认为10s。 |
backup | 标记该服务器为备用服务器。当主服务器停止时,请求会被发送到它这里。 |
down | 标记服务器永久停机了。 |
keepalive | 连接数(keepalive的值)指定了每个工作进程中保留的持续连接到nginx负载均衡器缓存的最大值。如果超过这个设置值的闲置进程想链接到nginx负载均衡器组,最先连接的将被关闭。 |
upstream backserver{
ip_hash;
# down 表示单前的server暂时不参与负载
server 192.168.1.101:8080 down;
server 192.168.1.101:8081;
# max_fails允许请求失败的次数默认为1,此处允许失败的次数为3。每次失败后暂停的时间为30s
server 192.168.1.101:8082 max_fails=3 fail_timeout=30s;
# 其它所有的非backup机器down或者忙的时候,请求backup机器
server 192.168.1.101:8083 backup;
# 连接到nginx负载均衡器的最大
keepalive 16;
}
Nginx你学会了吗?更多前端资料请关注公众号【前端壹读】
。
如果觉得写得还不错,请关注我的掘金主页。更多文章请访问谢小飞的博客