初识 Nginx

安装部署

本次安装 nginx-1.21.6.tar.gz,步骤略。

目录结构

进入Nginx的主目录这些文件夹

1
client_body_temp  conf  fastcgi_temp  html  logs  proxy_temp  sbin  scgi_temp  uwsgi_temp

其中这几个文件夹在刚安装后是没有的,主要用来存放运行过程中的临时文件

1
client_body_temp fastcgi_temp proxy_temp scgi_temp
  • conf

    用来存放配置文件相关

  • html

    用来存放静态文件的默认目录 html、css等

  • sbin

    nginx的主程序

  • logs

    日志相关

基本运行原理

image-20220829154852615

最小配置

满足nginx正常运行的最小配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
worker_processes  1; # 默认为1,表示开启一个业务进程


events {
worker_connections 1024; # 单个业务进程可接受连接数
}


http {
include mime.types; # 引入http mime类型
default_type application/octet-stream; # 如果mime类型没匹配上,默认使用二进制流的方式传输。

sendfile on; # 使用linux的sendfile(socket, file, len)高效网络传输,也就是数据0拷贝。

keepalive_timeout 65; # 保持连接超时时间

# 虚拟主机配置
server {
listen 80; # 监听端口号
server_name localhost; # 主机名或域名

# 配置server_name后面跟的uri,后面详解
location / { # 匹配路径
root html; # 文件根目录
index index.html index.htm; # 默认页名称
}

error_page 500 502 503 504 /50x.html; # 报错编码对应页面
location = /50x.html {
root html;
}
}
}

servername匹配规则

完整匹配

可以在同一servername中匹配多个域名

1
server_name  vod.mmban.com www1.mmban.com;

通配符匹配

1
server_name  *.mmban.com

通配符结束匹配

1
server_name  vod.*;

正则匹配

1
server_name  ~^[0-9]+\.mmban\.com$;

虚拟主机

创建目录

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@instance-jwzbgijw conf]# cd /
[root@instance-jwzbgijw /]# mkdir www
[root@instance-jwzbgijw /]# cd www
[root@instance-jwzbgijw www]# mkdir picture
[root@instance-jwzbgijw www]# mkdir video
[root@instance-jwzbgijw www]# ll
total 8
drwxr-xr-x 2 root root 4096 Aug 29 17:44 picture
drwxr-xr-x 2 root root 4096 Aug 29 17:44 video
[root@instance-jwzbgijw www]# cd picture/
[root@instance-jwzbgijw picture]# vi index.html # this is picture.
[root@instance-jwzbgijw picture]# cd ../video
[root@instance-jwzbgijw video]# vi index.html # this is video.

配置主机

如果有域名并且已经解析到当前服务器,可以将主机名替换成具体域名,例如 aaa.域名或bbb.域名。需要注意如果是上述格式,需要在域名解析是配置*.域名,并且当servername不同时端口相同不会产生冲突。

image-20220829175351833

重启nginx

1
systemctl restart nginx.service

测试

image-20220829175623646

image-20220829175739258

反向代理

(隧道式代理)所有请求必须经过nginx,并且返回的数据也必须经过nginx,所以如果nginx的带宽小,纵使服务器带宽很大也无用。

image-20220829215512709

当然也有其它的反向代理,例如请求只经过代理服务器,返回的数据可直接由应用服务器返回到用户。

1
proxy_pass http://www.baidu.com;

eg:

image-20220829224720773

图中注释掉那两行的原因很明显,配置了反向代理,访问的路径只是转发给另一服务器进行处理。而不是自身进行处理。

重启nginx服务后访问本服务器ip就会跳转到百度网站去。当然也可以nginx转nginx,随你。

负载均衡

基于反向代理的负载均衡。nginx将请求分配到不同服务器中去,减少单一服务器的压力。

1
proxy_pass http://httpd; # httpd 随便取的
1
2
3
4
5
# 注意跟server同一级别
upstream httpd {
server 192.168.44.102:8008;
server 192.168.43.103:8088;
}

负载均衡策略

  • 轮询

    默认情况下使用轮询方式,逐一转发,这种方式适用于无状态请求。

  • weight(权重)

    指定轮询机率,weight和访问比率成正比,用于后端服务器性能不均的情况。

    1
    2
    3
    4
    5
    upstream httpd {
    server 127.0.0.1:8050 weight=10 down;
    server 127.0.0.1:8060 weight=1;
    server 127.0.0.1:8060 weight=1 backup;
    }
    • down:表示当前的server暂时不参与负载
    • weight:默认为1.weight越大,负载的权重就越大。
    • backup: 其它所有非backup机器down或者忙的时候,请求backup机器,正常情况下不使用backup备用机。
  • ip_hash

    根据客户端的ip地址转发同一台服务器,可以保持回话。

  • least_conn

    最少连接访问

  • url_hash

    根据用户访问的url定向转发请求

  • fair

    根据后端服务器响应时间转发请求

动静分离

将静态资源保存在nginx服务器里,而动态资源保存在应用服务器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
location / {
proxy_pass http://127.0.0.1:8080; # 配置反向代理访问到本机tomcat服务器上
}

# 配置静态资源
# 以/css为例,localhost/css 请求会自动去访问static目录下的css文件夹
location /css {
root /usr/local/nginx/static;
index index.html index.htm;
}

location /images {
root /usr/local/nginx/static;
index index.html index.htm;
}

location /js {
root /usr/local/nginx/static;
index index.html index.htm;
}

location 匹配规则

/ 通用匹配,任何请求都会匹配到,但是优先级低

= 精准匹配,不是以指定模式开头

~ 正则匹配,区分大小写

~* 正则匹配,不区分大小写

^~ 非正则匹配,匹配以指定模式开头的 location

location 匹配顺序

  • 多个正则 location 直接按书写顺序匹配,成功后就不会继续往后面匹配
  • 普通 (非正则) location会一直往下,直到找到匹配度最高的(最大前缀匹配)
  • 当普通location与正则location同时存在,如果正则匹配成功,则不会再执行普通匹配
  • 所有类型location存在时,“=”匹配 > “^~”匹配 > 正则匹配 > 普通(最大前缀匹配)

上述静态资源location合一:

1
2
3
4
5
# 表示 ~* 后面的路径不区分大小写
location ~*/(css|img|js) {
root /usr/local/nginx/static;
index index.html index.htm;
}

alias与root

1
2
3
4
location /css {
alias /usr/local/nginx/static/css; # 多加了/css
index index.html index.htm;
}
  • alias 指定的目录是准确的,即 location 匹配访问的 path 目录下的文件直接是在 alias 目录下查找的;
  • root 指定的目录是 location 匹配访问的 path 目录的上一级目录,这个 path 目录一定要是真实存在 root 指定目录下的;

UrlRewrite

语法格式及参数语法:

1
2
3
4
5
6
7
8
9
10
11
12
rewrite 是实现URL重写的关键指令,根据regex,重定向到replacement,结尾是flag标记。

rewrite <regex> <replacement> [flag];
关键字 正则 替代内容 flag标记

rewrite参数的标签段位置: server,location,if

flag标记说明:
last #本条规则匹配完成后,继续向下匹配新的location URI规则
break #本条规则匹配完成即终止,不再匹配后面的任何规则
redirect #返回302临时重定向,浏览器地址会显示跳转后的URL地址
permanent #返回301永久重定向,浏览器地址栏会显示跳转后的URL地址

实例

1
2
3
4
5
location / {
# 当客户端访问 localhost/2.html,实际访问的是 localhost/index.jsp?pageNum=2
rewrite ^/2.html$ /index.jsp?pageNum=2 break;
proxy_pass http://127.0.0.1:8080;
}
1
2
3
4
5
location / {
# $1 代表第一个匹配到的规则
rewrite ^/([0-9]+).html$ /index.jsp?pageNum=$1 break;
proxy_pass http://127.0.0.1:8080;
}

同时使用负载均衡

1
2
3
4
5
6
7
8
9
upstream httpds {
server 192.168.44.102 weight=8 down;
server 192.168.44.103:8080 weight=2;
server 192.168.44.104:8080 weight=1 backup;
}
location / {
rewrite ^/([0-9]+).html$ /index.jsp?pageNum=$1 redirect;
proxy_pass http://httpds;
}

防盗链

例如用户请求了index.html,html里内嵌了js、css等文件地址,客户端会再次请求去访问这些资源,这属于二次访问。根据http协议规定,二次访问需要在请求头加上referer,浏览器会自动完成。referer里头有客户端的一些信息,后面就可以通过referer判断是否是非法请求。

1
valid_referers none | blocked | server_names | strings ....;
  • none, 允许 Referer 头域不存在进行访问。
  • blocked,检测 Referer 头域的值被防火墙或者代理服务器删除或伪装的情况。这种情况该头域的值不以 “http://” 或 “https://” 开头。也就是不以http://或https://也可以进行访问。
  • server_names ,设置一个或多个 URL ,检测 Referer 头域的值是否是这些 URL 中的某一个。允许配置的URL进行访问。

在需要防盗链的location中配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
location ~*/(css|img|js) {
# 允许192.168.44.101访问(也可以使用域名),其他来源不允许
valid_referers 192.168.44.101;
if ($invalid_referer) {
return 403;
# rewrite ^/ /img/x.png break; # 也可以不return,直接去访问某张图片
}
root /usr/local/nginx/static;
index index.html index.htm;
}

# 返回对应错误页面
error_page 403 /403.html;
location = /403.html{
root html;
}

高可用

防止nginx挂掉,需配置多台nginx。

例如在两台nginx(有各自的ip)上跑keepalived,两个keepalived之间会进行通讯,以互相检测对方是否存活。

之后提供一个虚拟ip(vip)给客户端访问,vip会在两个nginx中进行切换。客户端并不知道真正访问的是哪台nginx。

安装Keepalived

  • 编译安装

    下载地址 https://www.keepalived.org/download.html#

    使用 ./configure 编译安装

    如遇报错提示:

    1
    2
    3
    configure: error:
    !!! OpenSSL is not properly installed on your system. !!!
    !!! Can not include OpenSSL headers files. !!!

    安装依赖

    1
    yum install openssl-devel
  • yum安装

    1
    yum install keepalived

配置

使用yum安装后配置文件在 /etc/keepalived/keepalived.conf

第一台nginx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
! Configuration File for keepalived
global_defs {
router_id lb111
}
vrrp_instance aaa {
state MASTER
interface ens33
virtual_router_id 51
priority 100
advert_int 1
# 分组配置,同一组配置需一样
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.44.200
}
}

第二台nginx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
! Configuration File for keepalived
global_defs {
router_id lb110
}
vrrp_instance aaa {
state BACKUP
interface ens33
virtual_router_id 51
priority 50 # 优先级,第一台为100,所以刚开始为master
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.44.200
}
}

启动服务

1
systemctl start keepalived

Https证书

不安全的http协议

客户端发送给服务器的数据中间会经过很多网关,如果传输期间数据被抓包,解包之后就可直接查看客户端发送的明文。所以使用http协议不安全。

对称加密

客户端和服务端规定同一加密算法,发送数据时根据规定的加密算法对数据进行加密,接收端在根据算法对数据进行解密。弊端就是加密算法就那几套固定的,拦截者只要有心同样不安全。

非对称加密

客户端先请求服务器的443端口下载公钥,之后客户端通过公钥+算法对明文进行加密,服务端通过私钥+算法对密文进行解密,返回的明文再通过私钥+算法加密,最后客户端通过公钥+算法进行解密。安全的前提条件是公钥加密的数据用公钥解不开。这就是https底层算法。

期间私钥是不进行传输的,即使公钥被拿到,也解不开加密的数据。但是没有绝对安全的系统,当客户端发送下载公钥请求时,该请求可被拦截下来,拦截者代替客户端去请求服务器并得到公钥,然后伪造一个公钥给客户端,客户端通过伪造的公钥加密的数据即可被拦截者解密,后续拦截者还可通过解密的数据通过之前服务器下载的公钥进行加密发送给服务端。

ca签名

服务端生成的公钥需要先上传到ca机构,ca机构拿到公钥后需确认公钥是否属于本人,比如叫你在某个目录下上传ca机构生成的随机字符文件,认证成功后ca机构会通过自身的私钥+算法对上传的公钥进行加密生成一个证书。将证书颁发给服务器端。服务端再将证书下发给客户端。这时证书不会被拦截者伪造。因为ca机构的私钥只有ca机构知道,拦截者不能获得ca机构私钥自然不能伪造证书。需要注意的是证书可以被拦截者解开的。客户端接收到证书后通过电脑内置的ca公钥解开证书拿到服务器公钥。重点就是内置ca公钥,不会在任何网络上传输,也就不可能被伪造。除非入侵操作系统。

单机垂直扩容

硬件资源增加。省流:花钱

1
2
3
4
5
6
7
8
9
10
云服务资源增加
整机:IBM、浪潮、DELL、HP等
CPU/主板:更新到主流
网卡:10G/40G网卡
磁盘:SAS(SCSI) HDD(机械)、HHD(混合)、SATA SSD、PCI-e SSD、 MVMe SSD
SSD
多副本机制
系统盘/热点数据/数据库存储
HDD
冷数据存储

水平扩展

集群化

会话维持

多台功能相同机子一起工作,之前通过 uptream+proxy_pass 实现。但需要解决访问不同机子需要重复登录的问题。

可以使用Redis + SpringSession

这是java的解决方案,下面讲nginx的解决。

高级负载均衡

ip_hash

根据用户ip选择服务器,不适合大型项目。弊端:例如网吧同一ip有好几台电脑,这些电脑的所有请求都会被转发到同一服务器上;又或者所访问的服务器宕机,会话也就没了,因为同一ip访问的都是同一服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream httpds {
ip_hash;
server 192.168.44.102;
server 192.168.44.103;
}

server {
listen 80;
server_name localhost;

location / {
proxy_pass http://httpds;
}
}

其他hash

hash $cookie_jsessionidhash $request_uri(通过请求 的uri负载均衡),当然nginx还可以对很多http协议里头的东西做hash。

1
2
3
4
5
6
upstream httpds {
hash $cookie_jsessionid;
# hash $request_uri;
server 192.168.44.102;
server 192.168.44.103;
}

lua

后面讲

sticky

第三方模块sticky完成对Nginx的负载均衡

使用参考

http://nginx.org/en/docs/http/ngx_http_upstream_module.html#sticky

sticky是第三方模块,需要重新编译Nginx,它可以对Nginx这种静态文件服务器使用基于cookie的负载均衡

  1. 下载模块

    https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/src/master/

  2. 上传解压

    1
    tar -xzvf nginx-goodies-nginx-sticky-module-ng-c78b7dd79d0d.tar.gz -C /opt/module

    并修改解压后的ngx_http_sticky_misc.h文件,在12行添加

    1
    2
    #include <openssl/sha.h>
    #include <openssl/md5.h>
  3. 重新编译Nginx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    tar -zxvf nginx-1.21.6.tar.gz
    cd nginx-1.21.6/
    ./configure --prefix=/usr/local/nginx --add-module=/opt/module/nginx-goodies-nginx-sticky-module-ng-c78b7dd79d0d
    make
    make upgrade
    cd objs
    mv nginx /usr/local/nginx/sbin/nginx
    cd /usr/local/nginx/sbin
    ./nginx -V
    nginx version: nginx/1.21.6
    built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
    configure arguments: --prefix=/usr/local/nginx --add-module=/opt/module/nginx-goodies-nginx-sticky-module-ng-c78b7dd79d0d
  4. 配置文件

    1
    2
    3
    4
    5
    6
    7
    upstream httpds {
    # 单独 sticky; 也行
    # cookie name 不配默认为route,expires:cookie过期时间
    sticky name=route expires=6h;
    server 192.168.44.102;
    server 192.168.44.103;
    }

KeepAlive

http协议特性,保持和客户端和服务端的连接。在http协议header中可以看到当前连接状态

image-20220830190531307

keepalive_timeout:活跃时间,确认连接之后65秒内不再进行确认,默认连接状态是成功的。65秒之后才会再次确认。

什么时候使用?

明显的预知用户会在当前连接上有下一步操作。复用连接,有效减少握手次数,尤其是https建立一次连接开销会更大

什么时候不用?

访问内联资源一般用缓存,不需要keepalive。长时间的tcp连接容易导致系统资源无效占用

客户端使用 keepalive

  • keepalive_time:限制 keepalive 保持连接的最大时间。1.19.10新功能

  • keepalive_timeout:用于设置Nginx服务器与客户端保持连接的超时时间,踢出不活动连接

    keepalive_timeout = 0 即关闭

  • send_timeout

    表示nginx接收到请求,处理后返回给客户端期间的时间,如果大于这个时间则关闭连接。默认60s,此处有坑

  • keepalive_request

    默认1000,单个连接中可处理的请求数

  • keepalive_disable

    不对某些浏览器建立长连接,默认msie6

对上游服务器使用keepalive

首先需要配置使用http1.1协议。以便建立更高效的传输,默认使用http1.0,在http1.0中需要配置header才能

在Upstream中所配置的上游服务器默认都是用短连接,即每次请求都会在完成之后断开

upstream中配置

  • keepalive 100;

    向上游服务器的保留连接数

  • keepalive_timeout

    连接保留时间

  • keepalive_requests

    一个tcp复用中 可以并发接收的请求个数

server中配置

1
2
3
proxy_http_version 1.1; # 配置http版本号
# 默认使用http1.0协议,需要在request中增加”Connection: keep-alive“ header才能够支持,而HTTP1.1默认支持。
proxy_set_header Connection ""; # 清除close信息

AB

压力测试工具

1
yum install httpd-tools

参数说明

  • -n 即requests,用于指定压力测试总共的执行次数。
  • -c 即concurrency,用于指定的并发数。
  • -t 即timelimit,等待响应的最大时间(单位:秒)。
  • -b 即windowsize,TCP发送/接收的缓冲大小(单位:字节)。
  • -p 即postfile,发送POST请求时需要上传的文件,此外还必须设置-T参数。
  • -u 即putfile,发送PUT请求时需要上传的文件,此外还必须设置-T参数。
  • -T 即content-type,用于设置Content-Type请求头信息,例如:application/x-www-form-urlencoded,默认值为text/plain。
  • -v 即verbosity,指定打印帮助信息的冗余级别。
  • -w 以HTML表格形式打印结果。
  • -i 使用HEAD请求代替GET请求。
  • -x 插入字符串作为table标签的属性。
  • -y 插入字符串作为tr标签的属性。
  • -z 插入字符串作为td标签的属性。
  • -C 添加cookie信息,例如:”Apache=1234”(可以重复该参数选项以添加多个)。
  • -H 添加任意的请求头,例如:”Accept-Encoding: gzip”,请求头将会添加在现有的多个请求头之后(可以重复该参数选项以添加多个)。
  • -A 添加一个基本的网络认证信息,用户名和密码之间用英文冒号隔开。
  • -P 添加一个基本的代理认证信息,用户名和密码之间用英文冒号隔开。
  • -X 指定使用的和端口号,例如:”126.10.10.3:88”。
  • -V 打印版本号并退出。
  • -k 使用HTTP的KeepAlive特性。
  • -d 不显示百分比。
  • -S 不显示预估和警告信息。
  • -g 输出结果信息到gnuplot格式的文件中。
  • -e 输出结果信息到CSV格式的文件中。
  • -r 指定接收到错误信息时不退出程序。
  • -h 显示用法信息,其实就是ab -help。

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
[root@instance-jwzbgijw ~]# ab -n 10000 -c 30 http://120.48.54.126/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 120.48.54.126 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software: nginx/1.21.6
Server Hostname: 120.48.54.126
Server Port: 80

Document Path: /
Document Length: 615 bytes

Concurrency Level: 30
Time taken for tests: 17.233 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Total transferred: 8480000 bytes
HTML transferred: 6150000 bytes
Requests per second: 580.30 [#/sec] (mean) # QPS
Time per request: 51.698 [ms] (mean)
Time per request: 1.723 [ms] (mean, across all concurrent requests)
Transfer rate: 480.56 [Kbytes/sec] received # 吞吐量

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 39 217.3 1 3008
Processing: 0 3 58.6 2 5204
Waiting: 0 3 58.6 2 5204
Total: 0 42 227.5 3 6242

Percentage of the requests served within a certain time (ms)
50% 3
66% 3
75% 3
80% 3
90% 3
95% 3
98% 1004
99% 1005
100% 6242 (longest request)

UpStream配置

proxy_pass 向上游服务器请求数据共有6个阶段

  1. 初始化
  2. 与上游服务器建立连接
  3. 向上游服务器发送请求
  4. 处理响应头
  5. 处理响应体
  6. 结束

set_header

设置header

proxy_connect_timeout

与上游服务器连接超时时间、快速失败

proxy_send_timeout

定义nginx向后端服务发送请求的间隔时间(不是耗时)。默认60秒,超过这个时间会关闭连接

proxy_read_timeout

后端服务给nginx响应的时间,规定时间内后端服务没有给nginx响应,连接会被关闭,nginx返回504 Gateway Time-out。默认60秒

proxy_requset_buffering

是否完全读到请求体之后再向上游服务器发送请求

proxy_buffering

是否缓冲上游服务器数据

proxy_buffers 32 64k

缓冲区大小 32个 64k大小内存缓冲块

proxy_buffer_size

header缓冲区大小

proxy_temp_file_write_size 8k

当启用从代理服务器到临时文件的响应的缓冲时,一次限制写入临时文件的数据的大小。 默认情况下,大小由proxy_buffer_size和proxy_buffers指令设置的两个缓冲区限制。 临时文件的最大大小由proxy_max_temp_file_size指令设置。

proxy_max_temp_file_size 1024m

临时文件最大值

proxy_temp_path

临时文件位置

客户端配置

可配置位置:

  • http
  • server
  • location

client_body_buffer_size

对客户端请求中的body缓冲区大小

默认32位8k 64位16k

如果请求体大于配置,则写入临时文件

client_header_buffer_size

设置读取客户端请求体的缓冲区大小。 如果请求体大于缓冲区,则将整个请求体或仅将其部分写入临时文件。 默认32位8K。 64位平台16K。

client_max_body_size 1000M

默认1m,如果一个请求的大小超过配置的值,会返回413 (request Entity Too Large)错误给客户端

将size设置为0将禁用对客户端请求正文大小的检查。

client_body_timeout

指定客户端与服务端建立连接后发送 request body 的超时时间。如果客户端在指定时间内没有发送任何内容,Nginx 返回 HTTP 408(Request Timed Out)

client_header_timeout

客户端向服务端发送一个完整的 request header 的超时时间。如果客户端在指定时间内没有发送一个完整的 request header,Nginx 返回 HTTP 408(Request Timed Out)。

client_body_temp_path

在磁盘上客户端的body临时缓冲区位置

client_body_in_file_only on

把body写入磁盘文件,请求结束也不会删除

client_body_in_single_buffer

尽量缓冲body的时候在内存中使用连续单一缓冲区,在二次开发时使用$request_body读取数据时性能会有所提高

client_header_buffer_size

设置读取客户端请求头的缓冲区大小

如果一个请求行或者一个请求头字段不能放入这个缓冲区,那么就会使用large_client_header_buffers

large_client_header_buffers

默认8k

重试机制

http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream

max_fails

最大失败次数

0为标记一直可用,不检查健康状态

fail_timeout

失败时间

当fail_timeout时间内失败了max_fails次,标记服务不可用

fail_timeout时间后会再次激活次服务

proxy_next_upstream

当后端服务器返回指定的错误时,将请求传递到其他服务器。

error与服务器建立连接,向其传递请求或读取响应头时发生错误;

timeout在与服务器建立连接,向其传递请求或读取响应头时发生超时;

invalid_header服务器返回空的或无效的响应;

http_500服务器返回代码为500的响应;

http_502服务器返回代码为502的响应;

http_503服务器返回代码为503的响应;

http_504服务器返回代码504的响应;

http_403服务器返回代码为403的响应;

http_404服务器返回代码为404的响应;

http_429服务器返回代码为429的响应;

比如有这么一个场景:一个用于导入数据的web页面,上传一个excel,通过读取、处理excel,向数据库中插入数据,处理时间较长,且为同步操作(即处理完成后才返回结果)。若处理时间超过nginx配置的响应等待时间(proxy_read_timeout)为30秒,就会触发超时重试,将请求又打到另一台。如果处理中没有考虑到重复数据的场景,就会发生数据多次重复插入!

proxy_next_upstream_timeout

重试最大超时时间

proxy_next_upstream_tries

重试次数,包括第一次

proxy_next_upstream_timeout时间内允许proxy_next_upstream_tries次重试

用户IP

使用nginx做反向代理时,访问上游服务器的是nginx而不是用户,上游服务器正常只能拿到nginx服务器ip而不能拿到用户真实ip。

X-Real-IP

额外模块,不推荐使用

setHeader

在nginx中向请求头额外加入一个请求头,而value就是用户ip。之后上游服务器通过获取对应请求头内容就可拿到用户ip。

当然如果有多级nginx代理服务器,应该在与用户直连的nginx中进行配置。

1
proxy_set_header X-Forwarded-For $remote_addr;

Gzip

客户端在请求头配置可接收gzip,服务端就可将响应数据通过gzip压缩再传输给客户端。最后客户端解压缩即可。

经过gzip压缩后的数据变小了,同时也可设置压缩等级,等级越大数据就会被压缩的更小。

f

image-20220831114045154

动态压缩

作用域: http, server, location

gzip on

开关,默认关闭

gzip_buffers 32 4k|16 8k

缓冲区大小

gzip_comp_level 1

压缩等级 1-9,数字越大压缩比越高

gzip_http_version 1.1

使用gzip的最小版本

gzip_min_length

设置将被gzip压缩的响应的最小长度。 长度仅由Content-Length响应报头字段确定。

gzip_proxied 多选

off 为不做限制,作为反向代理时,针对上游服务器返回的头信息进行压缩

expired - 启用压缩,如果header头中包含 “Expires” 头信息

no-cache - 启用压缩,如果header头中包含 “Cache-Control:no-cache” 头信息

no-store - 启用压缩,如果header头中包含 “Cache-Control:no-store” 头信息

private - 启用压缩,如果header头中包含 “Cache-Control:private” 头信息

no_last_modified - 启用压缩,如果header头中不包含 “Last-Modified” 头信息

no_etag - 启用压缩,如果header头中不包含 “ETag” 头信息

auth - 启用压缩,如果header头中包含 “Authorization” 头信息

any - 无条件启用压缩

gzip_vary on

增加一个header,适配老的浏览器 Vary: Accept-Encoding

gzip_types

哪些mime类型的文件进行压缩

gzip_disable

禁止某些浏览器使用gzip

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gzip on;
gzip_buffers 16 8k;
gzip_comp_level 6;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_types
text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml image/svg+xml
text/javascript application/javascript application/x-javascript
text/x-json application/json application/x-web-app-manifest+json
text/css text/plain text/x-component
font/opentype application/x-font-ttf application/vnd.ms-fontobject
image/x-icon;
gzip_disable "MSIE [1-6]\.(?!.*SV1)"; # 最好不写正则,否则每次请求都需要匹配正则消耗性能

缺点就是使用了动态压缩就不能 send_file 0 拷贝。因为动态压缩是请求过来才进行压缩的。

静态压缩

提前把文件压缩成 .gz 格式,请求来了,直接返回即可。

http_gzip_static_module

需要重新编译nginx

1
2
3
4
5
6
cd /usr/local/nginx-1.21.6
./configure --prefix=/usr/local/nginx/ --with-http_gzip_static_module
make
cd objs
mv nginx /usr/local/nginx/sbin/
systemctl restart nginx.service

配置

1
2
3
4
5
# 作用域:http、server、location
gzip_static on|off|always;
# on:检查客户端是否支持gzip
# off:不使用静态压缩
# always:不管客户端是否支持都进行压缩

ngx_http_gunzip_module

当使用gzip_static always;时,如果客户端不能接收gzip格式文件,客户端就不能解开gzip拿到数据。可以配合ngx_http_gunzip_module 模块帮助不支持gzip的客户端解压本地文件。当数据发出去前,判断用户能否接收gzip,不能接受则对文件进行解压,再传输给客户端。

1
2
3
# 作用域同上
gunzip on|off;
gunzip_buffers 32 4K|16 8K;

使用前同样需要重新编译nginx

1
2
3
cd /usr/local/nginx-1.21.6
./configure --prefix=/usr/local/nginx/ --with-http_gunzip_module --with-http_gzip_static_module
之后同上

进行测试时可在浏览器设置能否接受gzip。

Brotli

同Gzip,可以共存,Brotli 优先级比较高,如果客户端不支持再降级到 Gzip

两个好像只能在https下才能搞。

  1. 下载

  2. 解压缩到module文件夹

  3. 挪位置

    1
    2
    cd /opt/module/brotli-1.0.9
    mv ./* /opt/module/ngx_brotli-1.0.0rc/deps/brotli/
  4. 模块化编译

    1
    2
    3
    4
    5
    6
    7
    8
    9
    cd /usr/local/nginx-1.21.6
    ./configure --with-compat --add-dynamic-module=/opt/module/ngx_brotli-1.0.0rc --prefix=/usr/local/nginx/
    make
    mkdir /usr/local/nginx/modules
    cd objs
    cp ngx_http_brotli_filter_module.so /usr/local/nginx/modules/
    cp ngx_http_brotli_static_module.so /usr/local/nginx/modules/
    mv nginx /usr/local/nginx/sbin/
    systemctl restart nginx.service
  5. 配置文件

    其它用法查官网

    1
    2
    load_module "/usr/local/nginx/modules/ngx_http_brotli_filter_module.so";
    load_module "/usr/local/nginx/modules/ngx_http_brotli_static_module.so";
    1
    2
    3
    4
    5
    6
    brotli on;
    brotli_static on;
    brotli_comp_level 6;
    brotli_buffers 16 8k;
    brotli_min_length 20;
    brotli_types text/plain text/css text/javascript application/javascript text/xml application/xml application/xml+rss application/json image/jpeg image/gif image/png;

合并请求

减少并发请求

例如某个html页面内嵌多个js、css请求,客户端发送一个请求,当nginx接收到该请求,会去读取对应css,合并成一个文件发回客户端。

  1. 下载

    https://github.com/alibaba/nginx-http-concat

  2. unzip解压并重新编译ngnix

    1
    2
    3
    ./configure --prefix=/usr/local/nginx --add-module=/opt/module/解压后的目录

    其它同之前
  3. 配置

    1
    2
    3
    4
    5
    6
    7
    # 作用域:location
    location / {
    concat on;
    concat_max_files 30;
    root html;
    index index.html index.htm;
    }
  4. 测试

    1
    2
    3
    4
    5
    6
    <link href="font.css" rel="stylesheet" >
    <link href="bg.css" rel="stylesheet" >

    <!------------------改成--------------------->

    <link href="??font.css,bg.css" rel="stylesheet" >

图片等静态资源也可以进行合并。

资源静态化

案例:客户端请求一个商品列表

正常处理逻辑

  1. 请求发到nginx
  2. nginx转发请求到上游服务器
  3. 服务器从数据库拿到数据
  4. 使用模板引擎将数据渲染成静态页面
  5. 之后将静态页面以流的形式转发给nginx
  6. 再由nginx下发给客户端

资源静态化

  1. 请求发到nginx
  2. nginx查看本地是否有请求的静态页面,有则将本地静态页面直接返回给客户端
  3. 如果没有同样需要将请求转发给服务器进行处理并将静态页面转发给nginx
  4. nginx在本地保存静态页面并下发静态页面给客户端

openresty 强化

可使用openresty强化资源静态化,当nginx发现本地没有请求的静态页面时,不将请求转发给服务器而是由openresty直接去访问数据库拿到数据,并渲染成静态页面,保存到nginx并下发给客户端。

image-20220831160831748

上面只是演示单台nginx,正常使用时有多台nginx共同工作的,所以还会有将静态资源同步到其它nginx的步骤。可以使用 rsync进行同步。而且会有一台nginx专门将静态资源同步到其它nginx,自身不处理请求,因为负荷太大。其它nginx再专门去处理客户端的请求。

合并文件

一个页面可能很复杂,所有内容全都渲染在单一页面不切实际,并且很多页面中有很多固定内容,如果放进每个静态页面会造成冗余,并且如果这些内容某天需要更改,又得更新nginx中所有的静态页面。

解决办法

  • 固定内容抽取单独生成一个静态页面
  • 关联引用,静态页面中可引用其它动态页面(服务器经过处理返回的页面)

一致性问题

资源静态化存在nginx中的静态资源跟数据库真实数据不一致的问题。

SSI

SSI合并服务器端文件。官方文档

ssi on | off

开启/关闭

ssi_min_file_chunk

向磁盘存储并使用sendfile发送,文件大小最小值

ssi_last_modified

是否保留lastmodified

ssi_silent_errors

不显示逻辑错误

ssi_value_length

限制脚本参数最大长度

ssi_types

默认text/html;如果需要其他mime类型需要设置

使用:

类似一个模板引擎,具体使用方法查看官网

include file

静态文件直接引用

1
<!--# include file="footer.html" -->  

include virtual

可以指向location,而不一定是具体文件

include wait

阻塞请求

include set

在virtual基础上设置变量

set

设置临时变量

block

可以声明一个ssi的命令块,里面可以包裹其他命令

config errmsg

在模板中配置报错情况

config timefmt

日期格式化

echo

直接输出变量

  • var变量名称
  • encoding 是否使用特殊编码格式
  • default 变量没有值的时候使用默认值

if

逻辑判断

Rsync

概述

remote synchronize 是一个远程数据同步工具,可通过 LAN/WAN 快速同步多台主机之间的文件。也可以使用 rsync 同步本地硬盘中的不同目录。rsync 是用于替代 rcp 的一个工具,rsync 使用所谓的 rsync算法 进行数据同步,这种算法只传送两个文件的不同部分,而不是每次都整份传送,因此速度相当快。

可以结合 inotify 监控目录变化再调用 rsync 进行同步。实现自动化。

Rsync有三种模式:

  • 本地模式(类似于cp命令)
  • 远程模式(类似于scp命令)
  • 守护进程(socket进程:是rsync的重要功能)

常用选项

选项 含义
-a 包含-rtplgoD
-r 同步目录时要加上,类似cp时的-r选项
-v 同步时显示一些信息,让我们知道同步的过程
-l 保留软连接
-L 加上该选项后,同步软链接时会把源文件给同步
-p 保持文件的权限属性
-o 保持文件的属主
-g 保持文件的属组
-D 保持设备文件信息
-t 保持文件的时间属性
–delete 删除DEST中SRC没有的文件
–exclude 过滤指定文件,如–exclude “logs”会把文件名包含logs的文件或者目录过滤掉,不同步
-P 显示同步过程,比如速率,比-v更加详细
-u 加上该选项后,如果DEST中的文件比SRC新,则不同步
-z 传输时压缩

安装

两端安装

1
yum install -y rsync

开机自启

/etc/rc.local文件中添加

1
rsync --daemon

手动同步

  • 源服务器(也就是负责同步文件到其它服务器)

    编辑配置文件

    1
    vim /etc/rsyncd.conf

    配置需要同步的目录

    1
    2
    [ftp]
    path = /usr/local/nginx/html

    启动

    1
    rsync --daemon
  • 非源服务器

    无需启动 rsync

    执行以下命令可查看源服务器同步目录内容

    1
    rsync --list-only 源服务器IP::ftp/

    拉取同步

    1
    rsync -avz 源服务器IP::ftp/ /usr/local/nginx/html/

    注意 -avz 只能同步增量请求,当源服务器删除某个文件,同步到客户端服务器后并不会把对应文件进行删除。

    如需同步删除:

    1
    rsync -avz --delete 源服务器IP::ftp/ /usr/local/nginx/html/

安全认证

  1. 创建密码文件并降低权限

    不降低权限的话,就算输入正确密码一样拉取不了

    1
    2
    echo "root:root" >> /etc/rsync.password
    chmod 600 /etc/rsync.password
  2. 配置 rsyncd.conf 配置文件

    1
    2
    3
    4
    5
    + auth users = root  # 授权用户
    + secrets file = /etc/rsync.password

    [ftp]
    path = /usr/local/nginx/html
  3. 重启 rsync

    rsync 没有重启命令,所以得杀掉进程

    1
    2
    3
    ps -ef | grep rsync
    kill -9 进程号
    rsync --daemon

    以上步骤都是在源服务器上操作

  4. 拉取同步

    需要指定用户

    1
    rsync -avz root@源服务器ip::ftp/ /usr/local/nginx/html

实时推送

之前都是客户端主动拉取

  1. 推送端安装 inotify(源服务器)

    依赖

    1
    yum install -y automake

    安装

    1
    2
    3
    wget http://github.com/downloads/rvoicilas/inotify-tools/inotify-tools-3.14.tar.gz
    ./configure --prefix=/usr/local/inotify
    make && make install
  2. 解压

    1
    tar -xzvf inotify-tools-3.14.tar.gz
  3. 编译安装

    进入解压后的目录

    1
    2
    3
    ./configure --prefix=/usr/local/inotify
    make
    make install
  4. 客户端服务器设置安全认证

    同上

  5. 客户端服务器配置文件

    1
    2
    3
    4
    5
    6
    auth users = root  # 授权用户
    secrets file = /etc/rsync.password
    read only = no # 使之支持源服务器推送

    [ftp]
    path = /usr/local/nginx/html

    需要将客户端服务器的 rsync 启动

  6. 源服务器创建保存客户端密码的文件

    1
    2
    3
    vi /etc/rsynmc.pwd.client

    root
  7. 自动化脚本(auto.sh)

    1
    2
    3
    4
    5
    6
    #!/bin/bash

    /usr/local/inotify/bin/inotifywait -mrq --timefmt '%d/%m/%y %H:%M' --format '%T %w%f %e' -e close_write,modify,delete,create,attrib,move //usr/local/nginx/html/ | while read file
    do
    rsync -az --delete --password-file=/etc/rsync.pwd.client /usr/local/nginx/html/ root@客户端服务器IP::ftp/
    done

    赋予可执行

    1
    2
    chmod 777 auto.sh
    chmod 777 /usr/local/nginx/html (两端都需要,应该吧)

    之后执行即可

    1
    ./auto.sh
  8. 测试

    在源服务器上随便新建一个文件,文件即可同步到客户端服务器。但是报了一些错,原因是rsync配置文件配置的太简陋了。

    配置用户组即可:

    1
    2
    3
    4
    5
    6
    7
    + uid = root
    + gid = root
    auth users = root # 授权用户
    secrets file = /etc/rsync.password

    [ftp]
    path = /usr/local/nginx/html

多级缓存

多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomcat压力,提升服务性能:

  • 浏览器访问静态资源时,优先读取浏览器本地缓存
  • 访问非静态资源(ajax查询数据)时,访问服务端
  • 请求到达Nginx后,优先读取Nginx本地缓存
  • 如果Nginx本地缓存未命中,则去直接查询Redis(不经过Tomcat)
  • 如果Redis查询未命中,则查询Tomcat
  • 请求进入Tomcat后,优先查询JVM进程缓存
  • 如果JVM进程缓存未命中,则查询数据库

当然还有其它缓存,下面介绍。

缓存又分为两大类:

  • 强制缓存:在有效期内都不需要向服务器请求数据,直接使用缓存。

  • 协商缓存:

    发送请求header中携带Last-Modified(最后更新时间),服务器校验请求头的Last-Modified与自己的是否一致,一致返回304 Not Modified 状态码并继续使用缓存;如不一致则返回数据以及新的Last-Modified。

浏览器缓存

浏览器本地取缓存的位置

  • memorycache

代表资源是直接从内存中拿到的,不会请求服务器。一般已经加载过该资源且缓存在了内存当中,当关闭该页面时,此资源就被内存释放掉了,再次重新打开相同页面时不会出现from memory cache的情况。

  • deskcache

是从磁盘当中取出的,也是在已经在之前的某个时间加载过该资源,不会请求服务器。但是此资源不会随着该页面的关闭而释放掉,因为是存在硬盘当中的,下次打开仍会from disk cache。

协商缓存

默认使用协商缓存。

last-modifiedetag

ffhttp1.1支持

在HTTP协议中If-Modified-Since和If-None-Match分别对应Last-Modified和ETag

Entity Tag 的缩写,中文译过来就是实体标签的意思.

HTTP中并没有指定何种算法生成ETag,哈希是比较理想的选择。

Etag 会根据 last-modified文件大小 生成。

在计算Etag的时候,会产生CPU的耗费,所以也可以用时间戳,但这样直接使用Last-Modified即可。

ETag 用来校验用户请求的资源是否有变化,作用和lastmodified很像,区别是lastmodified精确到秒,ETag可以用hash算法来生成更精确的比对内容。

当用户首次请求资源的时候返回给用户数据和200状态码并生成ETag,再次请求的时候服务器比对ETag,没有发生变化的话返回304。

Cache-Control(强制缓存)直接是通过不请求来实现,而ETag是会发请求的,只不过服务器根据请求的东西的内容有无变化来判断是否返回请求的资源(同last-modified)。默认last-modifiedetag同时工作(不同浏览器工作方式可能不同)。

关闭协商缓存

nginx 关闭 etag,关闭后浏览器不使用 etag 但是还是会根据 last-modified 去进行协商缓存。

1
2
# 作用域 location
etag off;

我们知道协商缓存nginx会比对last-modified并返回相应状态码,浏览器再根据状态码决定是否需要请求数据。

所以此时有两种思路完全关闭协商缓存

  1. 禁止nginx返回状态码304(304代表数据没变,无需请求)

    此时浏览器虽然会发送if_modified_since给nginx请求比对日期,但nginx不理它,自然只能去请求服务端数据。

    1
    2
    etag off;
    + if_modified_since off;
  2. 禁用 last-modified

    也就是浏览器请求时nginx不会发最后更新时间给浏览器,后续自然没法再根据它进行比对。

    1
    2
    etag off;
    + add_header Last-Modified "";

注意 last-modified 与ssi 冲突,配置了 ssi 就不能使用 last-modified。

开启强制缓存

并不是规定一定使用缓存,而规定什么时候用缓存什么时候不用。

有两种方式

  1. expires

    设置过期时间

    1
    2
    3
    etag off;
    if_modified_since off;
    + expires 300s;
  2. cache-control

    http1.1的规范,功能比较强大。

    1
    2
    3
    etag off;
    if_modified_since off;
    + add_header cache-control "no-store";
标记 类型 功能
public 响应头 响应的数据可以被缓存,客户端和代理层都可以缓存
private 响应头 可私有缓存,客户端可以缓存,代理层不能缓存(CDN,proxy_pass)
no-cache 请求头 可以使用本地缓存,但是必须发送请求到服务器回源验证
no-store 请求和响应 禁用缓存
max-age 请求和响应 文件可以在浏览器中缓存的时间以秒为单位
s-maxage 请求和响应 用户代理层缓存,CDN下发,当客户端数据过期时会重新校验
max-stale 请求和响应 缓存最大使用时间,如果缓存过期,但还在这个时间范围内则可以使用缓存数据
min-fresh 请求和响应 缓存最小使用时间,
must-revalidate 请求和响应 当缓存过期后,必须回源重新请求资源。比no-cache更严格。
因为HTTP 规范是允许客户端在某些特殊情况下直接使用过期缓存的,
比如校验请求发送失败的时候。那么带有must-revalidate的缓存必须校验,
其他条件全部失效。
proxy-revalidate 请求和响应 和must-revalidate类似,只对CDN这种代理服务器有效,
客户端遇到此头,需要回源验证
stale-while-revalidate 响应 表示在指定时间内可以先使用本地缓存,后台进行异步校验
stale-if-error 响应 在指定时间内,重新验证时返回状态码为5XX的时候,可以用本地缓存
only-if-cached 响应 那么只使用缓存内容,如果没有缓存 则504 getway timeout

两种方式可以配合使用也可以各自单独使用。但是 cache-control 优先级比 expires 高。

反向代理缓存

缓存上游服务器的数据

配置

1
2
3
4
5
6
7
# http模块
# 缓存存储位置
proxy_cache_path /ngx_tmp levels=1:2 keys_zone=test_cache:100m inactive=1d max_size=10g;
# location模块
add_header Nginx-Cache "$upstream_cache_status"; # 可在请求头查看是否命中缓存
proxy_cache test_cache; # 对应上面的keys_zone
proxy_cache_valid 1h; # 过期时间

其它配置查看官网

http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache

缓存清理

便于管理缓存,当然也可以手动去目录删除。

第三方模块:

https://github.com/FRiCKLE/ngx_cache_purge

配置

1
2
3
4
5
6
7
8
9
10
# 自定义cachekey,在配置了缓存的location中
proxy_cache_key $uri;

# 新增location
location ~ /purge(/.*) {
proxy_cache_purge test_cache $1; # 通过访问的uri删除对应key的缓存
}

# 之后访问 服务器ip/purge+访问uri 即可删除对应目录下的缓存
# 例如 120.48.54.126/purge/ 表示删除首页的缓存

断点续传

指的是在上传/下载时,将任务(一个文件或压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传/下载,如果碰到网络故障,可以从已经上传/下载的部分开始继续上传/下载未完成的部分,而没有必要从头开始上传/下载。可以节省时间,提高速度。

测试

客户端配置Range请求头请求,nginx默认支持:

image-20220901154633461

返回的头信息会告诉我们总共有多少字节:

image-20220901154847364

如下访问就能将其余数据都拿到:

image-20220901155317182

配置

上面只是基于nginx本地的断点续传,在反向代理服务器中向后传递header,当然前提是上游服务器支持range,具体配置现用现学。

1
proxy_set_header Range $http_range;

Nginx内存缓存

一般应用为静态文件元数据信息缓存,sendfile 执行过程:

1
2
3
4
5
6
7
8
9
epoll_wait(8, [{EPOLLIN, {u32=1904243152, u64=140709327827408}}, {EPOLLIN, {u32=1904242704, u64=140709327826960}}], 512, 25215) = 2
recvfrom(10, "GET / HTTP/1.1\r\nHost: 192.168.44"..., 1024, 0, NULL, NULL) = 475
stat("/usr/local/nginx//html/index.html", {st_mode=S_IFREG|0644, st_size=1429, ...}) = 0
open("/usr/local/nginx//html/index.html", O_RDONLY|O_NONBLOCK) = 11
fstat(11, {st_mode=S_IFREG|0644, st_size=1429, ...}) = 0
writev(10, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=263}], 1) = 263
sendfile(10, 11, [0] => [1429], 1429) = 1429
write(4, "192.168.44.1 - - [27/May/2022:14"..., 193) = 193
close(11)

open_file_cache

1
2
3
4
open_file_cache max=500 inactive=60s
open_file_cache_min_uses 1;
open_file_cache_valid 60s;
open_file_cache_errors on

max

缓存最大数量,超过数量后会使用LRU淘汰

inactive

指定时间内未被访问过的缓存将被删除

pen_file_cache_min_uses

被访问到多少次后会开始缓存

open_file_cache_valid

间隔多长时间去检查文件是否有变化

open_file_cache_errors

对错误信息是否缓存

外置内存缓存

nginx + memcached

nginx + redis

其它缓存

CDN缓存、正向代理缓存、Nginx内存缓存、外置内存缓存、上游服务器应用缓存等

GEOIP

根据用户ip定位地理位置。

可根据地理位置让这些ip访问指定站点,例如不同国家用户可分配到不同站点以显示不同风格的页面。

  1. 下载ip数据库

    maxmind.com

  2. 安装依赖

    https://github.com/maxmind/libmaxminddb

    下载解压后进入目录执行编译安装

    1
    2
    3
    4
    5
    ./configure
    make
    make install
    echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf
    ldconfig
  3. nginx下geoip模块安装

    https://github.com/leev/ngx_http_geoip2_module

    解压并添加模块到nginx,略。

  4. nginx配置

    1
    2
    3
    4
    5
    6
    # 作用域http
    geoip2 /root/GeoLite2-ASN_20220524/GeoLite2-ASN.mmdb {
    $geoip2_country_code country iso_code;
    }
    # 作用域location
    add_header country $geoip2_country_code;
  5. 之后再请求头获取地理位置

正向代理

正向代理,指的是通过代理服务器 代理浏览器/客户端去重定向请求访问到目标服务器 的一种代理服务。
正向代理服务的特点是代理服务器 代理的对象是浏览器/客户端,也就是对于目标服务器 来说浏览器/客户端是隐藏的。

https://github.com/chobits/ngx_http_proxy_connect_module

使用nginx代理客户/浏览器访问客户需要请求的地址。客户端网络同时也需要配置。正常不使用nginx做正向代理。

error_page

1
2
3
4
5
# 发生如下错误时,由location=/50x.html处理
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

个性化

1
2
3
4
5
# 当发生404错误时,跳转到其它地址并返回302状态
error_page 404 =302 https://www.baidu.com;

# 也可以跳转到其它location
error_page 404 =200 /success.html;
注意=与302之间不能有空格。

匿名location与return

Location

正常情况下 location 是可以被访问的,eg:

1
2
3
4
5
location = /50x.html {
root html;
}

# http://ip/50x.html 可以直接访问

匿名location表示不能被访问,内部才可以跳转进去

1
2
3
4
5
error_page 404 = @666;

location @666 {
return 866; # 放回状态码
}

Return

return 的第一种使用就是返回任意状态码,客户端会下载一个空文件。文件名为请求的uri。

状态码后面还可以跟内容,这时客户端同样会下载一个文件,内容为自定义内容。

1
return 200 "hello world!";

下载的原因是因为浏览器不知道如何处理,因为没有Content-Type

1
2
add_header content-type 'text/html';
return 200 "hello world!";

之后浏览器就不会下载,而是将内容显示在网页上。

Stream模块

从1.9.0开始,NGINX增加了stream模块用来实现四层协议的转发、代理和负载均衡

http://nginx.org/en/docs/stream/ngx_stream_core_module.html

QPS限制模块

基于漏桶算法实现

http://nginx.org/en/docs/http/ngx_http_limit_req_module.html

日志模块

可用于大数据

ngx_http_log_module

http://nginx.org/en/docs/http/ngx_http_log_module.html

ngx_http_empty_gif_module

http://nginx.org/en/docs/http/ngx_http_empty_gif_module.html

errorlog

http://nginx.org/en/docs/ngx_core_module.html#error_log

日志分割

两种方式:

1.脚本

2.Logrotate

主动健康检查

主动的去检查上游服务器健康状态

tengine版

https://github.com/yaoweibin/nginx_upstream_check_module

nginx商业版

http://nginx.org/en/docs/http/ngx_http_upstream_hc_module.html

Lua

Lua 是由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于1993年开发的一种轻量、小巧的脚本语言,用标准 C 语言编写,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

官网:http://www.lua.org/

IDE插件:EmmyLua

基础语法:

hello world

1
print("hello world!")

保留关键字

nd break do else elseif end false for function if in local nil not or repeat return then true until while

注释

1
2
3
4
5
6
7
8
9
-- 两个减号是行注释

--[[

这是块注释

这是块注释.

]]

变量

数字类型

Lua的数字只有double型,64bits

1
2
3
4
5
6
7
8
9
10
11
12
13
num = 1024

num = 3.0

num = 3.1416

num = 314.16e-2

num = 0.31416E1

num = 0xff

num = 0x56

字符串

可以用单引号,也可以用双引号

也可以使用转义字符 ‘\n’(换行),’\r’(回车),’\t’(横向制表),’\v’(纵向制表),’\\‘ (反斜杠),’\“‘ (双引号),以及 ‘\‘’(单引号)等等

下面的四种方式定义了完全相同的字符串(其中的两个中括号可以用于定义有换行的字符串)

1
2
3
4
5
6
7
8
9
a = 'alo\n123"'

a = "alo\n123\""

a = '\97lo\10\04923"'

a = [[alo

123"]]

空值

nil,比如你访问一个没有声明过的变量,就是nil

布尔类型

只有nil和false是 false

数字0,空字符串(’\0’)都是 true

作用域

lua中的变量如果没有特殊说明,全是全局变量,那怕是在语句块或函数里。

变量前加 local 关键字的是局部变量。

控制语句

while循环

1
2
3
4
5
6
7
8
9
10
11
local i = 0

local max = 10

while i <= max do

print(i)

i = i + 1

end

if-else

1
2
3
4
5
6
7
8
9
10
11
12
13
local age = 140

local sex = 'Male'

if age == 40 and sex =="Male" then
print(" 男人四十一枝花 ")
elseif age > 60 and sex ~="Female" then
print("old man!!")
elseif age < 20 then
io.write("too young, too simple!\n")
else
print("Your age is "..age)
end

for循环

1
2
3
4
5
6
7
sum = 0

for i = 100, 1, -2 do

sum = sum + i

end

函数

1
2
3
4
5
6
7
8
9
function myPower(x,y)

return y+x

end

power2 = myPower(2,3)

print(power2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function newCounter()
local i = 0
return function()
i = i + 1
return i
end
end

c1 = newCounter()

print(c1()) --> 1

print(c1()) --> 2

print(c1()) --> 3

返回值

1
2
3
name,age,mail = "yiming", 37, false, "yimingl@hotmail.com"

print(name,age,mail)
1
2
3
4
5
6
7
function isMyGirl(name)
return name == 'xiao6' , name
end

local bol,name = isMyGirl('xiao6')

print(name,bol)

Table

key,value的键值对,类似 map

1
2
3
4
5
6
7
dog = {name='111',age=18,height=165.5}

dog.age=35

print(dog.name,dog.age,dog.height)

print(dog)

数组

下标从1开始

1
2
3
arr = {"string", 100, "dog",function() print("wangwang!") return 1 end}

print(arr[4]())

遍历

1
2
3
4
5
arr = {"string", 100, "dog",function() print("wangwang!") return 1 end}

for k, v in pairs(arr) do
print(k, v)
end

成员函数

1
2
3
4
function  person.eat(food)
print(person.name .." eating "..food)
end
person.eat("骨头")

Openresty

安装

  1. 下载

    http://openresty.org/cn/download.html

  2. 解压并编译

    1
    2
    3
    4
    tar -xzvf openresty-1.21.4.1.tar.gz -C /opt/module
    ./configure --prefix=/usr/local/openresty
    make
    make install

    注意依赖于 gcc openssl-devel pcre-devel zlib-devel

  3. 修改openresty下的nginx.conf配置文件避免与nginx冲突

    1
    2
    3
    vim /usr/local/openresty/nginx/conf/nginx.conf

    listen 888; # 任意
  4. 启动

    1
    2
    cd /usr/local/openresty/nginx/sbin
    ./nginx -c /usr/local/openresty/nginx/conf/nginx.conf
  5. 访问

    1
    Ip:888

    image-20220901210039192

  6. 停止

    1
    ./nginx -s stop

测试Lua脚本

1
2
3
4
5
6
7
8
# 在 openresty 的 nginx.conf 中写入
location /lua {
default_type text/html; # 返回类型
# ''里为 lua 脚本
content_by_lua '
ngx.say("<p>Hello, World!</p>")
';
}

重启openresty并访问:

image-20220901210824505

location引入lua脚本

lua脚本全都写在配置文件中也不好。

1
2
3
4
location /lua {
default_type text/html;
content_by_lua_file /lua/hello.lua; # 相对路径或绝对路径
}

热部署

每次修改lua脚本无需手动重启,注意损耗性能,开发时使用就好

1
2
# http 域下
lua_code_cache off;

获取系统变量及参数

获取Nginx uri中的单一变量

1
2
3
4
5
6
7
8
9
10
location /nginx_var {

default_type text/html;

content_by_lua_block {

ngx.say(ngx.var.arg_a)

}
}

获取Nginx uri中的所有变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
local uri_args = ngx.req.get_uri_args()  

for k, v in pairs(uri_args) do

if type(v) == "table" then

ngx.say(k, " : ", table.concat(v, ", "), "<br/>")

else

ngx.say(k, ": ", v, "<br/>")

end
end

在处理http请求时还可以使用

  • set_by_lua

    修改nginx变量

  • rewrite_by_lua

    修改uri

  • access_by_lua

    访问控制

  • header_filter_by_lua

    修改响应头

  • boy_filter_by_lua

    修改响应体

  • log_by_lua

    日志

获取Nginx请求头信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local headers = ngx.req.get_headers()                         

ngx.say("Host : ", headers["Host"], "<br/>")

ngx.say("user-agent : ", headers["user-agent"], "<br/>")

ngx.say("user-agent : ", headers.user_agent, "<br/>")

for k,v in pairs(headers) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ","), "<br/>")
else
ngx.say(k, " : ", v, "<br/>")
end

end

获取post请求参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ngx.req.read_body()  

ngx.say("post args begin", "<br/>")

local post_args = ngx.req.get_post_args()

for k, v in pairs(post_args) do

if type(v) == "table" then

ngx.say(k, " : ", table.concat(v, ", "), "<br/>")

else

ngx.say(k, ": ", v, "<br/>")

end
end

http协议版本

1
ngx.say("ngx.req.http_version : ", ngx.req.http_version(), "<br/>")

请求方法

1
ngx.say("ngx.req.get_method : ", ngx.req.get_method(), "<br/>")  

原始的请求头内容

1
ngx.say("ngx.req.raw_header : ",  ngx.req.raw_header(), "<br/>")  

body内容体

1
ngx.say("ngx.req.get_body_data() : ", ngx.req.get_body_data(), "<br/>")

Nginx 缓存

Nginx全局内存缓存

shared_dict

1
2
# http 域下,申请1m空间用于缓存
lua_shared_dict shared_data 1m;
1
2
3
4
5
6
7
8
9
10
local shared_data = ngx.shared.shared_data  
local i = shared_data:get("i")

if not i then
i = 1
shared_data:set("i", i)
ngx.say("lazy set i ", i, "<br/>")
end
i = shared_data:incr("i", 1)
ngx.say("i=", i, "<br/>")

lua-resty-lrucache

Lua 实现的一个简单的 LRU 缓存,适合在 Lua 空间里直接缓存较为复杂的 Lua 数据结构:它相比 ngx_lua 共享内存字典(就是上面的)可以省去较昂贵的序列化操作,相比 memcached 这样的外部服务又能省去较昂贵的 socket 操作

https://github.com/openresty/lua-resty-lrucache

连接Redis

lua-resty-redis

https://github.com/openresty/lua-resty-redis

常用方法

1
2
3
4
5
local res, err = red:get("key")

local res, err = red:lrange("nokey", 0, 1)

ngx.say("res:",cjson.encode(res))

创建连接

1
2
3
red, err = redis:new()

ok, err = red:connect(host, port, options_table?)

timeout

1
red:set_timeout(time)

keepalive

1
red:set_keepalive(max_idle_timeout, pool_size)

close

1
ok, err = red:close()

pipeline

1
2
3
red:init_pipeline()

results, err = red:commit_pipeline()

认证

1
2
3
4
5
6
7
8
local res, err = red:auth("foobared")

if not res then

ngx.say("failed to authenticate: ", err)

return
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
local redis = require "resty.redis"
local red = redis:new()

red:set_timeouts(1000, 1000, 1000) -- 1 sec

local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("failed to connect: ", err)
return
end

ok, err = red:set("dog", "an animal")
if not ok then
ngx.say("failed to set dog: ", err)
return
end

ngx.say("set result: ", ok)

local res, err = red:get("dog")
if not res then
ngx.say("failed to get dog: ", err)
return
end

if res == ngx.null then
ngx.say("dog not found.")
return
end


ngx.say("dog: ", res)

redis2-nginx-module

redis2-nginx-module是一个支持 Redis 2.0 协议的 Nginx upstream 模块,它可以让 Nginx 以非阻塞方式直接防问远方的 Redis 服务,同时支持 TCP 协议和 Unix Domain Socket 模式,并且可以启用强大的 Redis 连接池功能。

test

1
2
3
4
5
6
7
8
9
10
11
12
13
location = /foo {

default_type text/html;

redis2_query auth 123123;

set $value 'first';

redis2_query set one $value;

redis2_pass 192.168.199.161:6379;

}

get

1
2
3
4
5
6
7
8
location = /get {

default_type text/html;
redis2_pass 192.168.199.161:6379;
redis2_query auth 123123;
set_unescape_uri $key $arg_key; # this requires ngx_set_misc
redis2_query get $key;
}

set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# GET /set?key=one&val=first%20value

location = /set {

default_type text/html;

redis2_pass 192.168.199.161:6379;

redis2_query auth 123123;


set_unescape_uri $key $arg_key; # this requires ngx_set_misc

set_unescape_uri $val $arg_val; # this requires ngx_set_misc

redis2_query set $key $val;

}

pipeline

1
2
3
4
5
6
7
8
9
10
11
set $value 'first';

redis2_query set one $value;

redis2_query get one;

redis2_query set one two;

redis2_query get one;

redis2_query del key1;

list

1
2
3
4
5
6
7
redis2_query lpush key1 C;

redis2_query lpush key1 B;

redis2_query lpush key1 A;

redis2_query lrange key1 0 -1;

集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
upstream redis_cluster {

server 192.168.199.161:6379;

server 192.168.199.161:6379;

}

location = /redis {

default_type text/html;

redis2_next_upstream error timeout invalid_response;

redis2_query get foo;

redis2_pass redis_cluster;
}

连接Mysql

lua-resty-mysql

https://github.com/openresty/lua-resty-mysql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
local mysql = require "resty.mysql"
local db, err = mysql:new()
if not db then
ngx.say("failed to instantiate mysql: ", err)
return
end

db:set_timeout(1000) -- 1 sec


local ok, err, errcode, sqlstate = db:connect{
host = "192.168.44.211",
port = 3306,
database = "zhangmen",
user = "root",
password = "111111",
charset = "utf8",
max_packet_size = 1024 * 1024,
}


ngx.say("connected to mysql.<br>")



local res, err, errcode, sqlstate = db:query("drop table if exists cats")
if not res then
ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
return
end


res, err, errcode, sqlstate =
db:query("create table cats "
.. "(id serial primary key, "
.. "name varchar(5))")
if not res then
ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
return
end

ngx.say("table cats created.")



res, err, errcode, sqlstate =
db:query("select * from t_emp")
if not res then
ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
return
end

local cjson = require "cjson"
ngx.say("result: ", cjson.encode(res))


local ok, err = db:set_keepalive(10000, 100)
if not ok then
ngx.say("failed to set keepalive: ", err)
return
end

模版引擎

lua-resty-template

https://github.com/bungle/lua-resty-template

lua-resty-template模板引擎其最终会被翻译成Lua代码,然后通过ngx.print输出。

lua-resty-template大体内容:

l 模板位置:从哪里查找模板;

l 变量输出/转义:变量值输出;

l 代码片段:执行代码片段,完成如if/else、for等复杂逻辑,调用对象函数/方法;

l 注释:解释代码片段含义;

l include:包含另一个模板片段;

l 其他:lua-resty-template还提供了不需要解析片段、简单布局、可复用的代码块、宏指令等支持。

基础语法

l {(include_file)}:包含另一个模板文件;

l { var }:变量输出;

l :变量转义输出;

l {% code %}:代码片段;

l :注释;

l {-raw-}:中间的内容不会解析,作为纯文本输出;

测试

html

1
2
3
4
5
6
<!DOCTYPE html>
<html>
<body>
<h1>{{message}}</h1>
</body>
</html>

模板文件存放位置

nginx.conf中配置

1
set $template_root /usr/local/openresty/nginx/tmp;

执行函数,得到渲染之后的内容

1
2
3
4
local template = require "resty.template"
local view = template.new "view.html"
view.message = "Hello, World!"
view:render()

...

redis+mysql+模版引擎

实战来

Lua开源项目

WAF

https://github.com/unixhot/waf

https://github.com/loveshell/ngx_lua_waf

l 防止 SQL 注入,本地包含,部分溢出,fuzzing 测试,XSS/SSRF 等 Web 攻击

l 防止 Apache Bench 之类压力测试工具的攻击

l 屏蔽常见的扫描黑客工具,扫描器

l 屏蔽图片附件类目录执行权限、防止 webshell 上传

l 支持 IP 白名单和黑名单功能,直接将黑名单的 IP 访问拒绝

l 支持 URL 白名单,将不需要过滤的 URL 进行定义

l 支持 User-Agent 的过滤、支持 CC 攻击防护、限制单个 URL 指定时间的访问次数

l 支持支持 Cookie 过滤,URL 与 URL 参数过滤

l 支持日志记录,将所有拒绝的操作,记录到日志中去

Kong 基于Openresty的流量网关

https://konghq.com/

https://github.com/kong/kong

Kong 基于 OpenResty,是一个云原生、快速、可扩展、分布式的微服务抽象层(Microservice Abstraction Layer),也叫 API 网关(API Gateway),在 Service Mesh 里也叫 API 中间件(API Middleware)。

Kong 开源于 2015 年,核心价值在于高性能和扩展性。从全球 5000 强的组织统计数据来看,Kong 是现在依然在维护的,在生产环境使用最广泛的 API 网关。

Kong 宣称自己是世界上最流行的开源微服务 API 网关(The World’s Most Popular Open Source Microservice API Gateway)。

核心优势:

l 可扩展:可以方便的通过添加节点水平扩展,这意味着可以在很低的延迟下支持很大的系统负载。

l 模块化:可以通过添加新的插件来扩展 Kong 的能力,这些插件可以通过 RESTful Admin API 来安装和配置。

l 在任何基础架构上运行:Kong 可以在任何地方都能运行,比如在云或混合环境中部署 Kong,单个或全球的数据中心。

APISIX

ABTestingGateway

https://github.com/CNSRE/ABTestingGateway

ABTestingGateway 是一个可以动态设置分流策略的网关,关注与灰度发布相关领域,基于 Nginx 和 ngx-lua 开发,使用 Redis 作为分流策略数据库,可以实现动态调度功能。

ABTestingGateway 是新浪微博内部的动态路由系统 dygateway 的一部分,目前已经开源。在以往的基于 Nginx 实现的灰度系统中,分流逻辑往往通过 rewrite 阶段的 if 和 rewrite 指令等实现,优点是性能较高,缺点是功能受限、容易出错,以及转发规则固定,只能静态分流。ABTestingGateway 则采用 ngx-lua,通过启用 lua-shared-dict 和 lua-resty-lock 作为系统缓存和缓存锁,系统获得了较为接近原生 Nginx 转发的性能。

l 支持多种分流方式,目前包括 iprange、uidrange、uid 尾数和指定uid分流

l 支持多级分流,动态设置分流策略,即时生效,无需重启

l 可扩展性,提供了开发框架,开发者可以灵活添加新的分流方式,实现二次开发

l 高性能,压测数据接近原生 Nginx 转发

l 灰度系统配置写在 Nginx 配置文件中,方便管理员配置

l 适用于多种场景:灰度发布、AB 测试和负载均衡等