Nginx学习笔记

最近在系统的学习 Nginx 知识,主要看 陶辉 老师极客时间的课程,下面是整理的一部分笔记。

初识Nginx

Nginx主要应用场景

Nginx的主要优点

Nginx组成部分

Nginx命令行

用免费的SSL证书实现一个HTTPS站点

下载certbot,一个python工具

yum install python2-certbot-nginx

为指定目录下(默认/usr/local/nginx/conf/目录)的配置文件生成指定域名的HTTPS证书

certbot --nginx --nginx-server-root=/usr/local/nginx/conf/vhost/ -d turbobin.site

Nginx架构基础

Nginx的请求处理流程

当外部流量(web、Email、TCP流量)进入 Nginx 时,会经过非阻塞事件驱动引擎,引擎包括三个状态机,即处理 TCP 流量的传输层状态机、处理 HTTP 请求的 HTTP 状态机、处理 email 的 MAIL 状态机。当 nginx 处理静态资源或作为反向代理时,可以把资源缓存在磁盘中,当服务器内存已不足以缓存住所有的文件,aIO 的调用会退化成阻塞的磁盘调用,此时会使用线程池来处理磁盘的阻塞调用。

对每个处理完成的请求,Nginx 会记录 access 访问日志和 error 错误日志。当然也可以使用 syslog 传输到远程的 log 机器上,方便统一分析处理日志。

Nginx 也可以通过负载均衡,把请求以协议的方式,如 HTTP、Mail、stream(TCP)代理,转发给下游的应用服务器;另外一种方式是通过 FastCGI、uWSGI、SCGI,memcached 代理方式与下游服务器连接。

Nginx的进程结构

Nginx 采用多进程的方式,即一个 master 进程,多个子 worker。其中 master 只是作为 worker 进程的管理,只有worker 进程才真正地处理请求。一般建议把 worker 进程的个数设置为 CPU 核数。

除了 worker 进程,还有处理缓存使用的 Cache manager、Cache loader 进程。

使用信号管理Nginx进程

Nginx 管理进程是通过信号的方式来管理的。

master 进程:

work进程

nginx 命令行

以上管理进程的信号对应了 Nginx 的几个命令行

优雅的关闭worker进程是怎样的过程

1、设置定时器(worker_shutdown_timeout)

2、关闭监听句柄

3、关闭空闲连接

4、在循环中等待全部连接关闭

5、退出进程

reload 流程

每当修改配置文件时需要执行 reload 命令,Nginx reload 命令可以平滑的重启服务,它执行的流程如下:

Nginx升级流程

线上执行 nginx 升级不能简单的先停止服务,再重启,这样会使大量的请求无法响应,安全的做法是使用热升级。

1、将旧的 nginx 二进制文件换成新的(先备份旧的)

2、向 master 进程发送 USR2 信号,这时候会新起一个 master 进程,新的 master 进程会产生新的 worker 进程,新产生的连接请求会由新的 master 进程和新的 worker 进程处理。

3、master 进程修改 pid 文件名,加后缀 .oldbin

4、master 进程用新的 nginx 文件启动新 master 进程

5、向老 master 进程发送 QUIT 信号,关闭老 master 进程

6、回滚操作:向老 master 发送 HUP 信号,重新拉起 woker 进程,向新 master 发送 QUIT

Nginx的事件驱动模型

image-20200328192957683

nginx 启动时,处于等待连接状态,对应于epoll 中的epoll_wait方法(阻塞方法),worker 进程处于睡眠状态。

当有 TCP 连接过来,操作系统处理完握手过程后,会通知 epoll_wait 方法往下执行,同时唤醒 worker 进程。操作系统会把准备好的事件放在事件队列中,epoll 从队列中把一个个事件取出来处理。

同步和异步、阻塞和非阻塞

同步和异步是描述的代码的执行顺序,或者说代码处理数据的逻辑状态。

同步 指进程调用接口时,需要等待接口处理完数据并返回响应才继续执行。

异步 指的是进程调用接口后,不必等待数据准备好,可以继续执行,后续数据准备好可以通过一定的方法获得,比如回调。

阻塞和非阻塞描述的是进程等待结果状态,是否会进行进程间切换。

阻塞 指的是进程等待接收数据处理结果时,如果数据没有准备好,当前进程会被挂起,操作系统会切换到另外一个进程,直到有数据返回时,再切换回来,当前进程才会被唤醒。

非阻塞 指的是进程等待接收数据处理结果时,如果数据没有准备好,直接返回一个 err 状态,之后会再次轮询请求,直到数据被处理完成并返回。

Nginx模块分类

nginx 模块主要集中在 ngx_core_module中,其中包括:

此外还有其他如解析配置的处理模块(ngx_conf_module)等

image-20200328211247485

image-20200328211622941

Nginx连接池

nginx 连接池由两部分构成:对下游客户端的连接、对上游服务器的连接。

连接池的大小影响了 nginx 的并发性。

{
    woker_connections 512;		# 默认512大小的连接池数组,一般需要改大一点
}

Nginx内存池

nginx 的内存池分为连接内存池和请求内存池,一般会预分配一定的大小。

连接内存池

与 nginx 建立 TCP 连接都需要分配内存,如果频繁的建立、断开连接就需要频繁的为连接分配内存,而操作系统分配内存是有代价的,所以 nginx 设置了连接内存池。对于长连接(keep-alive)而言,一个 TCP 连接可能运行很多 HTTP 连接,只要 TCP 连接不断开,这个连接的内存就不会释放。

{
    connection_pool_size 256|512;	# 预分配大小,不同的操作系统可能不一样。
}

当连接超过预分配大小时,操作系统会再次分配同样的内存的大小。

请求内存池

默认预分配大小:4k。因为对连接而言,需要保存大量的上下文信息,如 HTTP 请求的 url 比较长,header 比较大,需要保存下来,就需要比较大的内存。

{
    request_pool_size 4k;
}

Nginx进程间的通讯方式

进程间的通讯可以使用以下两种方式:

image-20200328223323463

如何使用Slab内存管理器?

需要下载 tengine 源码,slab 模块在 tenine-x.x.x/modules/ngx_slab_stat/目录下。在编译开源 nginx 时,可以手动把模块添加进来:

nginx-1.14.0 $ ./configure --add-module=../tenine-x.x.x/modules/ngx_slab_stat/

然后执行 make,就可以把模块安装进来了。

详解HTTP模块

HTTP请求的完整流程

操作系统内核与客户端经过三次握手建立 TCP 连接 ——> nginx 内部负载均衡算法选中 CPU 上的 worker 进程 ——> nginx 事件处理模块中的 epoll_wait 读取连接(读事件) ——> accept 方法,分配连接内存池(connection_pool_size: 512) ——> 转交请求给 HTTP 模块,设置超时定时器,分配请求内存池,读取 URI、header ——> 开始 11 个阶段的 http 请求处理。

接收请求的事件模块:

image-20200329152059555

接收请求的HTTP处理模块:

image-20200329152130928

HTTP请求处理的11个阶段:

image-20200329152322942

image-20200329160928914

指令的覆盖

指令分为两类:

值指令:存储配置项的值,如:root、access_log、gzip,这类指令可以合并

动作类指令:指定行为,如:rewrite、proxy_pass,这类指令不可以合并

值指令的指令合并规则:向上覆盖

listen指令的用法

listen unix:/var/run/nginx.sock;
listen 127.0.0.1:8080;
listen 127.0.0.1;	# 默认监听80端口
listen 8080;
listen *:8080;
listen localhost:8080 bind;
listen [::]:8080 ipv6only=on;
listen [::1];

server_name指令的用法

server_name指令:

server匹配的顺序

1、精确匹配

2、* 在前的泛域名

3、*在后的泛域名

4、按文件中的顺序匹配正则表达式域名

5、default server

Rewrite模块的 return 指令

返回状态码

error_page指令

示例用法

1、error_page 404 /404.html;

2、error_page 500 502 503 504 /50x.html;

3、error_page 404 =200 /empty.gif;

4、error_page 404 = /404.php;

5、

location / {
    error_page 404=@fallback;
} 
location @fallback {
    proxy_pass http://backend;
}

6、error_page 403 http://example.com/forbidden.html;

7、error_page 404 =301 http://example.com/notfound.html;

rewrite 指令:重写URL

用法:rewrite regex replacement [flag]

日志:rewrite_log on;

条件判断:if 指令

image-20200329170834823

location指令

location 匹配规则:仅匹配 URL,忽略参数。

合并连续的 /符号

用于内部跳转的命名 location

前缀字符串

正则表达式

location 的匹配顺序

image-20200329174146871

限制客户端的并发连接数 limit_conn

生效阶段:NGX_HTTP_PREACCESS_PHASE 阶段

模块:http_limit_conn_module

默认编译进 nginx,通过 –without-http_limit_conn_module 禁用

生效范围:

步骤

限制发生时的日志级别

语法:limit_conn_log_level info|notice|warn|error;

默认日志级别是 error,可以到 error .log 日志中查看。

限制发生时向客户端返回的错误码

语法:limit_conn_status code;

默认返回 503。

限制返回速率

语法:limit_rate byte;

limit_rate 50;每秒返回50字节。

限制客户端每秒处理请求数 limit_req

生效阶段:NGX_HTTP_PREACCESS_PHASE 阶段

模块:http_limit_req_module

默认编译进 nginx,通过 –without-http_limit_conn_module 禁用

生效算法:leaky bucket 算法

生效范围:

步骤

限制发生时的日志级别

语法:limit_req_log_level info|notice|warn|error;

默认日志级别是 error,可以到 error .log 日志中查看。

限制发生时向客户端返回的错误码

语法:limit_req_status code;

默认返回 503。

Access 模块:限制 ip 的访问

生效阶段:NGX_HTTP_ACCESS_PHASE 阶段

模块:http_access_module

默认编译进 nginx,通过 –without-http_access_module 禁用功能

生效范围:进入 access 阶段前不生效

语法:

允许访问:allow address|CIDR|unix:|all;

禁止访问deny address|CIDR|unix:|all;

示例:

location / {
    deny 192.168.1.1;
    allow 192.168.1.0/255;
    allow 10.1.1.0/16;
    allow 2001:0db8::32;
    deny all;
}

auth_basic:限制网站登录的用户名和密码

基于 HTTP Basic Authutication 协议进行用户名密码的认证。

默认编译进 nginx,可以通过--without-http_auth_basic_module禁用此功能。

指令:

auth_basic string|off;

auth_basic_user_file;指定密码文件

示例:

location {
    satisfy any;
    auth_basic "Auth Access";
    auth_basic_user_file /usr/local/nginx/auth/passwd;
    deny all;
}

生成密码文件的工具:yum -y install httpd-tools

新增:htpasswd -c /usr/local/nginx/auth/passwd -b user password

添加:去掉 -c

auth_request:第三方鉴权模块

如果需要管理比较多的用户名密码,用 auth_basic 就比较繁琐了,nginx 提供了一个统一的用户权限验证系统:auth_request 模块

功能:向上游服务转发请求,若上游服务响应码是 2xx,则继续执行,若上游服务返回的是 401 或者 403,则将响应返回给客户端。

原理:收到请求后,生成子请求,通过反向代理技术把请求传递给上游服务。

auth_request 模块默认未编译进 nginx,需要通过 --with-http_auth_request_module编译进来。

指令:

auth_request uri|off;默认 of。

auth_request_set $variable value;

示例:

location / {
    auth_request /test_auth;
}
location = /test_auth {
    proxy_pass http://127.0.0.1:8090/auth_upstream;
    proxy_pass_request_body off;
    proxy_pass_header Content_Length "";
    proxy_pass_header X-Original-URI $request_uri;
}

限制所有access阶段模块的satisfy指令

语法:satisfy all | any;默认 all

try_files 指令:按序访问资源

语法:

模块:ngx_http_try_files_module,默认编译进 nginx 中

功能:依次试图访问多个 url 对应的文件(由 root 或 alias 指定),当文件存在时,直接返回文件内容,如果所有的文件都不存在,则按最后一个 url 结果或者 code 返回。

示例:

location /first {
    try_files /system/maintenance.html
        $uri $uri/index.html $uri.html
        @lasturl;	# 当前面的文件都不存在时,返回@lasturl定义的返回
}
location @lasturl {
    return 200 'last url';
}

location /second {
    try_files $uri $uri/index.html $uri.html =404;
}

实时拷贝流量:mirror模块

模块:ngx_http_mirror_module,默认编译进了 nginx

作用:处理请求时,生成子请求访问其他服务,对子请求的返回值不做处理

语法:

mirror 请求的响应会被直接丢弃。

示例:

在一台 nginx 中

server {
    listen 8001;
    location / {
        mirror /test_mirror;
        mirror_request_body off;
    }
    
    location = /test_mirror {
        internal;	# 表示只允许内部请求
        proxy_pass http://127.0.0.1:10020$request_uri;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
    }
}

在另一台 nginx 中

server {
    listen 10020;
    
    location / {
        return 200 'mirror response';	# 这里实际并不会返回
    }
}

当请求 8001 端口的 uri 时,会同时将流量传入到 10020 端口上。

mirror 模块通常用于将生产环境的部分流量传入到测试环境中。

详解 root 和 alias 指令 (content 阶段)

功能:将 url 映射为文件路径,以返回静态文件内容。

差别:

static 模块提供的几个变量

生成待访问文件的三个相关变量:

示例:

location /realpath/ {
    alias html/realpath/;
    return 200 '$request_filename:$document_root:$realpath_root'
}

其中 realpath 目录 软连接到了 first 目录: realpath -> first

当访问 realpath/1.txt时,返回:

/usr/local/nginx/html/realpath/1.txt:/usr/local/nginx/html/realpath/:/usr/local/nginx/html/first

静态文件放回的 content-type

未找到文件时的错误日志

URL不以斜杆结尾却能访问目录的做法

static 模块对的 3 个指令:

示例:

server {
    listen 8088;
    server_name turbobin.com turbobin.site;
    server_name_in_redirect off;
    port_in_redirect on;
    absolute_redirect off;
    
    root html/;		# html目录下有个first文件夹
}

index 和 autoindex

index

模块:ngx_http_index_module,默认编译进了 nginx 二进制文件中。

功能:指定/访问时返回 index 文件的内容,默认为 index index.html;

autoindex

模块:ngx_http_autoindex_module,默认编译进 nginx。

功能:当 URL 以 /结尾时,尝试以 html/xml/json/jsonp 等格式返回 root/alias 中指向目录的结构。

指令:

index 模块先于 autoindex 模块执行,所以当 index 和 autoindex 同时指定,且 index 指定的文件存在时,autoindex 的配置会失效。由于 index 模块不能移除,所有为了让 autoindex 指令生效,只能使 index 指定的文件不存在。

concat 指令:提升访问多个小文件的性能

功能:当需要访问多个小文件时,把它们的内容合并到一次 HTTP 响应中返回,提升性能。

模块:ngx_http_concat_module,此模块在阿里开发 Tengine 版本中,需要下载 Tengine源码,通过 ./configure --add_module=../nginx-http-concat/手动编译添加进来。

使用方式:在 URL 后加上 ??后,通过多个逗号 ,分割文件。如果还有参数,则在最后通过 ? 添加参数。

指令:

access 日志的详细用法

功能:将 HTTP 请求的相关信息记录到日志中

模块ngx_http_log_module,默认编译到 nginx 中,无法禁用

access 日志格式log_format

Syntax: log_format name [escape=default/json/none] string...;
Default: log_format combined "";
Context: http

默认的 access 格式:

http {
	log_format combined '$remote_addr - $remote_user [$time_local] "$request" '
			'$status $body_bytes_sent "$http_referer" '
			'"$http_user_agent" "$http_x_forwarded_for"'; 
}

配置日志文件路径:access_log

Syntax: access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
		access_log off;	
Default: access_log logs/access.log combined;
Context: http, server, location, if in location, limit_except

对日志文件名包含变量时的优化:

Syntax: open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
		open_log_file_cache off;
Default: open_log_file_cache off;
Contex: http, server, location

HTTP 过滤模块的调用流程

HTTP 过滤模块主要对 http 响应内容做处理,比如对返回的图片进行压缩。以下是过滤模块所处的位置:

image-20200404171123811

替换响应中的字符串:sub 模块

模块:ngx_http_sub_filter_module默认未编译进 nginx ,通过 –with-http_sub_module 启用

功能:将响应中指定的字符串替换成新的字符串

指令:

在 HTTP 响应前后添加内容:addition 模块

模块:ngx_http_addition_filter_module,默认未编译进 nginx ,需要 –with-http_addtion_module 启用

功能:在响应前或响应后增加内容,而增加内容的方式是通过增加子请求的响应完成的

指令:

Nginx 的变量

nginx 针对变量的处理有两个模块:

nginx 变量的特性:

存放变量的哈希表:

HTTP 框架提供的变量:

HTTP 请求相关的变量

参数 说明
args_参数名 URL中某个具体参数的值
args 全部 URL 参数
query_string 与 args 变量完全相同
is_args 如果请求中有参数则返回? ,否则返回空
content_length HTTP 请求中标识 body 长度的Content_Length头部的值
content_type 标识请求包体类型的 Content_Type 头部的值
uri 请求的 URI(不同于 URL,不包含 ? 后的参数)
document_uri 与 uri 完全相同,因为历史原因存在
request_uri 请求的 URL,包括 URI 以及完整的参数
scheme 协议名,例如 HTTP, HTTPS
request_method 请求方法,例如 GET , POST
request_length 所有请求内容的大小,包括请求行、头部、包体等
remote_user 由 HTTP Basic Authentication 协议传入的用户名(auth_basic模块)
request_body_file 临时存放请求包体的文件
1. 如果包体非常小,则不会存文件
2. client_body_in_file_only 强制所有包体存入文件,且可决定是否删除
request_body 请求中的包体,这个变量当且仅当使用反向代理,且设定用内存暂存包体时才有效
request 原始的 url请求,含有方法与协议版本,如:GET /?a=1&b=22 HTTP/1.1
host 1. 先从请求行中取
2. 如果含有 Host 头部,则用其值替换掉请求行中的 主机名
3. 如果前两者都取不到,则使用匹配上的 server_name

其他相关的变量

http_头部名字:返回一个具体请求头部的值

特殊:

TCP 连接相关的变量

image-20200404212218011

image-20200404212251875

Nginx 处理请求过程中产生的变量

image-20200404212418050

image-20200404212554157

发送 HTTP 响应时相关的变量

image-20200404212654421

Nginx 系统变量

image-20200404212743400

防盗链:referer 模块

场景:某网站通过 url 引用了你的页面,当用户在浏览器上点击你的 url 时,http 请求的头部中会通过 referer 头部,将改网站当前的 url 带上,告诉服务器本请求是由这个页面发起的。

目的:拒绝非正常的网站访问我们的站点。

思路:通过 referer 模块,用 invalid_referer 变量根据配置判断 referer 头部是否合法。

模块:默认编译进 nginx ,通过 –without-http_referer_module 禁用

指令:

valid_referers指令

可同时携带多个参数,表示多个 referer 头部都生效。

invalid_referer变量

示例:

server {
    server_name referer.turbobin.com;
    
    error_log logs/my_error.log debug;
    root html;
    
    location / {
        valid_referers none blocked server_names
            *.turbobin.site www.turbobin.org/html/
            ~\.google\.;
        
        if ($invalid_referer) {
            return 403
        }
        
        return 200 'valid referer\n';
    }
}

image-20200405112055278

指令:

变量:

示例:

原请求:

/test.txt?md5=md5_string&expires=时间戳

命令行生成 md5 值:

echo -n '时间戳+URL+客户端IP+密钥' | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d

或者使用 python 生成 md5值

nginx 配置:

secure_link $arg_md5,&arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr secete"; 

nginx 配置示例:

server {
    server_name turbobin.com;
    default_type text/plain;
    
    location / {
        secure_link &arg_md5,&arg_expires;
        secure_link_md5 "$secure_link_expires$uri$remote_addr secete";
        
        if ($secure_link = "") {
            return 403;
        }
        if ($secure_link = 0) {
            return 410;
        }
        return 200 '$secure_link:&secure_link_expires \n';
       
    }
}

仅对URL进行hash的简单办法:

原理:

示例:

原请求:link

生成安全的请求:/prefix/md5/link

命令行生成 md5:echo -n 'link+secret' | openssl md5 -hex

示例配置:

server {
    server_name turbobin.com;
    default_type text/plain;
    
    location /prefix/ {
        secure_link_secret my_secret;
        
        if ($secure_link = "") {
            return 403;
        }
        
        rewrite ^ /secure/$secure_link;
    }
    
    location /secure/ {
        internal;
        alias html/;
    }
}

先生成 md5 值:

$ echo -n 'test1.txtmysecret' | openssl md5 -hex
(stdin)= 286323bc57bae84066d0b5031265159f

下面访问安全链接:

curl 'turbobin.com/prefix/286323bc57bae84066d0b5031265159f/test1.txt'

这时候可以访问到 html 目录下 test1.txt 文件的内容。

通过 map 模块映射新变量

模块ngx_http_map_module默认编译进了 nginx。

功能:基于已有变量,使用类似 switch {case: ... default: ...}语法创建新变量,为其他基于变量值实现功能的模块提供更多可能性。

指令

规则

已有变量:

case 规则匹配顺序:

default 规则:

其他:

示例:

map $status $normal {
    ~^2  1;
    ~^3  1;
    default 0;
}
map $status $abnormal {
    ~^2  0;
    ~^3  0;
    default 1;
}
map $uri $need_report {
    /banner/adstat 1;
    default 0;
}

server {
    listen 80;
    server_name api.turbobin.com;
    access_log  logs/bad_access.log access if=$abnormal;
    access_log  logs/access.log main if=$normal;
    access_log  syslog:server=100.119.167.81:12304 graylog2_format_api if=$need_report;
}

实现 AB 测试:split_clients 模块

image-20200405155943323

指令:split_client string $variable {...};

示例:

split_client "${http_userid}" $variant {	# http_头部变量,表示从http头部取值
    0.51%	one;
    20.0%	two;
    50.5%	three;
    10%		four;
    *		"";
}

server {
    listen 8089;
    server_name split.turbobin.com;
    location / {
        return 200 'AB test num:$variant \n';
    }
}

发送请求:

curl -H "userid: 234879815" split.turbobin.com:8089;

将会返回在百分比范围内对应的值。

geo 模块:根据客户端地址创建新的变量

模块:ngx_http_geo_module,默认编译进了 nginx

功能:根据 IP 地址创建新的变量

语法:geo [$address] $variable {...}

规则:

示例:

geo $country {
    default ZZ;
    #include conf/geo.conf;
    proxy 120.24.165.101;
    
    127.0.0.0/24	US;		# 表示 127.0.0.1 ~ 127.0.0.255地址段
    127.0.0.1/32	RU;
    10.1.0.0/16		RU;		# 表示 10.0.0.1 ~ 10.0.255.244地址段
    192.168.1.0/24	UK;
}

server {
    server_name geo.turbobin.com;
    location / {
        return 200 '$country\n';
    }
}

注意事项:

24表示子网掩码:255.255.255.0

16表示子网掩码:255.255.0.0

8表示子网掩码:255.0.0.0

依次发送请求:

curl -H 'X-Forwarded-For: 10.1.0.0,127.0.0.1,192.168.1.123' geo.turbobin.com	# 返回: UK
curl -H 'X-Forwarded-For: 10.1.0.0' geo.turbobin.com	# 返回 RU
curl -H 'X-Forwarded-For: 10.1.0.0,127.0.0.1' geo.turbobin.com	# 命中最长匹配,返回 RU

geoip 模块:获取用户的地理位置

基于 MaxMind 数据库从客户端地址获取变量:

image-20200405173826889

指令:

与 conunty 有关的变量:

与 city 有关的变量:

image-20200405174710638

代理 ip 网站,用于测试:http://www.goubanjia.com/

对客户端使用 keepalive 提升连接效率

功能:多个 HTTP 请求通过复用 TCP 连接实现以下功能

协议

对客户端 keepalive 行为控制的指令

 


关注微信公众账号「曹当家的」,订阅最新文章推送

Table of Contents