# 前言

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 安装

# docker

docker pull nginx:1.25.0-alpine3.17-slim
mkdir -p ~/docker-nginx/html && mkdir -p ~/docker-nginx/nginx/conf.d


docker run -it --name mflyyou-nginx \
nginx:1.25.0-alpine3.17-slim

docker cp mflyyou-nginx:/etc/nginx/conf.d/default.conf ~/docker-nginx/nginx/conf.d/default.conf
docker cp mflyyou-nginx:/etc/nginx/nginx.conf ~/docker-nginx/nginx/nginx.conf
docker cp mflyyou-nginx:/usr/share/nginx/html ~/docker-nginx/

docker container stop mflyyou-nginx
docker container rm mflyyou-nginx


docker run -it -d --name mflyyou-nginx -p 80:80 \
-v ~/docker-nginx/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \
-v ~/docker-nginx/nginx/nginx.conf:/etc/nginx/nginx.conf \
-v ~/docker-nginx/html:/usr/share/nginx/html \
nginx:1.25.0-alpine3.17-slim


docker exec -it mflyyou-nginx sh

# Nginx 安装步骤

根据 阿里 CentOS 镜像 (opens new window) 配置 yum 源,提高下载速度。

阿里 epel 镜像 (opens new window) 配置我们常用软件的包,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

获取请求中的名称为 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 中文翻译 (opens new window)