Nginx 作为 web 服务器 以低内存,高扩展,并且轻松单机支持 1-3w (据说可以单机 10w,但没有看到具体的机器配置)的并发链接的特性广受开发人员的青睐。
推荐在 linux 系统上使用 Nginx ,这会充分利用 linux 的特性,性能比在 windows 上会更好。
本文主要内容:
Nginx 简单配置
root 和 alias 的区别
location 的优先级及验证
Nginx 内置变量介绍
if
rewrite 转发
try_files
配置 gzip
协商缓存和强缓存的介绍和配置
后续章节,我会使用 ab 压测来一步一步优化 Nginx 的配置,Nginx 知道原理,懂得常用配置即可。有的性能优化需要了解 linux 内核 、http、tcp 相关的东西,如果你不想了解,可以记录一份自己的配置即可,不必纠结为什么。
本文内容在 nginx 1.16.1 上测试,Centos 7 4核 8g 内存的虚拟机。
Nginx 安装
Nginx 安装步骤
根据 阿里 CentOS 镜像 配置 yum 源,提高下载速度。
阿里 epel 镜像 配置我们常用软件的包,Nginx 也在其中。
yum clean all
yum makecache
复制代码刷新 yum 仓库信息之后,运行以下命令就可以找到 nginx
yum list | grep nginx
复制代码安装 nginx
sudo yum install nginx
复制代码配置 nginx 开机启动
sudo systemctl enable nginx
复制代码启动 nginx
sudo systemctl start nginx
复制代码检查 nginx 是否启动
sudo systemctl status nginx
复制代码
如果想查看 nginx包都安装了哪些文件,可以使用
rpm -qvl nginx
复制代码
Nginx 命令
nginx -s stop
nginx -s quit
nginx -s reload
nginx -s reopen
nginx -t
nginx -T
nginx -v
nginx -V
复制代码系统开启、关闭、重启、查看 nginx 命令
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl restart nginx
sudo systemctl stop nginx
sudo systemctl status nginx
复制代码Nginx 简单配置
Nginx 介绍
部署的 Nginx 使用一个 master 进程管理多个 worker 进程。master 进程不处理请求,提供管理服务,包括启动、停止、重载配置文件等服务,通常以 root 用户启动, worker 进程进行请求的处理,一般用非管理员账户启用,worker 进程数量和 cpu 核心设置一直,降低进程切换带来的 cpu 切换。
http 上下文中的配置是我们重点需要知道的,其余的了解会配置即可。
Server 配置
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80 ;
server_name _;
root /usr/share/nginx/html;
location / {
}
}
}
复制代码Server 既配置一个服务。
listen 80 用于配置监听 80 端口的服务。
root 指定静态资源存放的位置。
location 进行资源匹配。location / {} 匹配所有的资源。
listen 和 server_name 配置
匹配规则:
先匹配 listen 再匹配 server_name
server_name 匹配请求头中的 Host
当都没有匹配成功,由配置default_server 的处理
以上都没有匹配成功,由第一个配置处理
server {
listen 9099 default_server;
server_name “localhost”;
location / {
return 200 "server_name 为 localhost";
}
}
server {
listen 9099;
server_name 127.0.0.1;
location / {
return 200 "server_name 为 127.0.0.1";
}
}
server {
listen 9099;
server_name “localhost77”;
location / {
return 200 "server_name 为 localhost77";
}
}
复制代码在 Postman中设置请求头 Host 模拟访问。
http://localhost:9099 Host:127.0.0.1 返回 server_name 为 127.0.0.1
http://localhost:9099 Host:localhost 返回 server_name 为 localhost
http://localhost:9099 Host:localhost77 返回 server_name 为 localhost77
http://localhost:9099 Host:localhost779 返回 server_name 为 localhost
再添加一个配置
server {
listen localhost:9099 default_server;
server_name “localhost”;
location / {
return 200 "server_name 为 localhost:9099";
}
}
复制代码当 listen 访问 localhost:9099 的时候,返回 server_name 为 localhost:9099 ,因为只有这一个匹配上了。
如果想禁止没有 Host 请求头的访问。
server {
listen 80;
server_name “”;
# 表示 nginx 会关闭连接
return 444;
}
复制代码return 配置
return 用于定义返回的状态码,或者内容。
介绍 return 主要是为了好描述 location 配置
说明
语法
return code [text];return code URL;return URL ;
上下文
server、location、if
code 为状态码。text 为字符串。
location /a {
default_type application/json;
return 200 “访问 9088/a”;
}
复制代码# 重定向
location = /b {
return 301 http://www.baidu.com;
}
复制代码location 配置
location 用于匹配资源。
数字越小,优先级越高。
规则符号
描述
优先级
location = /a{}
精准完全匹配,匹配到之后
1
location ^~ /a{}
前缀匹配,匹配到之后
2
location ~ /a.*{}
正则匹配,区分大小写,检查到之后,还会检查有没有优先级跟高的
3
location ~* /a.*
正则匹配,不区分字母大小写,检查到之后,还会检查有没有优先级跟高的
4
location /a {}
也表示前缀匹配,但是优先级低于 正则匹配。 /a 和 ^~/a 会冲突,报错
5
location / {}
任何没有匹配成功的,都会匹配这里处理
6
server {
listen 9088 default_server;
server_name _ ;
location = /a {
default_type application/json;
return 200 "= /a,优先级第一";
}
location ^~ /a {
default_type application/json;
return 200 "^~ /a 匹配 /a 开头的路径,优先级第二";
}
location ~ /a\.* {
default_type application/json;
return 200 " /a\.* 匹配 /a...路径,优先级第三";
}
location ~* /a\.* {
default_type application/json;
return 200 "~* /a\.* 匹配 /a...路径,优先级第四";
}
# /a 会和 ^~ /a 冲突
location /a/ {
default_type application/json;
return 200 "/a/ 匹配 /a/...路径,优先级第五";
}
}
复制代码访问 http://localhost:9088/a ,依次注释优先级较高的,可以验证这个规律。
还有一类特殊的 location 用于配置跳转的,以 @ 开头
location @pass {
}
复制代码add_header 添加响应头
说明
语法
add_header name value [always];
上下文
http、server、location、location 中的 if
如果响应代码等于 200、201、204、206、301、302、303、304、307或 308,则将指定的字段添加到响应报头中。
加上 always 不管什么状态码都加上。
location ~ /a.* {
default_type application/json;
add_header test1 “asdfasdf” always;
return 200 " /a.* 匹配 /a…路径,优先级第三";
}
复制代码error_page
说明
语法
error_page code …[=[response code]] uri;
上下文
http、server、location、location 中的 if
配置错误状态码跳转页面。
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
复制代码以上不会改变响应状态码。
error_page 404 =200 /404.html;
复制代码server {
location / {
error_page 404 = @ops-coffee;
}
location @ops-coffee {
rewrite .* / permanent;
}
}
复制代码root 和 alias 的区别
alias 理解为:路径替换,location 以 / 结尾,alias 必须以 / 结尾。严格匹配。alias 替换掉 location 路径。
location /bieming/ {
alias /usr/local/var/www/alias2/;
}
复制代码root 理解为:root 路径加上 + location 路径。会将两个或更多个 / 压缩成一个。
location /data2/ {
root /usr/local/var/www;
}
location /data2/ {
root /usr/local/var/www/;
}
location /data2 {
root /usr/local/var/www;
}
location /data2 {
root /usr/local/var/www/;
}
location /data2/ {
root /usr/local/var/www////;
}
复制代码内置变量
通过内置变量,我们可以通过判断请求头、query string 等值来转发或者拒绝访问。
KaTeX parse error: Expected '}', got 'EOF' at end of input: … return 200 "arg_q1";
}
复制代码/arg/a?q1=334 返回的内容为 334。
KaTeX parse error: Expected '}', got 'EOF' at end of input: … return 200 "arg_q1 _ $args";
}
复制代码浏览器访问 /arg/a?q1=3334&aa=2&bb=33 返回的内容为 3334 _ q1=3334&aa=2&bb=33
$cookie_name 获取cookie的值
获取请求中的名称为 name 的 cookie。
$http_name 获取请求头
name 为请求头中的字段名称,请求头名称全部小写,并将破折号- 替换为下划线 _
$http_user_agent 获取请求头中的 User-Agent 字段。
$uri 获取请求路径中的 path
path 为端口后面的路径,不包括 query string。优化之后的路径,特殊字符转译及 / 压缩。
http://localhost:8888/arg/a?q1=q1canshu&bb=2323 中的 path 为 /arg/a
$host 获取请求的 ip
首先会获取请求头 Host ,如果没有请求头中没有 Host 请求头,那么获取的是 url 中的 ip。
$request_uri 获取 path 和 query string
访问 http://localhost:8888/arg/a/?q1=q1canshu&bb=2323
$request_uri 为/arg/a/?q1=q1canshu&bb=2323
$scheme 获取请求协议
值为 http 或 https
$request_method 获取请求方法
获得的值字母全大写。GET,POST,DELETE,PUT 等
其他变量
描述
$content_length
获取 Content-Length 请求头字段。
$content_type
获取 Content-Type 请求头字段
$https
如果连接以 SSL 模式运行,则为 on ,否则为空字符串
$is_args
如果请求行有参数则为 ? ,否则为空字符串
$pid
获取处理当前请求的 worker pid
$nginx_version
获取 nginx 的版本
if
说明
语法
if (condition){}
上下文
server、location
指定的 condition 求值之后,如果为 true ,则执行在大括号内指定的该模块的指令,并在 if 指令内为该请求分配配置。 if 指令内的配置继承自上一层的配置级别。
condition 可以是以下任何一种:
变量名,如果变量的值为空字符串或 0 ,则为 false
使用 = 和 != 运算符比较变量和字符串
使用 ~ (区分大小写的匹配)和 ~* (不区分大小写的匹配)运算符,变量将与正则表达式进行匹配。正则表达式可以包含可供以后在 $1…$9 变量中重用的捕获。
反操作符 !~ 和 !~* 也可用。如果正则表达式包含 } 或 ; 字符,则整个表达式应使用单引号 或双引号包围起来。
使用 -f 和 !-f 运算符检查文件是否存在
使用 -d 和 !-d 运算符检查目录是否存在
使用 -e 和 !-e 运算符检查文件、目录或符号链接是否存在
使用 -x 和 !-x 运算符检查是否为可执行文件
if 与小括号之间需要有空格
location = /a {
default_type application/json;
if ($request_uri ~* “/(a).*”) {
return 200 “正则表达式捕获的值:$1”;
}
return 200 “= /a,优先级第一”;
}
复制代码rewrite
说明
语法
rewrite regex replacement [flag];
上下文
server、location、if
flag 可选参数:
last
停止匹配,发送一个新的请求去匹配 location。
break
停止匹配,在当前 location 去搜索资源。
redirect
临时重定向。返回状态码 302。
permanent
永久重定向。返回状态码 301 。
指定的 regex 能匹配,uri 将根据 replacement 来处理。
验证 break 和 last
以下三张图片都存在,但是内容不一样。
/Users/zhangpanqin/stduy_app/break2/test/1.jpg
/Users/zhangpanqin/stduy_app/last2/test/1.jpg
/Users/zhangpanqin/stduy_app/test/1.jpg
location /break2 {
root /Users/zhangpanqin/stduy_app/break2;
rewrite /break2/(.*) /test/$1 break;
}
location /last2 {
root /Users/zhangpanqin/stduy_app/last2;
rewrite /last2/(.*) /test/$1 last;
}
location /test/ {
root /Users/zhangpanqin/stduy_app;
}
复制代码当访问 /break2/1.jpg 实际匹配第一个 location,然后在当前上下文处理 。
/break2/1.jpg 被替换为 /test/1.jpg 来处理了,然后和 root 指定的路径相结合,返回 /Users/zhangpanqin/stduy_app/break2/test/1.jpg 数据。
当访问 /last2/1.jpg ,uri 被替换为 /test/1.jpg 去匹配新的 location 进行处理。
返回 /Users/zhangpanqin/stduy_app/test/1.jpg 的内容。
验证 redirect 和 permanent
location /redirect2 {
rewrite ^/redirect2 http://www.baidu.com redirect;
}
location /permanent2 {
rewrite ^/permanent2 http://www.baidu.com permanent;
}
复制代码二者匹配成功之后,都会改变浏览器地址栏地址。浏览器根据响应头 Location 跳转对应地址。
二者的区别在于,永久重定向 (permanent),浏览器会保存记录,当再访问 http://localhost:9088/permanent2 而不会询问 nginx 直接跳转。
临时重定向,浏览器每次都要询问 nginx 需要跳转到哪里。可以关闭 nginx 就知道验证结果了。
try_files
说明
语法
try_files file … uri;try_files file … =code;
上下文
server、location
以指定顺序检查文件是否存在,并使用第一个找到的文件进行请求处理。如果找不到内容内部转发到最后一个参数 uri 。文件位置为 root + file。
location /try/ {
root /usr/local/var/www/data2/data2/;
try_files $uri KaTeX parse error: Expected 'EOF', got '}' at position 14: uri/ @pass2; }̲ location @pass…uri 为 /try/1.jpg 。
root + $uri 为 /usr/local/var/www/data2/data2/try/1.jpg 找到返回,没有找到继续匹配返回。都没有匹配内部转发至 @pass2。
如想验证跳转使用 /try/test 之类的,不要使用后缀名,因为使用后缀名的话,浏览器会返回content-type,导致内容与解析不一致,图片出不来。
配置 gzip
gzip on;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_min_length 2K;
gzip_proxied any;
gzip_disable “msie6”;
gzip_http_version 1.1;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml+rss application/rss+xml application/atom+xml image/svg+xml;
复制代码gzip 压缩对文本效果比较好,推荐只对文本之类的压缩。
配置缓存
为了减轻服务器压力,节省带宽,可以配置缓存。
memory cache:它是将资源文件缓存到内存中,缓存有效直接从内存加载。
disk cache: 它是将资源文件缓存到硬盘中,缓存有效直接从硬盘中加载。
先从 memory cache 找,找不到从 disk cache 找,再找不到,请求网络资源。
缓存分为 协商缓存 和 强缓存。
协商缓存 每次都要去服务器询问缓存是否过期,没有过期使用本地的缓存。
强缓存 会有缓存过期时间,在有效期内不会去服务端校验缓存,直接使用本地缓存。
现在的 webpack 可以根据文件内容的 hash 生产类似 app.asdfa21342.js 这样的文件。其实就是想使用强缓存,当网站更新,新的页面会解析加载不一样的资源,从而降低缓存校验对服务器性能的损耗。
协商缓存
协商缓存有:ETag/if-None-Match 和 Last-Modified/if-Modify-Since 两种。
http 协议规定,当这两种响应头都存在的时候,必须都要满足,才能使用缓存。
ETag/if-None-Match
说明
语法
etag on|off;
默认
etag on;
上下文
http、server、location
nginx 有个 etag 配置属性,会给每个静态资源生成 Etag 响应头,值为文件内容 hash。
当浏览器第一次访问资源的时候,返回的响应头中携带 Etag 。
后续的正常访问(不强制刷新缓存)相同的资源,都会带上请求头 if-None-Match ,值为 Etag 去 nginx 校验是否一样,一样说明缓存没有过期,返回状态码 304,直接访问浏览器中的缓存,否则从浏览器返回资源,返回状态码 200。
Last-Modified/if-Modify-Since
说明
语法
if_modified_since off|exact|before;
默认
if_modified_since exact;
上下文
http、server、location
指定如何比较文件的修改时间与请求头 If-Modified-Since 进行比较:
off
忽略 If-Modified-Since 请求头字段(0.7.34)
exact
完全匹配
before
资源的修改时间小于或等于 If-Modified-Since 请求头字段中的时间
浏览器第一次访问一个资源的时候,响应头 Last-Modified 返回,标识文件的最后修改时间。
当浏览器再次正常访问(不强制刷新资源)相同资源,请求头会加上 If-Modified-Since,该值为之前返回的 Last-Modified 。nginx 收到 If-Modified-Since 后,根据配置 if_modified_since 属性比较资源的最后修改时间(Last-Modified)和该值If-Modified-Since进行比较,匹配成功,则命中缓存,返回304,否则返回 资源,状态码为 200,并更新缓存时间。
强制缓存