阅读 2674

Nginx-包教包会-入门

前言

Nginx 作为 web 服务器 以低内存,高扩展,并且轻松单机支持 1-3w (据说可以单机 10w,但没有看到具体的机器配置)的并发链接的特性广受开发人员的青睐。

推荐在 linux 系统上使用 Nginx ,这会充分利用 linux 的特性,性能比在 windows 上会更好。

本文主要内容:

  • Nginx 简单配置
  • root 和 alias 的区别
  • location 的优先级及验证
  • Nginx 内置变量介绍
  • if
  • rewrite 转发
  • try_files
  • 配置 gzip
  • 协商缓存和强缓存的介绍和配置

后续章节,我会使用 ab 压测来一步一步优化 Nginx 的配置,Nginx 知道原理,懂得常用配置即可。有的性能优化需要了解 linux 内核httptcp 相关的东西,如果你不想了解,可以记录一份自己的配置即可,不必纠结为什么。

本文内容在 nginx 1.16.1 上测试,Centos 7 4核 8g 内存的虚拟机。

Nginx 安装

Nginx 安装步骤

根据 阿里 CentOS 镜像 配置 yum 源,提高下载速度。

阿里 epel 镜像 配置我们常用软件的包,Nginx 也在其中。

# 运行一下命令,更新 yum 源
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
复制代码

image-20200327220547818

如果想查看 nginx包都安装了哪些文件,可以使用

rpm -qvl nginx
复制代码

image-20200327221058643

Nginx 命令

# 强制立即关闭,不建议做
nginx -s stop

# 正常关闭,会处理已经接到的请求,但不会接受新的请求
nginx -s quit

# 重新加载配置文件
nginx -s reload

#  重新打开日志文件
nginx -s reopen

# 检查配置文件是否有误
nginx -t

# 检查配置文件是否有误,会将配置文件内容打印
nginx -T

# 查看 nginx 版本
nginx -v

# 查看 nginx 版本和编译配置致残
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 切换。

image-20200327222127446

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 / {} 匹配所有的资源。

listenserver_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 路径。

# /bieming/     替换  /usr/local/var/www/alias2/
# 访问 /bieming/1.jpg 去寻找 /usr/local/var/www/alias2/1.jpg
location /bieming/ {
    alias /usr/local/var/www/alias2/;
}
复制代码

root 理解为:root 路径加上 + location 路径。会将两个或更多个 / 压缩成一个。

# 当 location 和 root 路径的最后一部分匹配时,更好的方式是使用 root
## 以下配置都可以。 
# 访问 /data2/1.jpg   去寻找  /usr/local/var/www/data2/1.jpg
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 等值来转发或者拒绝访问。

$arg_name 获取请求参数

获取请求query string 中的 name 参数。

location /arg/ {
    default_type application/json;
    return 200 "$arg_q1";
}
复制代码

/arg/a?q1=334 返回的内容为 334

$args 获取请求 query_string 参数

location /arg/ {
    default_type application/json;
    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 获取 pathquery string

访问 http://localhost:8888/arg/a/?q1=q1canshu&bb=2323

$request_uri/arg/a/?q1=q1canshu&bb=2323

$scheme 获取请求协议

值为 httphttps

$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 的内容。

验证 redirectpermanent

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 就知道验证结果了。

image-20200328181255172

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 $uri/ @pass2;
}
location @pass2 {
    default_type application/json ;
    return 200 "没到到页面代理的数据" ;
}
复制代码

访问 /try/1.jpg 时,$uri/try/1.jpg

root + $uri/usr/local/var/www/data2/data2/try/1.jpg 找到返回,没有找到继续匹配返回。都没有匹配内部转发至 @pass2

如想验证跳转使用 /try/test 之类的,不要使用后缀名,因为使用后缀名的话,浏览器会返回content-type,导致内容与解析不一致,图片出不来。

配置 gzip

# 开启 gzip
gzip on;

# 在响应头中增加,Vary: Accept-Encoding
gzip_vary on;

# gzip压缩级别1-9,数字越大压缩效果越好,压缩所用时间也就越长,占用CPU越高
gzip_comp_level 6;

# 申请内存时大小,如果源文件 9k,超过了 8K,那会申请 16*8K。
gzip_buffers 16 8k;

gzip_min_length 2K;
gzip_proxied any;
gzip_disable "msie6";

gzip_http_version 1.1;

# 文本(js、text、css、xml、json)压缩比较好,图片已经进行过压缩,在压缩,效果不是很明显,还浪费 cpu
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 压缩对文本效果比较好,推荐只对文本之类的压缩。

配置缓存

为了减轻服务器压力,节省带宽,可以配置缓存。

image-20200328185715962

memory cache:它是将资源文件缓存到内存中,缓存有效直接从内存加载。

disk cache: 它是将资源文件缓存到硬盘中,缓存有效直接从硬盘中加载。

先从 memory cache 找,找不到从 disk cache 找,再找不到,请求网络资源。

缓存分为 协商缓存强缓存

协商缓存 每次都要去服务器询问缓存是否过期,没有过期使用本地的缓存。

强缓存 会有缓存过期时间,在有效期内不会去服务端校验缓存,直接使用本地缓存。

现在的 webpack 可以根据文件内容的 hash 生产类似 app.asdfa21342.js 这样的文件。其实就是想使用强缓存,当网站更新,新的页面会解析加载不一样的资源,从而降低缓存校验对服务器性能的损耗。

协商缓存

协商缓存有:ETag/if-None-MatchLast-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 ,值为 Etagnginx 校验是否一样,一样说明缓存没有过期,返回状态码 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-Modifiednginx 收到 If-Modified-Since 后,根据配置 if_modified_since 属性比较资源的最后修改时间(Last-Modified)和该值If-Modified-Since进行比较,匹配成功,则命中缓存,返回304,否则返回 资源,状态码为 200,并更新缓存时间。

强制缓存

Expires

Expireshttp1.0 的规范,它的值是一个绝对时间的GMT格式的时间字符串。这个时间代表的该资源的失效时间,如果在该时间之前请求的话,则都是从缓存里面读取的。如果服务端和客户端时区不一致会导致判断不准确。

Cache-Control

Cache-Controlhttp1.1 的规范,它是利用该字段 max-age 值进行判断的。该值是一个相对时间,比如 Cache-Control: max-age=3600 代表该资源的有效期是3600秒。除了该字段外,我们还有如下字段可以设置:

no-cache: 需要进行协商缓存,发送请求到服务器确认是否使用缓存。

**no-store:**禁止使用缓存,每一次都要重新请求数据。

**public:**可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。

**private:**只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。

配置缓存

location ~* \.(css|js|png|jpg|jpeg|gif|gz|svg|mp4|mp3|ogg|ogv|webm|htc|xml|woff)$ {
    # 关闭访问日志记录
    access_log off;
    # 强缓存,时间为一年,浏览器和 cdn 中间件可以缓存 
    add_header Cache-Control "max-age=31536000";
}
复制代码

推荐资料

nginx 中文翻译