1. Nginx反向代理概述

  关于反向代理、正向代理的概念,在《Nginx从入门到入坟(一)- Nginx入门篇》中已经说的很清楚了,简而言之就是正向代理代理的对象是客户端,反向代理代理的是服务端,这是两者之间最大的区别。
  下面先通过一个简单案例实现 Nginx 正向代理的应用。

2. 正向代理案例

  需求:


  实现:

  1. 服务端的配置:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    http {
    log_format main 'client send request=>clientIp=$remote_addr serverIp=>$host';
    server{
    listen 80;
    server_name localhost;
    access_log logs/access.log main;
    location {
    root html;
    index index.html index.htm;
    }
    }
    }
  2. 使用客户端访问服务端,打开日志查看结果:
  3. 代理服务器设置:
    1
    2
    3
    4
    5
    6
    7
    8
    server {

    listen 82;
    resolver 8.8.8.8;
    location /{
    proxy_pass http://$host$request_uri;
    }
    }
  4. 查看代理服务器的 IP 和 Nginx 配置监听的端口;
  5. 在客户端配置代理服务器:
  6. 设置完成后,再次通过浏览器访问服务端:

  通过对比,上下两次的日志记录,会发现虽然我们是客户端访问服务端,但是如何使用了代理,那么服务端能看到的只是代理发送过去的请求,这样的话,就使用 Nginx 实现了正向代理的设置。
  但是 Nginx 正向代理,在实际的应用中不是特别多,所以我们简单了解下,接下来我们继续说 Nginx 的反向代理,这是 Nginx 比较重要的一个功能。

3. Nginx反向代理的配置语法

  Nginx 反向代理模块的指令是由ngx_http_proxy_module模块进行解析,该模块在安装 Nginx 的时候已经自己加装到 Nginx 中了,接下来我们把反向代理中的常用指令一一介绍下:

1
2
3
proxy_pass
proxy_set_header
proxy_redirect

3.1 proxy_pass

  该指令用来设置被代理服务器地址,可以是主机名称、IP 地址加端口号形式。

语法 proxy_pass URL
默认值 -
位置 location

  URL:为要设置的被代理服务器地址,包含传输协议(http , https:// )、主机名称或 IP 地址加端口号、URI 等要素。
  举例:

1
2
3
4
5
6
proxy_pass http://www.baidu.com;
location /server{}
proxy_pass http://192.168.200.146;
http://192.168.200.146/server/index.html
proxy_pass http://192.168.200.146/;
http://192.168.200.146/index.html

  那么现在有一个问题,在编写 proxy_pass 的时候,后面的值要不要加/
  下面通过一个例子来说明这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
listen 80;
server_name localhost;
location /{
#proxy_pass http://192.168.200.146;
proxy_pass http://192.168.200.146/;
}
}
当客户端访问 http://localhost/index.html,效果是一样的
server{
listen 80;
server_name localhost;
location /server{
#proxy_pass http://192.168.200.146;
proxy_pass http://192.168.200.146/;
}
}
当客户端访问 http://localhost/server/index.html
这个时候,第一个proxy_pass就变成了http://localhost/server/index.html
第二个proxy_pass就变成了http://localhost/index.html效果就不一样了

3.2 proxy_set_header

  该指令可以更改 Nginx 服务器接收到的客户端请求的请求头信息,然后将新的请求头发送给代理的服务器。

语法 proxy_set_header field value
默认值 proxy_set_header Host $proxy_host
proxy_set_header Connection close
位置 http、server、location

  需要注意的是,如果想要看到结果,必须在被代理的服务器上来获取添加的头信息。
  被代理服务器:192.168.200.146

1
2
3
4
5
6
server {
listen 8080;
server_name localhost;
default_type text/plain;
return 200 $http_username;
}

  代理服务器:192.168.200.133

1
2
3
4
5
6
7
8
server {
listen 8080;
server_name localhost;
location /server {
proxy_pass http://192.168.200.146:8080/;
proxy_set_header username TOM;
}
}

  访问测试正常。

3.3 proxy_redirect

  该指令用来修改被代理服务器返回的响应头中的Location头域和refresh头域。

语法 proxy_redirect redirect replacement
proxy_redirect default
proxy_redirect off
默认值 proxy_redirect default
位置 http、server、location

  语法结构:

1
2
3
proxy_redirect 旧地址 新地址;
proxy_redirect default; #默认配置
proxy_redirect off; #关闭重定向

  案例:

  1. 假设被代理服务器返回 Location 字段为:http://localhost:8000/two/some/uri/
    1
    2
    proxy_redirect http://localhost:8000/two/ http://frontend/one/;
    将 Location 字段重写为http://frontend/one/some/uri/
  2. .参数off将在这个字段中禁止所有的proxy_redirect指令:
    1
    proxy_redirect off;

  注意,proxy_redirect default 必须在 proxy_pass 下方配置。

4. 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
34
35
36
37
代理服务器
server {
listen 8082;
server_name localhost;
location /server1 {
proxy_pass http://192.168.200.146:9001/;
}
location /server2 {
proxy_pass http://192.168.200.146:9002/;
}
location /server3 {
proxy_pass http://192.168.200.146:9003/;
}
}

服务端:
server1:
server {
listen 9001;
server_name localhost;
default_type text/html;
return 200 '<h1>192.168.200.146:9001</h1>'
}
server2:
server {
listen 9002;
server_name localhost;
default_type text/html;
return 200 '<h1>192.168.200.146:9002</h1>'
}
server3:
server {
listen 9003;
server_name localhost;
default_type text/html;
return 200 '<h1>192.168.200.146:9003</h1>'
}

  那么第二种三台服务器的内容一样的情况,后面说到负载均衡再说这种情况怎么处理。

5. 反向代理调优

  反向代理值 Buffer(缓冲)和 Cache(缓存)。

  • 相同点:
    • 两种方式都是用来提供 IO 吞吐效率,都是用来提升 Nginx 代理的性能;
  • 不同点:
    • 缓冲主要用来解决不同设备之间数据传递速度不一致导致的性能低的问题,缓冲中的数据一旦此次操作完成后,就可以删除;
    • 缓存主要是备份,将被代理服务器的数据缓存一份到代理服务器,这样的话,客户端再次获取相同数据的时候,就只需要从代理服务器上获取,效率较高,缓存中的数据可以重复使用,只有满足特定条件才会删除。

5.1 缓冲控制

proxy_buffering: 该指令用来开启或者关闭代理服务器的缓冲区,如果关闭缓冲,那么当 Nginx 一收到后端的反馈就同时传给客户端。

语法 proxy_buffering on|off
默认值 proxy_buffering on;
位置 http、server、location

  这个参数用来控制是否打开后端响应内容的缓冲区,如果这个设置为off,那么proxy_buffersproxy_busy_buffers_size这两个指令将会失效。 但是无论proxy_buffering是否开启,对proxy_buffer_size都是生效的。
  proxy_buffering开启的情况下,Nignx 会把后端返回的内容先放到缓冲区当中,然后再返回给客户端(边收边传,不是全部接收完再传给客户端)。 临时文件由proxy_max_temp_file_sizeproxy_temp_file_write_size这两个指令决定的。如果响应内容无法放在内存里边,那么部分内容会被写到磁盘上。
  如果proxy_buffering关闭,那么 Nginx 会立即把从后端收到的响应内容传送给客户端,每次取的大小为proxy_buffer_size的大小,这样效率肯定会比较低。
  Nginx不会去尝试计算被代理服务器整个响应内容的大小,Nginx 能从服务器接受的最大数据,是由指令proxy_buffer_size指定的。

  1. proxy_buffering启用时,要提防使用的代理缓冲区太大。这可能会吃掉你的内存,限制代理能够支持的最大并发连接数。
  2. 对于基于长轮询(long-polling)的 Comet 应用来说,关闭proxy_buffering是重要的,不然异步响应将被缓存导致 Comet 无法工作。

proxy_buffers: 该指令用来指定单个连接从代理服务器读取响应的缓存区的个数和大小。

语法 proxy_buffers number size;
默认值 proxy_buffers 8 4k|8K(与系统平台有关)
位置 http、server、location
  • number: 缓冲区的个数;
  • size: 每个缓冲区的大小,缓冲区的总大小就是number*size

  Nginx 从被代理的后端服务器取得的响应内容,放置到缓冲区,默认情况下,一个缓冲区的大小等于内存页面大小,可能是 4K 也可能是 8K,这取决于平台。
  若某些请求的响应过大,则超过proxy_buffers的部分将被缓冲到硬盘(缓冲目录由proxy_temp_path指令指定),当然这将会使读取响应的速度减慢,影响用户体验。可以使用proxy_max_temp_file_size指令关闭磁盘缓冲。

proxy_buffer_size: 该指令用来设置从被代理服务器获取的第一部分响应数据的大小。保持与 proxy_buffers 中的 size 一致即可,当然也可以更小。

语法 proxy_buffer_size size;
默认值 proxy_buffer_size 4k|8k(与系统平台有关)
位置 http、server、location

proxy_busy_buffers_size: 该指令用来限制同时处于 BUSY 状态的缓冲总大小。

语法 proxy_busy_buffers_size size;
默认值 proxy_busy_buffers_size 8k|16K
位置 http、server、location

  proxy_busy_buffers_size不是独立的空间,他是proxy_buffersproxy_buffer_size的一部分。Nginx 会在没有完全读完后端响应的时候就开始向客户端传送数据,所以它会划出一部分缓冲区来专门向客户端传送数据(这部分的大小是由proxy_busy_buffers_size来控制的,建议为proxy_buffers中单个缓冲区大小的 2 倍),然后它继续从后端取数据,缓冲区满了之后就写到磁盘的临时文件中。

proxy_max_temp_file_size | proxy_temp_file_write_size

语法 proxy_max_temp_file_size size;
默认值 proxy_max_temp_file_size 1024m;
位置 http、server、location
语法 proxy_temp_file_write_size size;
默认值 proxy_temp_file_write_size 8k|16k;
位置 http、server、location

  临时文件由proxy_max_temp_file_sizeproxy_temp_file_write_size这两个指令决定。 proxy_temp_file_write_size是一次访问能写入的临时文件的大小,默认是proxy_buffer_sizeproxy_buffers中设置的缓冲区大小的 2 倍,Linux下一般是 8k。
  proxy_max_temp_file_size指定当响应内容大于proxy_buffers指定的缓冲区时,写入硬盘的临时文件的大小。如果超过了这个值,Nginx 将与代理服务器同步的传递内容,而不再缓冲到硬盘。设置为 0 时, 则直接关闭硬盘缓冲。

proxy_temp_path: 当缓冲区存满后,仍未被 Nginx 服务器完全接受,响应数据就会被临时存放在磁盘文件上,该指令设置文件路径。

语法 proxy_temp_path path;
默认值 proxy_temp_path proxy_temp;
位置 http、server、location

  注意 path 最多设置三层。

5.1.1 总结

  所有的 proxy buffer 参数是作用到每一个请求的。每一个请求会安按照参数的配置获得自己的 buffer。proxy buffer 不是 global 而是 per request 的。
  proxy_buffering是为了开启 response buffering of the proxied server,开启后proxy_buffersproxy_busy_buffers_size参数才会起作用。
  无论proxy_buffering是否开启,proxy_buffer_size(main buffer)都是工作的,proxy_buffer_size所设置的buffer_size的作用是用来存储 upstream 端 response 的 header。
  在proxy_buffering开启的情况下,Nginx 将会尽可能的读取所有的 upstream 端传输的数据到 buffer,直到proxy_buffers设置的所有 buffer 们被写满或者数据被读取完(EOF)。此时 Nginx 开始向客户端传输数据,会同时传输这一整串 buffer 们。同时如果 response 的内容很大的话,Nginx 会接收并把他们写入到 temp_file 里去。大小由proxy_max_temp_file_size控制。如果 busy 的 buffer 传输完了会从 temp_file 里面接着读数据,直到传输完毕。
  一旦proxy_buffers设置的 buffer 被写入,直到 buffer 里面的数据被完整的传输完(传输到客户端),这个 buffer 将会一直处在 busy 状态,我们不能对这个 buffer 进行任何别的操作。所有处在 busy 状态的 buffer size 加起来不能超过proxy_busy_buffers_size,所以proxy_busy_buffers_size是用来控制同时传输到客户端的 buffer 数量的。

5.1.2 配置示例

  • 通用网站的配置:
1
2
3
4
proxy_buffer_size 4k; 			# 设置代理服务器(Nginx)保存用户头信息的缓冲区大小
proxy_buffers 4 32k; # proxy_buffers缓冲区,网页平均在32k以下的设置
proxy_busy_buffers_size 64k; # 高负荷下缓冲大小(proxy_buffers*2)
proxy_temp_file_write_size 64k; # 设置缓存文件大小,大于这个值,将从upstream服务器传
  • docker registry 的配置,这个每次传输至少都是 9M 以上的内容,缓冲区配置大;
1
2
3
4
5
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 1M;
proxy_busy_buffers_size 2M;
proxy_max_temp_file_size 0;

5.2 缓存和过期控制

  上面的配置是将所有请求都转发给后端应用。为避免静态请求给后端应用带来的过大负载,我们可以将 Nginx 配置为缓存那些不变的响应数据。这就意味着 Nginx 不会向后端转发那些请求。
  下面示例,将*.html*.gif等文件缓存 30 分钟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
http {
#
# The path we'll cache to.
#
proxy_cache_path /tmp/cache levels=1:2 keys_zone=cache:60m max_size=1G;
}
## send all traffic to the back-end
location / {
proxy_pass http://backend;
proxy_redirect off;
proxy_set_header X-Forwarded-For $remote_addr;
location ~* \.(html|css|jpg|gif|ico|js)$ {
proxy_cache cache;
proxy_cache_key $host$uri$is_args$args;
proxy_cache_valid 200 301 302 30m;
expires 30m;
proxy_pass http://backend;
}
}

  这里,我们将请求缓存到/tmp/cache,并定义了其大小限制为 1G。同时只允许缓存有效的返回数据,例如:

1
proxy_cache_valid  200 301 302 30m;

  所有响应信息的返回代码不是HTTP (200|301|302) OK的都不会被缓存。
  对于例如 workpress 的应用,需要处理 cookies 和缓存的过期时间,通过只缓存静态资源来避免其带来的问题。

参考文献

  【1】https://www.bilibili.com/video/BV1ov41187bq?p=85&vd_source=e66fcf471e44c7ac45f918fd1f1e7a77
  【2】https://blog.csdn.net/hknaruto/article/details/117289891
  【3】https://zhuanlan.zhihu.com/p/95852366
  【4】https://www.pianshen.com/article/2410401164/
  【5】https://blog.csdn.net/woshaguayi/article/details/117367087