Nginx实现反向代理
写在前面
通过前面的学习,我们已经对Nginx有了一个初步的认识,那么接下来将通过实际的例子来学习Nginx如何实现反向代理。
反向代理
入门演示
在实际工作中,服务器最常用的功能就是反向代理。使用反向代理后,直接收到请求的服务器是代理服务器,然后代理服务器将请求转发给内部网络上真正进行处理的服务器,并将得到的结果返回给客户端。也就是说,反向代理隐藏了真实的服务器,为服务器收发请求,使真实服务器对客户端不可见。反向代理一般用在需要处理跨域请求的时候,现在基本上大型的网站都设置了反向代理,下面简单介绍一下如何实现反向代理。
(1)考虑到后续的效果演示,这里修改之前在nginx.conf
主文件中的导入配置,之前只是导入了vhost目录下的某个名为envythink.com.conf
的配置文件,而在实际工作中肯定需要导入多个,因此将其修改为如下所示的信息:
1 | include vhost/*.conf; |
(2)在/usr/local/nginx/conf/vhost
目录下新建一个名为envy.com.conf
的配置文件,其中的代码如下所示:
1 | # /usr/local/nginx/conf/vhost/envy.com.conf |
可以看到这里修改了监听的端口和服务名称,更重要的是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 | # /usr/local/nginx/conf/vhost/envy.com.conf |
请注意由于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 | location /uri { |
这里假设的是server_name和listen分别为localhost和8080,这样当用户请求的是http://localhost:8080/uri
链接,那么就会被代理到http://localhost:8080/newuri
,也就是请求传递到上游服务器时会将/uri
替换为/newuri
。
但是请注意,这个规则存在两个例外的情况。
特殊情况1:如果在location中定义了一个正则表达式,那么URI部分就不会发生转换。
举个例子,下面例子中的/book
会被代理转发到/think
,进而访问到百度首页:
1 | server { |
但是当开发者采用正则表达式,将/book
变为如下配置:
1 | location ~ ^/book/ { |
那么URI中的/book
将会被直接传递到上游服务器,而不会转换到/think
。
特殊情况2:如果在location中有rewrite规则改变了URI,那么Nginx将使用这个URI处理请求,不会发生转换。
举个例子,下面例子中的URI传递到上游服务器的将是/index.html?page=<match>
,这个match来自于括号中捕获的参数,而不是预期的/index
,这一点需要格外注意:
1 | location / { |
在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 | # 由于在大多数情况下不会重写location的头,因此将proxy_redirect指令设置为off |
之后就可以修改之前的envy.com.conf
配置文件,如下所示:
1 | # /usr/local/nginx/conf/vhost/envy.com.conf |
当然了如果该proxy.conf文件中的某些值在某些location内不适用,那么开发者就可以通过在location内再次定义自己所需的参数,这样就能覆盖掉原来的值:
1 | location /think/ { |
请注意这里的顺序非常重要,如果在配置文件或者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 | upstream apache { |
在上面的配置中,我们指定了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 | upstream memcaches { |
如果从默认的轮询负载均衡算法(round-robin)切换为ip_hash或者least_conn,那么开发者需要在使用到keepalive指令之前指定负载均衡算法:
1 | upstream memcaches { |
上游服务器的类型
上游服务器是Nginx代理连接的一个服务器,它可以是不同的物理机器,也可以是虚拟机等。上游服务器可以是一个在本地机器上监听UNIX域套接字的机器,也可以是TCP监听的众多不同机器中的其中一员。上游服务器分为单个上游服务器、多个上游服务器和非HTTP型上游服务器。
单个上游服务器
这是最基本的代理配置,举个例子,如下所示:
1 | server { |
Nginx就会终止所有的客户端连接,然后将代理所有请求到本地主机的TCP协议的8080端口上。
多个上游服务器
在实际工作中可能也需要配置Nginx将请求转发到多个上游服务器,此时可以通过使用upstream来声明,定义多个server可以参考之前upstream中的proxy_pass指令,如下所示:
1 | upstream 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如何实现反向代理的介绍就到此为止。