6 Nginx 反向代理功能
反向代理: reverse proxy, 指的是代理外网用户的请求到内部的指定的服务器, 并将数据返回给用户的一种方式, 这是用的比较多的一种方式
Nginx除了可以为企业提供高性能的web服务之外, 另外还可以将Nginx本身不具备的请求通过某种预定义的协议转发至其他服务器处理, 不同的协议就是Nginx服务器与其他服务器进行通信的一种规范, 主要在不同的场景使用以下模块实现不同的功能
ngx_http_proxy_module: 将客户端的请求以http协议转发至指定的服务器进行处理(一般后端是http服务(apache,nginx), tomcat)
ngx_http_upstream_module: 用于定义为proxy_pass, fastcgi_pass, uwsgi_pass等指令引用的后端服务器分组
ngx_stream_proxy_module: 将客户端的请求以tcp协议转发至指定服务器处理(MySQL,Redis)
ngx_http_fastcgi_module: 将客户端对php的请求以fastcgi协议转发至指定服务器处理
ngx_http_uwsgi_module: 将客户端对Python的请求以uwsgi协议转发至指定服务器处理
生成环境部署架构:
访问逻辑图:
6.1 实现http反向代理
依赖于ngx_http_proxy_module模块
Nginx反向代理http服务:
6.1.1 Nginx http 反向代理入门
6.1.1.1 反向代理配置参数
1. proxy_pass
proxy pass; # 只能放在location中
# 用来设置将客户端请求转发给的后端服务器的主机, 可以是主机名, ip地址: 端口 的方式
# 也可以代理到预先设置的主机群组, 需要模块ngx_http_upstream_module支持
- proxy pass示例
location /web {
index index.html;
proxy_pass http://10.0.0.8:8080;
# 无/符号, 访问后端服务器时, 需要将location后面的url附加到proxy_pass指定的url后面, 类似于root
# proxy_pass指定的uri不带斜线, 访问/web, 等于访问后端服务器http://10.0.0.8:8080/web/index.html, 即后端服务器配置的站点根目录要有web目录才可以被访问
# http://nginx/web/index.html ==> http://10.0.0.8:8080/web/index.html
}
proxy_pass http://10.0.0.8:8080/;
# 有/符号, 相当于置换, 也就是alias, 即客户端访问/web, 实际返回proxy_pass后面uri内容
# proxy_pass指定的uri带斜线, 等于访问后端服务器的http://10.0.0.8:8080/index.html内容返回给客户端
# http://nginx/web/index.html --> http://10.0.0.8:8080
# 如果location定义其uri时, 使用了正则表达式模式, 则proxy_pass之后必须不能使用uri, 用户请求时传递的uri将直接附加至后端服务器之后
location ~|~* /uri {
proxy_pass http://host:port; # proxy_pass后面的uri不能加/, 如果加了/, 那么凡是包含uri的匹配条件, 都会转发到后端服务器的 http://host 根目录. 语法就会报错, 不让加/
}
# http://HOSTNAME/uri ---> http://host/uri
2. proxy_hide_header field
proxy_hide_header field;
# 用于nginx作为反向代理的时候, 在返回给客户端http响应时, 隐藏后端服务器响应报文指定头部的信息, 可以设置在http, server或location语句块
# 如果是多级代理的情况下, 那么只需要在与后端服务器连接的代理上配置proxy_hide_header即可
- proxy_hide_header field示例
location /static {
index index.html
proxy_pass http://10.0.0.85;
proxy_hide_header ETag;
}
修改前, 响应报文头部会携带ETag信息
修改后ETag信息被隐藏
[23:20:54 root@nginx ~]#curl -ikL pc.wang.org/static
HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0
Date: Thu, 18 Mar 2021 15:21:08 GMT
Content-Type: text/html; charset=iso-8859-1
Content-Length: 232
Location: http://pc.wang.org/static/
Connection: keep-alive
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Thu, 18 Mar 2021 15:21:08 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 21
Connection: keep-alive
Last-Modified: Thu, 18 Mar 2021 14:57:27 GMT
Accept-Ranges: bytes
/var/www/html/static
3. proxy_pass_header field
proxy_pass_header field;
# 默认nginx在响应报文中不会传递后端服务器的首部字段Date, Server, X-Pad, X-Accel等参数, 如果要传递的话则要使用proxy_pass_header field声明将后端服务器返回的值传递给客户端; field首部字段大小写不敏感
- proxy_pass_header field示例: 传递后端服务器Server字段
location /static {
proxy_pass http://10.0.0.85;
proxy_pass_header Server;
}
[23:35:09 root@client ~]#curl pc.wang.org/static -ikL
HTTP/1.1 301 Moved Permanently
Date: Thu, 18 Mar 2021 15:35:18 GMT
Content-Type: text/html; charset=iso-8859-1
Content-Length: 232
Location: http://pc.wang.org/static/
Connection: keep-alive
Server: Apache/2.4.37 (centos)
HTTP/1.1 200 OK
Date: Thu, 18 Mar 2021 15:35:18 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 21
Connection: keep-alive
Server: Apache/2.4.37 (centos) # 后端服务器的Server字段会覆盖Nginx自己的
Last-Modified: Thu, 18 Mar 2021 14:57:27 GMT
ETag: "15-5bdd0d162d9b7"
Accept-Ranges: bytes
/var/www/html/static
4. proxy_pass_request_body
proxy_pass_request_body on | off;
# 是否向后端服务器发送HTTP实体部分, 可以设置在http, server或location块, 默认为开启
# 建议不要关闭, 否则客户端发送的图片等信息, 会被nginx丢弃, 无法送达到后端服务器
5. proxy_pass_request_headers
proxy_pass_request_headers on | off;
# 是否将客户端的请求报文头部全部转发给后端服务器, 这里的请求报文头部指得是http头部. 可以设置在http, server或location语句块, 默认为开启, 不建议关闭
6. proxy_set_header
proxy_set_header;
# 可以更改或添加客户端请求头部信息内容并转发至后端服务器, 比如在后端服务器想要获取客户端的真实ip的时候, 就要更改每一级请求报文的头部, 比如当有多级代理的时候
- proxy_set_header示例: 获取客户端真实ip地址
默认情况, nginx在转发请求报文时, 只会保留http报文头部, 对于三层的ip首部会做修改, 因此, 需要在转发请求时, 把三层的ip首部添加到请求报文, 即可把客户端真实ip地址发送给后端服务器
利用proxy_set_header 可以自定义添加字段到请求报文的首部, proxy_set_header 要添加的自定义变量 nginx内置变量变量
location /static {
proxy_pass http://10.0.0.85;
proxy_pass_header Server;
proxy_set_header clientip $remote_addr;
}
由于proxy_set_header只是修改了请求报文的头部信息, 添加了自定义的字段, 因此, 还需要在后端服务器修改日志定义格式, 才能方便将客户端ip记录到日志信息中
# 修改后端apache服务器日志格式, 添加自定义的clientip
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \"%{clientip}i\"" combined
# 访问测试, 在10.0.0.187上访问
10.0.0.86 - - [19/Mar/2021:00:02:51 +0800] "HEAD /static HTTP/1.0" 301 - "-" "curl/7.29.0" "10.0.0.187 # 客户端ip地址
注意1:通过set_proxy_header自定义变量只是给请求报文添加了一个自定义的字段, 其字段值是人为根据系统内置变量设定的
注意2: 这种方法, 在多级代理的情况下, 并不能将客户端ip, 逐层的传给后端服务器, 而是需要利用$proxy_add_x_forwarded_for变量实现
注意3: 如果一定要使用proxy_set_header去传递客户端ip和每一层代理的ip地址, 那么需要在每一层nginx代理都开启proxy_set_header, 并且设置不同的自定义变量去引用nginx自带变量$remote_addr, 这样每一级nginx都会记录上一级, 也就包括客户端的ip地址, 同时, 在后端服务器的日志格式中, 要添加多个nginx自定义的变量, 这样也可以把客户端ip和中间经过的代理的ip全部传递给后端的服务器
client - nginx-1 - nginx-2 - server
nginx-1: proxy_set_header clientip $remote_addr;
nginx-2: proxy_set_header nginx-1 $remote_addr;
server日志: LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \"%{clientip}i\" \"%{nginx-1}i\"" combined
# clientip和nginx-1分别是nginx-1和nginx-2记录的前一级服务器的ip地址, %h会记录后端服务器前一级的ip地址, 这样就可以记录所有的ip地址信息了
$proxy_add_x_forwarded_for: 会把每一级代理的ip地址和客户端的ip地址统一发送给后端服务器
如果客户端的请求报文不包含X-Forwarded-For首部字段, 那么就用内置变量$remote_addr给X-Forwarded-For变量赋值, 这个X-Forwarded_For是nginx实现ip地址透传时官方定义的变量, $proxy_add_x_forwarded_for会往X-Forwarded_For中存ip地址
也就是说, 从客户端发出来的请求报文, 到达第一级代理时, 会被添加X-Forwarded-For:clientip, 发给二级代理, 然后添加X-Forwarded-For:clientip,一级代理ip, 以此类推, 会把从客户端到后端服务器中间经过的所有代理服务器的ip都带上
proxy_add_x_forwarded_for实现多级代理ip地址透传示例: 需要在每一级代理都开启
实验环境:
# 配置一级代理10.0.0.86
# 一级代理可以使用虚拟主机, 因为客户端访问的域名会配置在一级代理上
[00:41:46 root@nginx-1 ~]#vim /apps/nginx/conf.d/proxy.conf
server{
listen 80;
server_name apache.wang.org;
location /static {
proxy_pass http://10.0.0.85;
}
}
# 配置二级代理10.0.0.85
# 新开一个虚拟机, 安装nginx, 编辑主站点location
# 客户端访问apache.wang.org/static, 到了一级代理10.0.0.86会被转发到http://10.0.0.85/static, 因此, 要配置在主站点, 而非其他的虚拟主机
[09:09:04 root@nginx-2 ~]#vim /etc/nginx/nginx.conf
location /static {
proxy_pass http://10.0.0.84;
}
# 测试访问
[08:08:48 root@client ~]#curl apache.wang.org/static -L
10.0.0.84
# 在一二级代理分别配置$proxy_add_x_forwarded_for
# 一级代理
server{
listen 80;
server_name apache.wang.org; location /static {
proxy_pass http://10.0.0.85;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
# 二级代理
location /static {
proxy_pass http://10.0.0.84;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# apache上修改日志格式
[08:15:33 root@apache ~]#vim /etc/httpd/conf/httpd.conf
LogFormat "\"%{X-Forwarded-For}i\" %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
[08:16:21 root@apache ~]#systemctl restart httpd
# 如果后端是nginx, 需要添加日志格式, "$http_x_forwarded_for"
# 测试访问
# 客户端
[08:08:54 root@client ~]#curl apache.wang.org/static -L
10.0.0.84
# 一级代理日志
10.0.0.187 - - [19/Mar/2021:08:17:27 +0800] "GET /static HTTP/1.1" 301 232 "-" "curl/7.29.0"
10.0.0.187 - - [19/Mar/2021:08:17:27 +0800] "GET /static/ HTTP/1.1" 200 10 "-" "curl/7.29.0"
# 二级代理日志
10.0.0.86 - - [19/Mar/2021:08:17:27 +0800] "GET /static HTTP/1.0" 301 232 "-" "curl/7.29.0" "10.0.0.187"
10.0.0.86 - - [19/Mar/2021:08:17:27 +0800] "GET /static/ HTTP/1.0" 200 10 "-" "curl/7.29.0" "10.0.0.187"
# apache日志
"10.0.0.187, 10.0.0.86" 10.0.0.85 - - [19/Mar/2021:09:14:14 +0800] "GET /static/index.html HTTP/1.0" 200 10 "-" "curl/7.29.0"
# 多级代理总结
1. 一级代理, 可将用户访问的域名, 配置在虚拟主机上, 配合location指定proxy_pass和proxy_set_header
2. 二级代理, 需要在主站点的location / 配置proxy_pass和proxy_set_header,因为在一级代理上配置的是proxy_pass http://ip
3. 二级代理的ip是不会被添加到X-Forwarded-For中的, 而是直接作为客户端ip, 通过%h变量显示在apache的访问日志中, 如果后端是nginx, 那么就是通告$remote_addr变量
4. 如果后端服务器是nginx, 日志中需要使用"$http_x_forwarded_for" 代替 "X-Forwarded-For"
7. 有关反向代理时间的几个参数
以下时间都需要适当减少
proxy_connect_timeout [时间];
# 配置nginx服务器与后端服务器尝试建立连接的超时时间, 默认60s, 如果超过了超时时间, 那么会报504 Gateway Timeout
用法: proxy_connect_timeout 60s;
proxy_read_timeout [时间];
# 配置nginx服务器向后端服务器或服务器组发起read请求后, 等待的超时时间, 默认60s
proxy_send_timeout [时间];
# 配置nginx服务器向后端服务器或服务器组发起write请求后, 等待的超时时间, 默认60s
8. proxy_ignore_client_abort
proxy_ignore_client_abort off;
# 当客户端网络中断请求时, nginx服务器中断其对后端服务器的请求. 即如果此项设置为on, nginx会忽略客户端的网络中断, 并且一直等着代理服务执行返回, 如果设置为off, 那么客户端中断后, nginx也会终端客户端的请求, 并立即记录499日志, 默认是off
对于需要长连接的业务, 比如游戏, 建议开启, 一般的信息网站可以考虑中断, 减少服务器压力
9. hash表大小的设置
proxy_headers_hash_bucket_size 128;
# 当配置了proxy_hide_header和proxy_set_header的时候, 用于设置nginx保存http报文头部的hash表的上限
proxy_headers_hash_max_size 512;
# 设置了proxy_headers_hash_bucket_size的最大可用空间
server_names_hash_bucket_size 512;
# server_name hash表申请空间大小
server_names_hash_max_size 512;
# 设置服务器名称hash表的上限大小
6.1.1.2 案例: 反向代理单台web服务器
客户端 ----- http协议 ------- nginx(代理服务器,10.0.0.86) ----- http --- apache (10.0.0.85)
要求: 将用户对域pc.wang.org的请求转发至后端服务器处理
- 配置pc.wang.org
[21:49:16 root@nginx ~]#vim /apps/nginx/conf.d/pc.conf
server {
listen 80;
server_name pc.wang.org;
location / {
proxy_pass http://10.0.0.85; # 因为location是根, 所以proxy_pass后的斜线, 加不加无所谓, 因为到了后端服务器都是访问http://10.0.0.85的根目录
}
}
- 准备后端服务器
10.0.0.85, 安装apache
[22:04:46 root@apache ~]#echo website on 10.0.0.85 > /var/www/html/index.html
- 测试访问
客户端, 通过访问nginx上定义的虚拟主机中的server_name域名, 通过内部定义的location匹配规则, 被转发到10.0.0.85服务器
[22:05:37 root@nginx ~]#curl pc.wang.org
website on 10.0.0.85
- 查看访问日志
nginx上的访问日志显示请求来自于真正的客户端
10.0.0.1 - - [18/Mar/2021:22:23:44 +0800] "GET / HTTP/1.1" 200 21 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0"
后端apache服务器上的访问日志显示请求来自于反向代理服务器nginx
10.0.0.86 - - [18/Mar/2021:22:23:43 +0800] "GET / HTTP/1.0" 200 21 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0"
lvs与nginx对比:
lvs只工作在4层, 不会代替客户端向后端服务器发起请求, 因此, 后端服务器看到的请求地址是客户端地址
nginx作为反向代理, 工作在七层, 会代替客户端向后端服务器发起请求, 把客户端发出来的请求报文全部拆分, 拿到实际的数据, 然后nginx会重新封装请求报文, 把自己的ip作为请求报文的源ip, 发给后端服务器. 因此, 后端服务器看到的请求地址是nginx地址
lvs只是个转发器, 而nginx是代理
代理服务器与后端服务器连接出现问题可能发生的报错:
当后端服务器监听的端口和proxy_pass指定的端口不一致, 或者后端服务stop了, 会出现502报错, 502 Bad Gateway
当后端服务器把来自反向代理服务器的流量REJECT,会出现502, DROP时会出现504, 浏览器一直loading, 最后出现504 网关连接超时. 默认60秒内, 后端服务器无法响应, 会显示504超时
6.1.1.3 案例: 指定location实现反向代理
当用户访问http://pc.wang.org/static时, 转给后端apache服务器/var/www/html/static目录处理
当访问主页面时, 直接由nginx响应
# nginx配置
server {
listen 80;
server_name pc.wang.org;
location / {
root /data/nginx/html/pc;
}
location /static {
proxy_pass http://10.0.0.85; # 此时proxy_pass不能加/, 一旦加了/, 就和alias一个效果了
}
}
# apache配置
[22:57:09 root@apache ~]#mkdir /var/www/html/static
[22:57:15 root@apache ~]#echo /var/www/html/static > /var/www/html/static/index.html
6.1.1.4 案例: 基于正则表达式反向代理图片
location ~ \.(jpg|png|gif)$ {
proxy_pass http://10.0.0.85;
}
# 后端服务器创建图片
[23:07:34 root@apache /var/www/html]#ll -t
total 104
-rw-r--r-- 1 root root 41115 Mar 18 23:07 logo.jpg
如果后端服务器想把图片资源放到固定的目录下, 也可以自定义, 比如存到/var/www/html/static, 那么nginx的location就要修改为如下:
location ~ /static/\.(jpg|png|gif)$ {
proxy_pass http://10.0.0.85;
}
6.1.1.5 反向代理示例: nginx作为反向代理时可以提供缓存功能
缓存功能相关参数:
- 缓存功能默认关闭, 需要先手动配置才能启用
proxy_cache zone_name | off; 默认off, 可以配置在http, server, location
# 指明开启缓存, 或者关闭缓存
# zone_name, 表示缓存的名称, 需要由proxy_cache_path事先定义
proxy_cache_key string
# 缓存中'键'的内容, 默认值: proxy_cache_key $sheme$proxy_host$request_uri;
# 把用户访问的$scheme$proxy_host$request_uri也就是URL,做hash运算. 得到hash值
# 下次在访问时, 会比对用户访问的uri的hash值, 如果相同, 则返回缓存数据
proxy_cache_valid [code ...] time;
# 定义对特定响应码的响应内容的缓存时间, 定义在http语句块中
示例:
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_path;
# 定义缓存存放路径; 可以定义在http语句块
proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [min_free=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time];
# 定义缓存示例: 只能在HTTP语句块定义缓存信息
proxy_cache_path /var/cache/nginx/proxy_cache # 定义缓存保存路径, proxy_cache会自动创建, 但是上级目录必须事先存在
levels=1:2:2 # 定义缓存目录结构层次, 1:2:2可以生成2^4✖2^8✖2^8=1048576个目录
keys_zone=proxycache:20m # 指内存中缓存的大小, 主要用于存放key和metadata(如: 使用次数), hash值放在内存, 真正的缓存信息会放在磁盘
inactive=120s # 缓存有效时间, 指的是key的缓存时间
max_size=1g # 最大磁盘占用空间, 存入磁盘文件的缓存空间最大值
# 案例:
# 调用缓存功能, 需要定义在相应的配置语句块, 可以配置在http, server和location
proxy_cache proxycache;
proxy_cache_key $request_uri; # 对指定的数据进行MD5的运算作为缓存的key
proxy_cache_valid 200 302 301 10m; # 指定的状态码返回的数据缓存多长时间, 指的数据的缓存时间
proxy_cache_valid any 1m;
proxy_cache_use_state error | timeout | invalid_header | updating | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | off ...;
# 默认为off
# 在被代理的后端服务器出现哪种故障情况下, 可直接使用过期的缓存响应客户端
# 示例:
proxy_cache_use_stale error http_502 http_503;
proxy_cache_methods GET | HEAD | POST ...;
实验环境:
- apache上准备测试数据
[13:18:03 root@apache /var/www/html/static]#ll -h
total 1.2M
-rw-r--r-- 1 root root 1.2M Mar 19 13:18 log.html
- nginx上配置代理
[12:49:28 root@nginx ~]#vim /apps/nginx/conf.d/proxy.conf
server {
listen 80;
server_name apache.wang.org;
location /static {
proxy_pass http://10.0.0.85;
}
}
nginx -t
nginx -s reload
- 测试代理访问
[12:52:47 root@client ~]#curl apache.wang.org/static/log.html -I
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 19 Mar 2021 04:56:29 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 52809246
Connection: keep-alive
Last-Modified: Fri, 19 Mar 2021 04:47:40 GMT
ETag: "325ce1e-5bddc6a8218c4"
Accept-Ranges: bytes
- 测试没有开启缓存时的处理能力
[12:58:44 root@client ~]#yum -y install httpd-tools
[12:56:30 root@client ~]#ab -c 100 -n 1000 http://apache.wang.org/static/log.html
...
Requests per second: 26.15 [#/sec] (mean)
Time per request: 3824.697 [ms] (mean)
Time per request: 38.247 [ms] (mean, across all concurrent requests)
Transfer rate: 31458.96 [Kbytes/sec] received
...
- 开启缓存
# 先在主配置文件的HTTP语句块定义proxy_cache_path
http {
proxy_cache_path /data/nginx/proxycache levels=1:1:1 keys_zone=proxycache:20m inactive=120s max_size=1g;
# 在虚拟主机中调用缓存
server {
listen 80;
server_name apache.wang.org;
location /static {
proxy_pass http://10.0.0.85;
proxy_cache proxycache; # 这里的proxycache名字, 要和主配置文件http语句块中的keys_zone的值相等, 表示调用其缓存路径
proxy_cache_key $request_uri;
proxy_cache_valid 200 302 301 10m;
proxy_cache_valid any 5m;
}
}
[13:31:15 root@nginx ~]#mkdir /data/nginx
[13:32:17 root@nginx ~]#systemctl restart nginx
[13:32:24 root@nginx ~]#ll /data/nginx
total 0
drwx------ 2 nginx root 6 Mar 19 13:31 proxycache
[13:33:56 root@client ~]#curl http://apache.wang.org/static/log.html
# 现在只要有访问就会有缓存生成
[13:34:01 root@nginx ~]#tree /data/nginx/proxycache/
/data/nginx/proxycache/
└── d
└── b
└── e
└── a971fce2cfaae636d54b5121d7a74ebd
3 directories, 1 file
- 测试开启缓存后的处理能力
[13:36:14 root@client ~]#ab -c 100 -n 1000 http://apache.wang.org/static/log.html
Requests per second: 78.99 [#/sec] (mean)
Time per request: 1265.994 [ms] (mean)
Time per request: 12.660 [ms] (mean, across all concurrent requests)
Transfer rate: 95040.77 [Kbytes/sec] received
- 开启缓存出现的问题
页面更新会延迟, 页面更新了, 但是用户访问的还是旧的数据, 旧的数据需要120s才会清除
不过可以手动清除缓存, 删除/data/nginx/proxycache目录的内容
6.1.1.6 添加响应报文头部信息
nginx基于ngx_http_headers_module可以实现对后端服务器响应给客户端的报文中添加指定的响应首部字段
add_header name value [always]
可以配在http, server, location, if in location
示例:
add_header X-Via $server_addr; # 当前nginx主机的ip
add_header X-Cache $upstream_cache_status; # 是否缓存命中
add_header X-Accel $server_name; # 客户端访问的FQDN ```
add_header是给响应报文添加首部字段
而proxy_set_header是给请求报文的首部添加字段
- 添加响应报文字段前的头部信息
[13:36:46 root@client ~]#curl -I http://apache.wang.org/static/log.html
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 19 Mar 2021 06:03:25 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 1231827
Connection: keep-alive
Last-Modified: Fri, 19 Mar 2021 05:18:03 GMT
ETag: "12cbd3-5bddcd7258f75"
Accept-Ranges: bytes
- 添加响应报文字段
server {
listen 80;
server_name apache.wang.org;
location /static {
proxy_pass http://10.0.0.85;
proxy_cache proxycache;
proxy_cache_key $request_uri;
proxy_cache_valid 200 302 301 10m;
proxy_cache_valid any 5m;
add_header X-Via $server_addr; # 当前nginx主机的ip
add_header X-Cache $upstream_cache_status; # 是否缓存命中
add_header X-Accel $server_name; # 客户端访问的FQDN
}
}
[15:46:25 root@nginx ~]#nginx -s reload
- 客户端访问验证
[14:03:25 root@client ~]#curl -I http://apache.wang.org/static/log.html
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 19 Mar 2021 07:46:53 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 1231827
Connection: keep-alive
Last-Modified: Fri, 19 Mar 2021 05:18:03 GMT
ETag: "12cbd3-5bddcd7258f75"
X-Via: 10.0.0.86
X-Cache: MISS # 重启nginx后, 第一次访问是没有缓存的, 因此, X-Cache是MISS
X-Accel: apache.wang.org
Accept-Ranges: bytes
[15:46:53 root@client ~]#curl -I http://apache.wang.org/static/log.html
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 19 Mar 2021 07:47:22 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 1231827
Connection: keep-alive
Last-Modified: Fri, 19 Mar 2021 05:18:03 GMT
ETag: "12cbd3-5bddcd7258f75"
X-Via: 10.0.0.86
X-Cache: HIT # 再次访问, 因为第一次访问后, nginx会有缓存, 所以X-Cache为HIT, 缓存命中
X-Accel: apache.wang.org
Accept-Ranges: bytes
# 手动清空nginx上缓存, 再次访问, 就是MISS
[15:46:37 root@nginx ~]#rm -rf /data/nginx/proxycache/*
[15:47:38 root@client ~]#curl -I http://apache.wang.org/static/log.html
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 19 Mar 2021 07:48:22 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 1231827
Connection: keep-alive
Last-Modified: Fri, 19 Mar 2021 05:18:03 GMT
ETag: "12cbd3-5bddcd7258f75"
X-Via: 10.0.0.86
X-Cache: MISS
X-Accel: apache.wang.org
Accept-Ranges: bytes
6.1.2 Nginx http 反向代理高级应用
- lvs和nginx作为负载均衡调度器时的优缺点
lvs:
优点: 并发高, 常用的DR模型无需响应报文也经过lvs, 提高并发
缺点: 不对后端服务器做状态监测, 即使宕机, 也会继续调度;
只能给予四层以下进行调度, 基于目标ip, 目标mac, tcp协议
nginx:
优点: 支持4层和7层调度, 7层可以基于location的url地址进行调度, 实现动静分离
可以对后端服务器进行状态监测
nginx和后端服务器可以在相同或者不同的网段, 而lvs的DR模型要求lvs和后端服务器在相同的网段
缺点: 请求和响应报文都要经过nginx, 并发没有lvs高
proxy_pass可以让Nginx将客户端请求转发至后端单台服务器, 但是无法转发至特定的一组服务器, 而且不能对后端服务器提供相应的服务器状态监测.
Nginx可以基于ngx_http_upstream_module模块提供服务器分组转发, 权重分配, 状态监测, 使用不同的调度算法等高级功能
6.1.2.1 http upstream配置参数
upstream需要配置在http语句块
# 语法: 自定义一组服务器, 配置在http语句块内
upstream NAME {
server ... # 这里的server并不是虚拟主机, 而是定义分组里的一台服务器
...
}
# 示例:
upstream backend { # upstream指令需要配置主配置文件的http语句块, http语句块不能配置在子配置文件. 或者在子配置文件中只写upstream语句块
server backend1.example.com weight=5;
server 127.0.0.01:8080 max_fails=3 fail_timeout=30s;
sever unix:/tmp/backend3;
...
server backup1.example.com backup;
}
server ADDRESS [parameters];
# 配置一个后端web服务器, 配置在upstream内, 至少要有一个server服务器配置
# server支持的parameters如下:
weight=number # 设置权重, 默认为1, 实现类似于LVS之后能够的WRR和WLC等
max_conns=number # 给当前server设置最大活动连接数, 默认为0表示没有限制
max_fails=number # 对后端服务器连续监测失败多少次就标记为不可用, 默认为1次, 当客户端访问时, 才会利用TCP触发对后端服务器进行探测, 健康性检查, 而非周期性探测. 当有人访问时, nginx会向后端服务器发送tcp-syn包, 如果有回复, 才会调度, 根据配置的失败次数和超时时间, 会把后端服务器踢出分组
fail_timeout=time # 对后端服务器的单次监测超时时间, 默认为10秒, 10秒内如果没有回复, 会认为本次健康性检查失败, 超过了max_fails就会标记后端服务器不可用
backup # 设置为备份服务器, 当所有服务器不可用时将重新启动此服务器, 调度器可以把自己作为backup机器
down # 标记为down状态, 下线服务器, 不再被调度
resolve # 当server定义的是主机名的时候, 当A记录发生变化会自动应用新ip而不用重启nginx. 一般都是直接加ip地址, 不会写主机名
hash KEY [consistent];
# 基于指定请求报文中首部字段或者URI或内置变量等key做hash计算, 使用consistent参数, 将使用ketama一致性hash算法, 适用于后端是缓存服务器(如varnish)时使用, consistent定义使用一致性hash运算, 一致性hash基于取模运算
# 举例hash $request_uri consistent; # 基于用户请求的uri做hash
ip hash;
# 源地址hash调度算法, 基于客户端$remote_addr(源地址ipv4的前24位或整个ipv6地址)做hash计算, 以实现会话保持
least_conn;
# 最少连接调度算法, 优先将客户端请求调度到当前连接最少的后端服务器, 相当于lvs的wlc
关于ip_forward
LVS NAT需要开启: 因为NAT模式会修改目标ip地址, 需要根据目标ip进行转发
LVS DR无需开启: 因为DR模型是修改请求报文的目标MAC地址, ip地址不动, 因此不涉及ip转发
Nginx反向代理无需开启: Nginx作为反向代理时, 并不是转发数据包, 而是将请求报文拆分, 获得真实数据, 重新封装, 是代替客户端向后端服务器发起请求, 因此, 不需要ip转发
6.1.2.2 反向代理示例: 后端多台服务器
6.1.2.2.1 部署后端apache服务器
10.0.0.84和10.0.0.85分别安装apache, 制作默认页面
10.0.0.84的index.html为10.0.0.84
10.0.0.85的index.html为10.0.0.85
6.1.2.2.2 配置nginx反向代理
注意: 本实验过程要先关闭缓存
- 先定义upstream分组
# http语句块在主配置文件编辑
[18:17:28 root@nginx /apps/nginx/conf]#vim nginx.conf
http {
upstream apache-server {
server 10.0.0.84;
server 10.0.0.85;
}
#proxy_cache_path /data/nginx/proxycache levels=1:1:1 keys_zone=proxycache:20m inactive=120s max_size=1g;
#取消之前的缓存设置, 避免影响实验效果
- 定义后端服务器
server {
listen 80;
server_name apache.wang.org;
location / {
root /data/nginx/html/pc;
proxy_pass http://apache-server # 利用upstream模块中的proxy_pass时, proxy_pass指定的是后端服务器的组名
}
[18:23:55 root@nginx /apps/nginx/conf.d]#nginx -t
nginx: the configuration file /apps/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /apps/nginx/conf/nginx.conf test is successful
[18:23:56 root@nginx /apps/nginx/conf.d]#nginx -s reload
- 测试访问
# 由于没有定义权重, 因此, 默认都是1, 轮询调度
[18:24:26 root@client ~]#curl apache.wang.org
10.0.0.84
[18:24:48 root@client ~]#curl apache.wang.org
10.0.0.85
[18:24:48 root@client ~]#curl apache.wang.org
10.0.0.84
[18:24:49 root@client ~]#curl apache.wang.org
10.0.0.85
6.1.2.2.3 测试故障处理
- 停止10.0.0.84 apache服务
[18:27:34 root@84 ~]#systemctl stop httpd
- 客户端继续访问
[18:27:29 root@client ~]#while true; do curl apache.wang.org;sleep 1; done
10.0.0.84
10.0.0.85
10.0.0.84
10.0.0.85
10.0.0.84
10.0.0.85
10.0.0.84
10.0.0.85
10.0.0.85
10.0.0.85
10.0.0.85
# 可以看到, 即使没有配置超时时长和max_fails, nginx也会使用默认值对后端服务器进行状态检查, 默认超时10s, 失败一次就会被踢出分组
- 再把10.0.0.85停止
[18:30:36 root@85 ~]#systemctl stop httpd
- 结果出现502 Bad Gateway报错
[18:30:01 root@client ~]#while true; do curl apache.wang.org;sleep 1; done
10.0.0.85
10.0.0.85
10.0.0.85
10.0.0.85
10.0.0.85
10.0.0.85
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.18.0</center>
</body>
</html>
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.18.0</center>
</body>
</html>
6.1.2.2.4 配置backup服务器
- 在http语句块配置
http {
upstream apache-server {
server 10.0.0.84;
server 10.0.0.85;
server 127.0.0.1 backup;
}
#proxy_cache_path /data/nginx/proxycache levels=1:1:1 keys_zone=proxycache:20m inactive=120s max_size=1g;
[18:34:27 root@nginx /apps/nginx/conf]#nginx -s reload
- 由nginx的默认站点返回backup信息
# 默认站点家目录/apps/nginx/html
# 编写页面
[18:37:57 root@nginx /apps/nginx/conf]#echo 'sorry, website is down' > /apps/nginx/html/index.html
- 访问测试
[18:36:56 root@client ~]#while true; do curl apache.wang.org;sleep 1; done
sorry, website is down
sorry, website is down
- 恢复10.0.0.85
[18:31:43 root@85 ~]#systemctl start httpd
# 经过一段时间后, nginx恢复对于10.0.0.85的调度
[18:38:18 root@client ~]#while true; do curl apache.wang.org;sleep 1; done
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
sorry, website is down
10.0.0.85
10.0.0.85
10.0.0.85
10.0.0.85
6.1.2.2.5 nginx对后端服务器的健康性检查
当客户端访问时, 才会利用TCP触发对后端服务器进行探测, 健康性检查, 而非周期性探测.
当有人访问时, nginx会向后端服务器发送tcp-syn包, 如果有回复, 建立了三次握手, 才会调度, 否则根据配置的失败次数和超时时间, 会把后端服务器踢出分组
6.1.2.2.6 测试带权重的轮询
http {
upstream apache-server {
server 10.0.0.84 weight=2;
server 10.0.0.85;
server 127.0.0.1 backup;
}
[19:09:29 root@nginx /apps/nginx/conf]#nginx -s reload
# 结果为2:1调度
[19:09:59 root@client ~]#while true; do curl apache.wang.org;sleep 1; done
10.0.0.85
10.0.0.84
10.0.0.84
10.0.0.85
10.0.0.84
10.0.0.84
6.1.2.2.6 标记服务器为down
http {
upstream apache-server {
server 10.0.0.84 down;
server 10.0.0.85;
server 127.0.0.1 backup;
}
#proxy_cache_path /data/nginx/proxycache levels=1:1:1 keys_zone=proxycache:20m inactive=120s max_size=1g;
[19:10:56 root@nginx /apps/nginx/conf]#nginx -s reload
# 10.0.0.84不会被调度
[19:10:05 root@client ~]#while true; do curl apache.wang.org;sleep 1; done
10.0.0.85
10.0.0.85
10.0.0.85
10.0.0.85
6.1.2.2.7 基于hash的调度算法
hash KEY [consistent];
# 基于指定请求报文中首部字段或者URI或者内置变量等key做hash计算, 使用consistent参数, 将使用ketama一致性hash算法
# 适用于后端是缓存服务器(如varnish)时使用, consistent定义使用一致性hash运算, 一致性hash基于取模运算
# 举例hash $request_uri consistent; # 基于用户请求的uri做hash
http {
upstream apache-server {
hash $request_uri;
server 10.0.0.84;
server 10.0.0.85;
#server 127.0.0.1 backup; # 基于首部字段或URI等KEY做hash运算, 不支持backup备份服务器
}
#proxy_cache_path /data/nginx/proxycache levels=1:1:1 keys_zone=proxycache:20m inactive=120s max_size=1g;
[19:21:39 root@nginx /apps/nginx/conf]#nginx -s reload
- 测试效果
访问固定的URI会被调度到相同的服务器
- KEY可以是请求报文首部字段任意的KEY
[19:28:16 root@client ~]#curl -iv apache.wang.org
* About to connect() to apache.wang.org port 80 (#0)
* Trying 10.0.0.86...
* Connected to apache.wang.org (10.0.0.86) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: apache.wang.org
> Accept: */*
>
- 测试基于User-Agent字段进行调度, 由于nginx的内置变量没有user_agent, 因此, 可以使用http_NAME来获取user_agent, $http_NAME(表示请求报文的首部字段)
http {
upstream apache-server {
hash $http_user_agent;
server 10.0.0.84;
server 10.0.0.85;
#server 127.0.0.1 backup;
}
#proxy_cache_path /data/nginx/proxycache levels=1:1:1 keys_zone=proxycache:20m inactive=120s max_size=1g;
[19:31:26 root@nginx /apps/nginx/conf]#nginx -s reload
6.1.2.2.8 基于cookie实现会话绑定
基于内置变量$cookie_NAME: NAME可以是cookie中任意的key
- 示例:
[19:32:33 root@client ~]#curl -v -b country=cn apache.wang.org # curl利用-b携带cookie
* About to connect() to apache.wang.org port 80 (#0)
* Trying 10.0.0.86...
* Connected to apache.wang.org (10.0.0.86) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: apache.wang.org
> Accept: */*
> Cookie: country=cn # 请求报文中的头部信息, cookie一般是后端服务器生成, 发给客户端, 实验环境可以自定义, country就是自定义变量$cookie_NAME中NAME的值
...
- 先取消根据浏览器版本调度
http {
upstream apache-server {
#hash $http_user_agent;
server 10.0.0.84;
server 10.0.0.85;
#server 127.0.0.1 backup;
}
#proxy_cache_path /data/nginx/proxycache levels=1:1:1 keys_zone=proxycache:20m inactive=120s max_size=1g;
- 此时即使携带cookie, 也是默认的轮训调度, 因为还没有配置基于cookie调度
[19:41:28 root@client ~]#while true; do curl http://apache.wang.org/test1.html ;sleep 1; done
test1.html 10.0.0.84
test1.html 10.0.0.85
test1.html 10.0.0.84
test1.html 10.0.0.85
- 配置基于cookie调度
http {
upstream apache-server {
hash $cookie_country;
server 10.0.0.84;
server 10.0.0.85;
#server 127.0.0.1 backup;
}
#proxy_cache_path /data/nginx/proxycache levels=1:1:1 keys_zone=proxycache:20m inactive=120s max_size=1g;
[19:47:12 root@nginx /apps/nginx/conf]#nginx -s reload
- 测试访问
[19:44:53 root@client ~]#curl -b country=cn apache.wang.org
10.0.0.85
[19:47:54 root@client ~]#curl -b country=cn apache.wang.org
10.0.0.85
[19:47:55 root@client ~]#curl -b country=cn apache.wang.org
10.0.0.85
[19:47:55 root@client ~]#curl -b country=cn apache.wang.org
10.0.0.85
[19:47:56 root@client ~]#curl -b country=cn apache.wang.org
10.0.0.85
[19:47:56 root@client ~]#curl -b country=jp apache.wang.org
10.0.0.84
[19:48:01 root@client ~]#curl -b country=cn apache.wang.org
10.0.0.85
[19:48:03 root@client ~]#curl -b country=cn apache.wang.org
10.0.0.85
[19:48:04 root@client ~]#curl -b country=jp apache.wang.org
10.0.0.84
[19:48:05 root@client ~]#curl -b country=jp apache.wang.org
10.0.0.84
[19:48:05 root@client ~]#curl -b country=jp apache.wang.org
10.0.0.84
[19:48:06 root@client ~]#curl -b country=au apache.wang.org
10.0.0.84
[19:48:09 root@client ~]#curl -b country=au apache.wang.org
10.0.0.84
[19:48:10 root@client ~]#curl -b country=au apache.wang.org
10.0.0.84
[19:48:10 root@client ~]#curl -b country=au apache.wang.org
10.0.0.84
6.1.2.2.9 基于源ip地址hash实现会话绑定
nginx实现源地址hash可以有两种方式
一种是使用hash $remote_addr内置变量, 基于实现基于客户端源地址hash
hash只要是内置变量, 或者请求报文的首部字段, 都可以使用hash进行调度
另一种是ip hash, 源地址hash调度算法, 基于客户端$remote_addr(源地址ipv4的前24位或整个ipv6地址)做hash计算, 以实现会话保持
- 使用 hash $remote_addr
http {
upstream apache-server {
hash $remote_addr;
server 10.0.0.84;
server 10.0.0.85;
#server 127.0.0.1 backup;
}
#proxy_cache_path /data/nginx/proxycache levels=1:1:1 keys_zone=proxycache:20m inactive=120s max_size=1g;
[20:35:24 root@nginx /apps/nginx/conf]#nginx -s reload
[20:35:11 root@client ~]#while true; do curl http://apache.wang.org/test1.html ;sleep 1; done
test1.html 10.0.0.84
test1.html 10.0.0.84
test1.html 10.0.0.84
[20:36:12 root@84 ~]#curl apache.wang.org
10.0.0.84
[20:36:17 root@84 ~]#curl apache.wang.org
10.0.0.84
[20:36:18 root@84 ~]#curl apache.wang.org
10.0.0.84
[20:36:50 root@85 ~]#curl apache.wang.org
10.0.0.85
[20:36:51 root@85 ~]#curl apache.wang.org
10.0.0.85
[20:36:52 root@85 ~]#curl apache.wang.org
10.0.0.85
- 使用ip hash
# 客户端第一次访问时, 如何选择服务器进行调度
# 算法:
先将客户端源ip做hash运算, 然后除以后端服务器所有权重的总数, 得到取模结果, 再按照预先定义的取模值和被调度的服务器进行调度
比如: 后端两台服务器, 权重分别是1和3, 总权重为4
那么进行hash运算后的客户端ip地址除以4, 取模结果为0,1,2,3
之后可以定义, 如果取模结果为0,1,2就向第一台服务器调度, 如果是3, 就向第二台服务器调度
# 之后的访问就是基于第一次的调度进行调度了, 因为客户端的源ip一样
基于ip hash调度的问题
后端服务器, 如果添加或者删除后, 总的权重数会变化, 因此, ip地址hash运算后, 对总权重取模的值也会变化
此时, 如果还是按照原先的取模调度分配, 会造成相同的ip被调度到不同的服务器上
如果nginx调度的后端是缓存服务器, 那么添加或者删除了后端节点后会造成调度发生变化, 缓存失效
一旦缓存服务器缓存失效, 那么后端真正的服务器会接受大量的请求, 导致服务过载
缓存失效的解决方案:
consistent: 一致性hash算法
一致性hash会对用户访问的url地址做hash运算, 比如用户访问的某个url做hash运算的结果为zzz
同时也会对所有的后端服务器的ip地址做hash运算, 比如得到一个服务器的ip地址hash结果为xxx, 另一个是yyy
然后将xxx和yyy对2^32次方做取模, 得到的值是0-2^32-1中的某个数字, 比如结果分为是xxx->aaa, yyy->bbb
然后将这两个数字放到由40多亿个值组成的hash环上(0-2^32个),也就是从0-2^32-1的数字
此时, hash环上会有两个数字, 分别是aaa和bbb
当用户访问某个url, hash结果为zzz, 之后对2^32次方取模, 结果为ccc, 那么ccc会放在hash的某个地方
此时, 会按照顺时针(或者逆时针,看规定), 从ccc落位的位置, 开始找, 找到第一个落在了hash环上的后端服务器的信息
如果后端服务器权重不同
那么会对每个服务器, 按照自己的权重, 生成对应个数的ip地址+随机数
然后将多个ip地址+随机数的hash结果, 对2^32取模, 会得到和自己权重数字相同个数的结果, 落在hash环
这样, 高权重的后端服务器就会处理更多的请求
通过hash环的方法, 解决了后端服务器增加或者删除时, 整个调度策略都有可能改变的风险
因为, hash环上的数字是固定的, 每个url和ip地址计算出的结果也是固定的
即使添加或者删除后端服务器, 那么也只会影响其在环上周边节点的计算, 不会影响整个环
hash环偏斜
如果计算出来的节点, 都连在一起, 比如4个节点,连续落在了hash环
那么如果按照顺时针计算, 所有从第四个节点, 往后的所有节点的请求都会调度到这连续的4个节点的第一个节点服务器上, 这就是hash环的偏斜
hash环偏斜的解决方案
对后端服务器的权重加大, 比如分别设置为3000,5000,2000
这样三台后端服务器, 就会在hash环上有10000个节点, 一万个节点连续的概率较低
这样可以避免在hash环上, 不同服务器计算出来的节点在hash环上是连续的问题
配置案例:
http {
upstream apache-server {
hash $remote_addr consistent;
server 10.0.0.84;
server 10.0.0.85;
#server 127.0.0.1 backup;
}
#proxy_cache_path /data/nginx/proxycache levels=1:1:1 keys_zone=proxycache:20m inactive=120s max_size=1g;
6.1.2.2.10 least_conn调度算法
6.1.2.3 nginx调度算法总结
1. 轮询, server后不接weight, 默认权重为1
2. 加权轮训, server后接不同的weight, 默认权重为1
3. session绑定: 基于cookie:hash $cookie_country; hash后可以接内置变量, 用户方位的uri或者请求报文的任意字段
4. session绑定: 基于源地址ip
hash $remote_addr: 利用源地址ip
ip hash: 源地址hash调度算法, 基于客户端$remote_addr(源地址ipv4的前24位或整个ipv6地址)做hash计算, 以实现会话保持
5. 一致性hash
5. least_conn调度算法