写在前面

通过前面的学习,我们已经对Nginx有了一个初步的认识,那么接下来将通过实际的例子来学习Nginx如何实现反向代理。

反向代理

入门演示

在实际工作中,服务器最常用的功能就是反向代理。使用反向代理后,直接收到请求的服务器是代理服务器,然后代理服务器将请求转发给内部网络上真正进行处理的服务器,并将得到的结果返回给客户端。也就是说,反向代理隐藏了真实的服务器,为服务器收发请求,使真实服务器对客户端不可见。反向代理一般用在需要处理跨域请求的时候,现在基本上大型的网站都设置了反向代理,下面简单介绍一下如何实现反向代理。

(1)考虑到后续的效果演示,这里修改之前在nginx.conf主文件中的导入配置,之前只是导入了vhost目录下的某个名为envythink.com.conf的配置文件,而在实际工作中肯定需要导入多个,因此将其修改为如下所示的信息:

1
include vhost/*.conf;

(2)在/usr/local/nginx/conf/vhost目录下新建一个名为envy.com.conf的配置文件,其中的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
# /usr/local/nginx/conf/vhost/envy.com.conf

server {
listen 8081;

server_name envy.com;

location / {
proxy_pass http://www.baidu.com;
}
}

可以看到这里修改了监听的端口和服务名称,更重要的是location中采用了proxy_pass来将服务代理到百度首页,注意此处必须使用HTTP协议,而不能写成HTTPS协议。

(3)使用nginx -s relaod命令来重新加载Nginx,并在本地宿主机Windows系统内C:\Windows\System32\drivers\etc的文件,并在里面新增如下DNS映射:

1
192.168.73.100  envy.com

之后开发者访问envy.com:8081即可被代理到百度首页,这样就实现了一个最简单的代理。

在实际工作中,开发者可以将请求转发到本机另一台机器上,或者根据访问路径跳转到不同的端口服务中。

举个例子,我们前面监听了8081端口,接下来通过访问不同路径来跳转到不同的端口中,如:
(1)访问envy.com:8081/envy链接时,请求转发到envythink.com:8080/
(2)访问envy.com:8081/think链接时,请求转发到envy.com:8081/

那么此时就需要修改envy.com.conf的配置文件为下面的代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# /usr/local/nginx/conf/vhost/envy.com.conf

server {
listen 8081;

server_name envy.com;

location / {
proxy_pass http://www.baidu.com;
}

location /envy/ {
proxy_pass http://192.168.73.100:8080/;
}

location /think/ {
proxy_pass http://192.168.73.100:8081/;
}
}

请注意由于envythink.com和envy.com均被DNS解析到192.168.73.100这一IP地址上,因此就可以直接使用IP地址进行配置。

反向代理proxy_pass指令

反向代理是一个Web服务器,它接受客户端的连接请求,然后将请求转发到上游服务器(upstream server),并将从服务器得到的结果返回给连接的客户端。

代理服务器中最重要的指令是proxy_pass,该指令有一个参数,URL请求会被转换,带有URI部分的proxy_pass指令将会使用该URI替代request_uri部分。什么意思,举个例子,如下所示:

1
2
3
location /uri {
proxy_pass http://localhost:8080/newuri;
}

这里假设的是server_name和listen分别为localhost和8080,这样当用户请求的是http://localhost:8080/uri链接,那么就会被代理到http://localhost:8080/newuri,也就是请求传递到上游服务器时会将/uri替换为/newuri

但是请注意,这个规则存在两个例外的情况。

特殊情况1:如果在location中定义了一个正则表达式,那么URI部分就不会发生转换。

举个例子,下面例子中的/book会被代理转发到/think,进而访问到百度首页:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 8081;

server_name envy.com;

location / {
proxy_pass http://www.baidu.com;
}

location /think/ {
proxy_pass http://192.168.73.100:8081/;
}

location /book/ {
proxy_pass http://192.168.73.100:8081/think/;
}
}

但是当开发者采用正则表达式,将/book变为如下配置:

1
2
3
location ~ ^/book/ {
proxy_pass http://192.168.73.100:8081/think/;
}

那么URI中的/book将会被直接传递到上游服务器,而不会转换到/think


特殊情况2:如果在location中有rewrite规则改变了URI,那么Nginx将使用这个URI处理请求,不会发生转换。

举个例子,下面例子中的URI传递到上游服务器的将是/index.html?page=<match>,这个match来自于括号中捕获的参数,而不是预期的/index,这一点需要格外注意:

1
2
3
4
location / {
rewrite /(.*)$ /index.html?page=$1 break;
proxy_pass http://192.168.73.100:8081/index;
}

在rewrite指令中,break标记用于立即停止rewrite模块的所有指令。

代理模块常用指令

当然还有一些其他重要的代理常用指令,如下所示:

Proxy模块指令 说明
proxy_connect_timeout Nginx从接受请求到连接至上游服务器的最长等待时间
proxy_cookie_domain 替代从上游服务器来的Set-Cookie头中的domain属性;domain被替换为一个字符串、正则表达式或者是引用的变量
proxy_cookie_path 替代从上游服务器来的Set-Cookie头中的path属性;path被替换为一个字符串、正则表达式或者是引用的变量
proxy_headers_hash_bucket_size 指定头名字的最大值
proxy_headers_hash_max_size 指定从上游服务器接收到头的总大小
proxy_hide_header 指定不应该传递给客户端头的列表
proxy_http_version 指定用于同上游服务器通信的HTTP协议版本(keepalive连接就使用1.1)
proxy_ignore_client_abort 如果该指令设置为on,那么当客户端放弃连接后,Nginx将不会放弃同上游服务器的连接
proxy_ignore_headers 当处理来自于上游服务器的响应时,该指令设置哪些头可以被忽略
proxy_intercept_errors 如果启用该指令,那么Nginx会显示配置的error_page错误,而不是来自于上游服务器的直接响应
proxy_max_temp_file_size 在写入内存缓存区时,当响应与内存缓存区不匹配时,该指令给出溢出文件的最大值
proxy_pass 指定请求被传递到的上游服务器,格式为URL
proxy_pass_header 很明显该指令用于覆盖在proxy_hide_header指令中设置的头,允许这些头传递到客户端
proxy_pass_request_body 如果设置为off,那么该指令会阻止请求体发送到上游服务器
proxy_pass_request_headers 如果设置为off,那么该指令会阻止请求头发送到上游服务器
proxy_read_timeout 重写来自于上游服务器的Location和Refresh头,这对于某种应用程序框架非常有用
proxy_send_timeout 指定在连接关闭之前,向上游服务器两次写成功的操作完成所需的时间长度
proxy_set_body 发送到上游服务器的请求体可能会被该指令的设置值修改
proxy_set_header 重写发送到上游服务器头的内容,也可以通过将某种头的值设置为空字符,而不发送某种头的方法实现
proxy_temp_file_write_size 限制在同一时间内缓冲到一个临时文件的数据量,以使得Nginx不会过长地阻止单个请求
proxy_temp_path 设定临时文件的缓冲,用于缓冲从上游服务器来的文件,可以设定目录的层次

由于这些指令用的较多,因此比较好的做法就是将其保存在一个名为proxy.conf文件中,然后再将其导入到与proxy_pass指令相同的location中。

proxy.conf文件中常配置的内容如下所示:

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
# 由于在大多数情况下不会重写location的头,因此将proxy_redirect指令设置为off
proxy_redirect off;

# 设置Host头,这样上游服务器能够将请求映射到一个虚拟服务器,否则就使用用户输入的URL中的主机部分
proxy_set_header Host $host;

# X-Real-IP和X-Forwarded-For作用相似,都用于转发连接客户端IP地址到上游服务器得到信息
# $remote_addr变量在X-Real-IP头中使用,就是Nginx接受客户端请求的IP地址
proxy_set_header X-Real-IP $remote_addr;

# $proxy_add_x_forwarded_for变量在X-Forwarded-For头中使用
# $proxy_add_x_forwarded_for来源于客户端请求,跟随有$remote_addr变量
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# 与代理有关,但不是严格的代理模块指令
# 允许客户端请求的最大单文件字节数
# 请注意它设置通过Web窗口上传的文件大小,一般是要大于它在文件系统中的大小
client_max_body_size 10m;

# 缓存区代理缓存用户端请求的最大字节数
# cleint_body_buffer_size 128k;

# Nginx和后端服务器连接超时时间(代理连接超时)
proxy_connect_timeout 30;

# 后端服务器数据回传时间(代理发送超时)
proxy_send_timeout 15;

# 连接成功后,后端服务器响应时间(代理接收超时)
proxy_read_timeout 15;

# 只在FreeBSD系统内有效,且在该协议下传输数据之前指定套接字发送缓冲应该容纳的字节数
# proxy_send_lowat 12000;

# 设置代理服务器(Nginx)保存用户头信息的缓存区大小
proxy_buffer_size 4k;

# proxy_buffers缓冲区,网页平均在32K以下可以这样设置
proxy_buffers 4 32k;

# 高负荷下缓冲大小(proxy_buffers*2)
proxy_busy_buffers_size 64k;

# 控制worker进程阻塞后台数据的时间,注意值越大,意味着处理阻塞的时间就越长
proxy_temp_file_write_size 64k;

之后就可以修改之前的envy.com.conf配置文件,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# /usr/local/nginx/conf/vhost/envy.com.conf

server {
listen 8081;

server_name envy.com;

location / {
proxy_pass http://www.baidu.com;
}

location /envy/ {
proxy_pass http://192.168.73.100:8080/;
}

location /think/ {
proxy_pass http://192.168.73.100:8081/;
}

location /book/ {
include proxy.conf;
proxy_pass http://192.168.73.100:8081/think/;
}
}

当然了如果该proxy.conf文件中的某些值在某些location内不适用,那么开发者就可以通过在location内再次定义自己所需的参数,这样就能覆盖掉原来的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
location /think/ {
include proxy.conf;

client_max_body_size 10m;

proxy_connect_timeout 75;

proxy_send_timeout 90;

proxy_read_timeout 90;

proxy_pass http://192.168.73.100:8081/;
}

请注意这里的顺序非常重要,如果在配置文件或者include中,同一个指令存在多个参数值,那么将会使用最后定义的值。

upstream模块

由于upstream模块和proxy模块关系非常紧密,因此这里再次学习一下upstream模块。upstream模块会启用一个定义了一组上游服务器的新配置区段,组内的上游服务器可能设置了不同的权重(一般来说,权重越高的上游服务器会被Nginx传递更多的连接)或者是不同的类型(TCP与UNIX域),甚至可能出于对服务器维护的需要,将标记设置为down。这里面涉及到部分后续将要学习的负载均衡的知识,但是这里可以提前了解一下。

下表示upstream模块中常用的指令:

upstream模块指令 说明
ip_hash 该指令通过IP地址的哈希值来确保客户端均匀地连接所有服务器,键值基于C类地址
keepalive 该指令指定每个worker进程缓存到上游服务器的连接数。在使用HTTP连接时,proxy_http_version应该设置为1.1,并且将proxy_set_header设置为Connection “”
least_conn 该指令激活负载均衡算法,将请求发送到活跃连接数最少的那台服务器
server 该指令为upstream定义一个服务器地址(带有TCP端口号的域名,IP地址或者是UNIX域套接字)和可选参数。参数有5种:weight、max_fails、fail_timeout、backup和down。

关于server中定义的参数详细介绍如下:
(1)weight:该参数设置一个服务器的优先级优于其他服务器;
(2)max_fails:该参数设置在fail_timeout时间之内尝试对一个服务器连接的最大次数,如果超出这个次数,那么就会被标记为down;
(3)fail_timeout:在这个指定的时间内,服务器必须提供响应,如果没有收到响应,那么该服务器就会被标记为down状态;
(4)backup:一旦其他服务器宕机,那么仅有该参数标记的机器才会接收请求;
(5)down:该参数标记一个服务器不再接受任何请求。

保持活动连接

upstream模块有一个比较重要的keepalive指令,该指令指定每个worker进程缓存到上游服务器的连接数。在Nginx需要同上游服务器保持一定数量的打开连接时,连接缓存非常有用。如果上游服务器需要通过HTTP协议进行通信,那么Nginx将会使用HTTP/1.1协议的持久连接机制来维护这些打开的连接。

举个例子,如下所示的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream apache {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name envythink.com;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
# 这里设置一个代理,注意名称必须和upstream保持一致
proxy_pass http://apache.com;
}
}

在上面的配置中,我们指定了Nginx要同运行在本机8080端口的Apache服务保持32个打开的连接。起初Nginx仅需要为每一个worker打开32个TCP握手连接,然后通过不发送close的Connection头来保持这些连接的打开。同时使用proxy_http_version来指定使用HTTP/1.1协议来同上游服务器进行通信,当然开发者也可以使用proxy_set_header指令来清除Connection头的内容,这样就没有直接代理客户端的连接属性。

需要注意的是,当需要的连接数多于32个,那么Nginx就会打开它们以满足需要;但是此后如果高峰已过,那么Nginx将会关闭最近最少使用的连接,使得这个连接数回落到32,也就是keepalive指定的数。

当然上述机制也能够被用在代理非HTTP连接中,举个例子,下面的配置就显示了Nginx与两个memcached实例保持64个连接:

1
2
3
4
5
upstream memcaches {
server 192.168.73.100:8080;
server 192.168.73.101:8080;
keepalive 64;
}

如果从默认的轮询负载均衡算法(round-robin)切换为ip_hash或者least_conn,那么开发者需要在使用到keepalive指令之前指定负载均衡算法:

1
2
3
4
5
6
upstream memcaches {
least_conn;
server 192.168.73.100:8080;
server 192.168.73.101:8080;
keepalive 64;
}

上游服务器的类型

上游服务器是Nginx代理连接的一个服务器,它可以是不同的物理机器,也可以是虚拟机等。上游服务器可以是一个在本地机器上监听UNIX域套接字的机器,也可以是TCP监听的众多不同机器中的其中一员。上游服务器分为单个上游服务器、多个上游服务器和非HTTP型上游服务器。

单个上游服务器

这是最基本的代理配置,举个例子,如下所示:

1
2
3
4
5
server {
location / {
proxy_pass http://localhost:8080;
}
}

Nginx就会终止所有的客户端连接,然后将代理所有请求到本地主机的TCP协议的8080端口上。

多个上游服务器

在实际工作中可能也需要配置Nginx将请求转发到多个上游服务器,此时可以通过使用upstream来声明,定义多个server可以参考之前upstream中的proxy_pass指令,如下所示:

1
2
3
4
5
6
7
8
9
10
upstream app {
server 127.0.0.1:5000;
server 127.0.0.1:5001;
server 127.0.0.1:5002;
}
server {
location / {
proxy_pass http://app;
}
}

使用上述配置时,Nginx会通过轮询的方式将连续的请求传递给3个上游服务器,当一个应用程序仅处理一个请求时,这种配置非常有用。

如果一个客户端总是希望到达同一个上游服务器来改善传统的会话粘滞性,那么就应该使用ip_hash;当发出的请求导致每一个请求的响应时间长短不一,那么应该选择使用least_conn;在一般请求下,使用默认的轮询算法就可以了。

非HTTP型上游服务器

在前面我们讨论的都是使用HTTP协议与上游服务器进行通信,此时我们使用了proxy_pass指令,但是在保持活动连接部分,Nginx能够将请求代理到不同类型的上游服务器上,它们每一个都有相对应的*_pass指令。

常用的有Memcached上游服务器、FastCGI上游服务器、SCGI上游服务器和uWSGI上游服务器,这里分别简单介绍一下。

Memcached上游服务器:在Nginx中,memcached模块(默认启用)负责与memcached守护进程通信,客户端和memcached守护进程之间没有直接通信,也就是说在这种情况下Nginx不是扮演一个反向代理的角色。

FastCGI上游服务器:一般来说都是使用FastCGI在Nginx服务器上运行PHP应用程序。fastcgi模块默认被编译过,通过fastcgi_pass模块即可激活使用该模块,然后Nginx就可以使用FastCGI协议同一个或者多个上游服务器进行会话。

SCGI上游服务器:Nginx还可以使用其内建的scgi模块来使用SCGI协议进行通信,其原理上与fastcgi模块类似,Nginx通过scgi_pass指令与上游服务器通信。

uWSGI上游服务器:uWSGI协议对于Python开发者非常受用,Nginx通过uwsgi模块提供基于Python的上游服务器连接,它的配置类似于fastcgi模块使用uwsgi_pass指令来指定上游服务器。

那么这样关于Nginx如何实现反向代理的介绍就到此为止。