nginx功能介绍和使用 nginx配置反向代理和负载均衡
反向代理与负载均衡配置接下来介绍Nginx的重要功能:反向代理+负载均衡 。单体Nginx的性能虽然不错 , 但也是有瓶颈的 。打个比方:用户请求发起一个请求 , 网站显示的图片量比较大 , 如果这个时候有大量用户同时访问 , 全部的工作量都集中到了一台服务器上 , 服务器不负重压 , 可能就崩溃了 。高并发场景下 , 自然需要多台服务器进行集群 , 既能防止单个节点崩溃导致平台无法使用 , 又能提高一些效率 。一般来说 , Nginx完成10万多用户同时访问 , 程序就相对容易崩溃 。
要做到高并发和高可用 , 肯定需要做Nginx集群的负载均衡 , 而Nginx负载均衡的基础之一就是反向代理 。
文章插图
演示环境说明为了较好地演示反向代理的效果 , 本小节调整一下演示的环境:
不再通过浏览器发出HTTP请求 , 而是使用curl指令从笔者的CentOS虚拟机192.168.233.128向Windows宿主机器192.168.233.1上的Nginx发起请求 。
为了完成演示 , 在宿主机Nginx的配置文件nginx-proxydemo.conf中配置两个server虚拟主机 , 一个端口为80 , 另一个端口为8080 。具体如下:
#模拟目标主机 server { listen 8080 ; server_name localhost; default_type 'text/html'; charset utf-8; location / { echo "-uri= $uri""-host= $host" "-remote_addr= $remote_addr" "-proxy_add_x_forwarded_for= $proxy_add_x_forwarded_for" "-http_x_forwarded_for= $http_x_forwarded_for" ; } } #模拟代理主机 server { listen 80 default; server_name localhost; default_type 'text/html'; charset utf-8; location / { echo "默认根路径匹配: /"; } ... }
本节用到的配置文件为源码工程nginx-proxy-demo.conf文件 。运行本小节的实例前需要修改openresty-start.bat(或openrestystart.sh)脚本中的PROJECT_CONF变量的值 , 将其改为nginx-proxydemo.conf , 然后重启OpenRestry/Nginx 。文章插图
proxy_pass反向代理指令这里介绍的proxy_pass反向代理指令处于ngx_http_proxy_module模块 , 并且注册在HTTP请求11个阶段的content阶段 。
proxy_pass反向代理指令的格式如下:
proxy_pass 目标URL前缀;
当proxy_pass后面的目标URL格式为”协议”+”IP[:port]”+”/”根路径的格式时 , 表示最终的结果路径会把location指令的URI前缀也给加上 , 这里称为不带前缀代理 。如果目标URL为”协议”+”IP[:port]” , 而没有“/根路径” , 那么Nginx不会把location的URI前缀加到结果路径中 , 这里称为带前缀代理 。
1.不带location前缀的代理
proxy_pass后面的目标URL前缀加“/根路径” , 实例如下:
#不带location前缀的代理类型 location /foo_no_prefix { proxy_pass http://127.0.0.1:8080/; }
通过CentOS的curl指令发出请求http://192.168.233.1/foo_no_prefix/bar.html , 结果如下:[root@localhost ~]#curl http://192.168.233.1/foo_no_prefix/bar.html-uri= /bar.html -host= 127.0.0.1 -remote_addr= 127.0.0.1 -proxy_add_x_forwarded_for= 127.0.0.1 -http_x_forwarded_for=
可以看到 , $uri变量输出的代理URI为/bar.html , 并没有在结果URL中看到location配置指令的前缀/foo_no_prefix 。2.带location前缀的代理
proxy_pass后面的目标URL前缀不加“/根路径” , 实例如下:
#带location前缀代理 location /foo_prefix { proxy_pass http://127.0.0.1:8080; }
通过CentOS的curl指令发出请求http://192.168.233.1/foo_prefix/bar.html , 结果如下:[root@localhost ~]#curl http://192.168.233.1/foo_prefix/bar.html-uri= /foo_prefix/bar.html -host= 127.0.0.1 -remote_addr= 127.0.0.1 -proxy_add_x_forwarded_for= 127.0.0.1 -http_x_forwarded
可以看到 , $uri变量输出的代理URI为/foo_prefix/bar.html , 也就是说 , 在结果URL中看到了location配置指令的前缀/foo_prefix 。除了以上两种代理(带location前缀的代理和不带location前缀的代理)之外 , 还有一种带部分URI路径的代理 。
3.带部分URI路径的代理
如果proxy_pass的路径参数中不止有IP和端口 , 还有部分目标URI的路径 , 那么最终的代理URL由两部分组成:第一部分为配置项中的目标URI前缀;第二部分为请求URI中去掉location中前缀的剩余部分 。
下面是两个实例:
#带部分URI路径的代理 , 实例1 location /foo_uri_1 { proxy_pass http://127.0.0.1:8080/contextA/; } #带部分URI路径的代理 , 实例2 location /foo_uri_2 { proxy_pass http://127.0.0.1:8080/contextA-; }
通过CentOS的curl指令发出两个请求分别匹配到这两个location配置 , 结果如下:[root@localhost ~]#curl http://192.168.233.1/foo_uri_1/bar.html-uri= /contextA/bar.html -host= 127.0.0.1 -remote_addr= 127.0.0.1 -proxy_add_x_forwarded_for= 127.0.0.1 -http_x_forwarded_fo[root@localhost ~]#curl http://192.168.233.1/foo_uri_2/bar.html-uri= /contextA-bar.html -host= 127.0.0.1 -remote_addr= 127.0.0.1 -proxy_add_x_forwarded_for= 127.0.0.1 -http_x_forwarded_fo
从输出结果可以看出 , 无论是例子中的目标URI前缀/contextA/ , 还是目标URI前缀/contextA- , 都加在了最终的代理路径上 , 只是在代理路径中去掉了location指令的匹配前缀 。新的问题来了:仅仅使用proxy_pass指令进行请求转发 , 发现很多原始请求信息都丢了 。明显的是客户端IP地址 , 前面的例子中请求都是从192.168.233.128 CentOS机器发出去的 , 经过代理服务器之后 , 服务端返回的remote_addr客户端IP地址并不是192.168.233.128 , 而是变成了代理服务器的IP 127.0.0.1 。
如何解决原始信息的丢失问题呢?使用proxy_set_header指令 。
文章插图
proxy_set_header请求头设置指令在反向代理之前 , proxy_set_header指令能重新定义/添加字段传递给代理服务器的请求头 。请求头的值可以包含文本、变量和它们的组合 。它的格式如下:
#head_field表示请求头 , field_value表示值proxy_pass_header head_field field_value;
前面讲到 , 由于经过反向代理后 , 对于目标服务器来说 , 客户端在本质上已经发生了变化 , 因此后端的目标Web服务器无法直接拿到客户端的IP 。假设后端的服务器是Tomcat , 那么在Java中request.getRemoteAddr()取得的是Nginx的地址 , 而不是客户端的真实IP 。如果需要取得真实IP , 那么可以通过proxy_set_header指令在发生反向代理调用之前将保持在内置变量$remote_addr中的真实客户端地址保持到请求头中(一般为X-real-ip) , 代码如下:
#不带location前缀的代理 location /foo_no_prefix/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header X-real-ip $remote_addr; }
在Java端使用request.getHeader(”X-real-ip”)获取X-real-ip请求头的值就可以获得真正的客户端IP 。在整个请求处理的链条上可能不仅一次反向代理 , 可能会经过N多次反向代理 。为了获取整个代理转发记录 , 也可以使用proxy_set_header指令来完成 , 在配置文件中进行如下配置:
#带location前缀的代理 location /foo_prefix { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8080; }
这里使用了$proxy_add_x_forwarded_for内置变量 , 它的作用就是记录转发历史 , 其值的第一个地址就是真实地址$remote_addr , 然后每经过一个代理服务器就在后面累加一次代理服务器的地址 。上面的演示程序中 , 如果在Java服务器程序中通过如下代码获取代理转发记录:
request.getHeader(“X-Forwarded-For”)
那么Java程序获得的返回值为“192.168.233.128 , 127.0.0.1” , 表示最初的请求客户端的IP为192.168.233.128 , 经过了127.0.0.1代理服务器 。每经过一次代理服务器 , 都会在后边追加上它的IP , 并且使用逗号隔开 。
为了不丢失信息 , 反向代理的设置如下:
location /hello { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_redirect off; }
设置了请求头Host、X-real-ip、X-Forwarded-For , 分别将当前的目标主机、客户端IP、转发记录保存在请求头中 。proxy_redirect指令的作用是修改从上游被代理服务器传来的应答头中的Location和Refresh字段 , 尤其是当上游服务器返回的响应码是重定向或刷新请求(如HTTP响应码是301或者302)时 , proxy_redirect可以重设HTTP头部的location或refresh字段值 。off参数表示禁止所有的proxy_redirect指令 。
upstream上游服务器组假设Nginx只有反向代理没有负载均衡 , 它的价值会大打折扣 。
Nginx在配置反向代理时可以通过负载均衡机制配置一个上游服务器组(多台上游服务器) 。当组内的某台服务器宕机时仍能保持系统可用 , 从而实现高可用 。
Nginx的负载均衡配置主要用到upstream(上游服务器组)指令 , 其格式如下:
语法:upstream name { … }
上下文:http配置块
upstream指令后面的name参数是上游服务器组的名称;upstream块中将使用server指令定义组内的上游候选服务器 。
upstream指令的作用与server有点类似 , 其功能是加入一个特殊的虚拟主机server节点 。特殊之处在于这是上游server服务组 , 可以包含一个或者多个上游server 。
一个upstream负载均衡主机节点的配置实例如下:
#upstream负载均衡虚拟节点upstream balanceNode { server "192.168.1.2:8080"; #上游候选服务1 server "192.168.1.3:8080"; #上游候选服务2 server "192.168.1.4:8080"; #上游候选服务3 server "192.168.1.5:8080"; #上游候选服务4
}实例中配置的balanceNode相当于一个主机节点 , 不过这是一个负载均衡类型的特定功能虚拟主机 。当请求过来时 , balanceNode主机节点的作用是按照默认负载均衡算法(带权重的轮询算法)在4个上游候选服务中选取一个进行请求转发 。实战案例:在随书源码的nginx-proxy-demo.conf配置文件中配置3个server主机和一个upstream负载均衡主机组 。此处配置了一个location块 , 将目标端口为80的请求反向代理到upstream主机组 , 以方便负载均衡主机的行为 。
实战案例的配置代码节选如下:
#负载均衡主机组 , 给虚拟主机1与虚拟主机2做负载均衡 upstream balance { server "127.0.0.1:8080"; #虚拟主机1 server "127.0.0.1:8081"; #虚拟主机2 } #虚拟主机1 server { listen 8080; server_name localhost; location / { echo "server port:8080" ; } } #虚拟主机2 server { listen 8081 ; server_name localhost; location / { echo "server port:8081" ; } } #虚拟主机3:默认虚拟主机 server { listen 80 default; ... #负载均衡测试连接 location /balance { proxy_pass http://balance; #反向代理到负载均衡节点 } }
运行本小节的实例前需要修改启动脚本openresty-start.bat(或openresty-start.sh)中的PROJECT_CONF变量的值 , 将其改为nginx-proxy-demo.conf , 然后重启OpenRestry/Nginx 。在CentOS服务器中使用curl命令请求http://192.168.233.1/balance链接地址(IP根据Nginx情况而定) , 并且多次发起请求 , 就会发现虚拟主机1和虚拟主机2被轮流访问到 , 具体的输出如下:
[root@localhost ~]#curl http://192.168.233.1/balanceserver port:8080[root@localhost ~]#curl http://192.168.233.1/balanceserver port:8081[root@localhost ~]#curl http://192.168.233.1/balanceserver port:8080[root@localhost ~]#curl http://192.168.233.1/balanceserver port:8081[root@localhost ~]#curl http://192.168.233.1/balanceserver port:8080
通过结果可以看出 , upstream负载均衡指令起到了负载均衡的效果 。默认情况下 , upstream会依照带权重的轮询方式进行负载分配 , 每个请求按请求顺序逐一分配到不同的上游候选服务器 。upstream的上游服务器配置upstream块中将使用server指令定义组内的上游候选服务器 。内部server指令的语法如下:
语法:server address [parameters];
上下文:upstream配置块
此内嵌的server指令用于定义上游服务器的地址和其他可选参数 , 它的地址可以指定为域名或IP地址带有可选端口 , 如果未指定端口 , 就使用端口80 。
内嵌的server指令的可选参数大致如下:
(1)weight=number(设置上游服务器的权重):默认情况下 , upstream使用加权轮询(Weighted Round Robin)负载均衡方法在上游服务器之间分发请求 。weight值默认为1 , 并且各上游服务器的weight值相同 , 表示每个请求按先后顺序逐一分配到不同的上游服务器 , 如果某个上游服务器宕机 , 就自动剔除 。
如果希望改变某个上游节点的权重 , 就可以使用weight显式进行配置 , 参考实例如下:
#负载均衡主机组 upstream balance { server "127.0.0.1:8080" weight=2; #上游虚拟主机1 , 权重为2 server "127.0.0.1:8081" weight=1; #上游虚拟主机2 , 权重为1 }
权重越大的节点 , 将被分发到更多请求 。(2)max_conns=number(设置上游服务器的最大连接数):
max_conns参数限制到上游节点的最大同时活动连接数 。默认值为零 , 表示没有限制 。如果upstream服务器组没有通过zone指令设置共享内存 , 那么在单个Worker工作进程范围内对上游服务的最大连接数进行限制;如果upstream服务器组通过zone指令设置了共享内存 , 那么在全体的Worker工作进程范围内对上游服务进行统一的最大连接数限制 。
(3)backup(可选参数):backup参数标识该server是备份的上游节点 , 当普通的上游服务(非backup)不可用时 , 请求将被转发到备份的上游节点;当普通的上游服务(非backup)可用时 , 备份的上游节点不接受处理请求 。
(4)down(可选参数):down参数标识该上游server节点为不可用或者永久下线的状态 。
(5)max_fails=number(最大错误次数):如果上游服务不可访问了 , 如何判断呢?max_fails参数是其中之一 , 该参数表示请求转发最多失败number次就判定该server为不可用 。max_fails参数的默认次数为1 , 表示转发失败1次 , 该server即不可用 。如果此参数设置为0 , 就会禁用不可用的判断 , 一直不断地尝试连接后端server 。
(6)fail_timeout=time(失败测试的时间长度):这是一个失效监测参数 , 一般与上面的参数max_fails协同使用 。fail_timeout的意思是失败测试的时间长度 , 指的是在fail_timeout时间范围内最多尝试max_fails次 , 就判定该server为不可用 。fail_timeout参数的默认值为10秒 。
server指令在进行max_conns连接数配置时 , Nginx内部会涉及共享内存区域的使用 , 配置共享内存区域的指令为zone , 其具体语法如下:语法:zone name [size];
上下文:upstream配置块
zone的name参数设置共享内存区的名称 , size可选参数用于设置共享内存区域的大小 。如果配置了upstream的共享内存区域 , 那么其运行时状态(包括最大连接数)在所有的Worker工作进程之间是共享的 。在name相同的情况下 , 不同的upstream组将共享同一个区 , 这种情况下 , size参数的大小值只需设置一次 。
下面是一个server指令和zone指令的综合使用实例:
upstream zuul {zone upstream_zuul 64k; //名称为upstream_zuul , 大小为64KB的共享内存区域server "192.168.233.128:7799" weight=5 max_conns=500;server "192.168.233.129:7799" fail_timeout=20s max_fails=2; //默认权重为1server "192.168.233.130:7799" backup; //后备服务}
upstream的负载分配方式upstream大致有3种负载分配方式 , 下面一一介绍 。1.加权轮询
默认情况下 , upstream使用加权轮询(Weighted Round Robin)负载均衡方法在上游服务器之间分发请求 , 默认的权重weight值为1 , 并且各上游服务器weight值相同 , 表示每个请求按到达的先后顺序逐一分配到不同的上游服务器 , 如果某个上游服务器宕机 , 就自动剔除 。
指定权重weight值 , weight和分配比率成正比 , 用于后端服务器性能不均的情况 。下面是一个简单的例子:
upstream backend { server 192.168.1.101 weight=1; server 192.168.1.102 weight=2; server 192.168.1.103 weight=3; }
2.hash指令基于hash函数值进行负载均衡 , hash函数的key可以包含文本、变量或二者的组合 。hash函数负载均衡是一个独立的指令 , 指令的格式如下:
语法:hash key [consistent];
上下文:upstream配置块注意 , 如果upstream组中摘除掉一个server , 就会导致hash值重
新计算 , 即原来的大多数key可能会寻址到不同的server上 。若配置有consistent参数 , 则hash一致性将选择Ketama算法 。这个算法的优势是 , 如果有server从upstream组里摘除掉 , 那么只有少数的key会重新映射到其他的server上 , 即大多数key不受server摘除的影响 , 还走到原来的server 。这对提高缓存server命中率有很大帮助 。下面是一个简单的通过请求的$request_uri的hash值进行负载均衡的例子:
upstream backend { hash $request_uri consistent; server 192.168.1.101 ; server 192.168.1.102 ; server 192.168.1.103 ;}
3.ip_hash指令基于客户端IP的hash值进行负载平衡 , 这样每个客户端固定访问同一个后端服务器 , 可以解决类似session不能跨服务器的问题 。如果上游server不可用 , 就需要手工摘除或者配置down参数 。ip_hash是一条独立的指令 , 其使用的示例如下:
【nginx功能介绍和使用 nginx配置反向代理和负载均衡】
upstream backend { ip_hash; server 192.168.1.101:7777; server 192.168.1.102:8888; server 192.168.1.103:9999; }
推荐阅读
- 简述龙爪槐修剪的具体方法 龙爪槐的修剪方法介绍
- 全球十大恐怖禁地 全球十大恐怖禁地介绍
- 楼兰古国在哪里 楼兰古国的位置介绍
- 花草树木的作用 用途介绍
- 全方面了解nginx高性能机制 nginx高性能web服务器详解
- 大发明家皮肤有哪些 大发明家皮肤介绍
- 丰收生菜包 丰收生菜包做法介绍
- 冷冻扇贝肉的家常做法 冷冻扇贝肉的家常做法介绍
- 脆皮烤鸭的做法与配方 脆皮烤鸭的做法与配方介绍
- 自动挡汽车档位介绍