骏马金龙 (新博客:www.junmajinlong.com)

网名骏马金龙,钟情于IT世界里的各种原理和实现机制,强迫症重症患者。爱研究、爱翻译、爱分享。特借此一亩三分田记录自己成长点滴!!!

nginx的反向代理功能和缓存功能

Nginx系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.html


1. nginx的反向代理功能

1.1 正向代理和反向代理

正向代理是众多内网客户机上网访问互联网上的网站时,将所有的请求交给内网前面处于公网上的"管家"服务器,由"管家"服务器代为请求想要访问的web服务器,然后将得到的结果缓存下来并提供给客户端,这是正向代理。"管家"服务器称为正向代理服务器。

反向代理是客户端访问web服务器时,请求发送到真实的web服务器的前端"助手"服务器上,由"助手"服务器决定将此请求转发给哪个真实的web服务器,外界客户端以为"助手"服务器就是真实的web服务器,而实际上它不是,也不需要安装任何web程序。"助手"服务器称为反向代理服务器。

nginx是一个优秀的反向代理服务程序,通过反向代理可以实现负载均衡的效果。因为是通过反向代理实现的负载均衡,所以nginx实现的是七层负载均衡。它能够识别http协议,根据http报文将不同类型的请求转发到不同的后端web服务器上。后端的web服务器称为"上游服务器",即upstream服务器。

实际上,nginx和php-fpm结合的时候,指令fastcgi_pass实现的也是反向代理的功能,只不过这种代理功能是特定的功能,只能转发给php-fpm。

nginx的反向代理有几种实现方式:

  1. 仅使用模块ngx_http_proxy_module实现简单的反向代理。指令为proxy_pass。
  2. 使用fastcgi模块提供的功能,反向代理动态内容。指令为fastcgi_pass。
  3. 使用ngx_http_memcached_module模块提供的功能,反向代理memcached缓存内容,指令为memcached_pass。
  4. 结合upstream模块实现更人性化的分组反向代理。

1.2 配置简单的反代实验

实验环境如下图:

反向代理服务器nginx-proxy(192.168.100.29)的配置。由于是做代理,所以配置文件的location段不再需要root、index等指令,只需几个和代理相关的指令即可。

server {
    listen       80;
    server_name  www.longshuai.com;
    location ~ \.(png|jpg|jpeg|bmp|gif)$ {
        proxy_pass http://192.168.100.28:80;
    }
    location / {
        proxy_pass http://192.168.100.30:80/;
    }
    location ~ \.(php|php5)$ {
        proxy_pass http://192.168.100.25:80;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
}

提供动态服务的nginx服务器(192.168.100.25)的配置如下。

server {
    listen       80;
    server_name  www.longshuai.com;
    location / {
        root    /www/longshuai/;
        index   index.php index.html index.htm;
    }
    location ~ \.php$ {
        root /php/;
        fastcgi_pass    192.168.100.27:9000;
        fastcgi_index   test.php;
        include         fastcgi.conf;
    }
}

其中php-fpm服务器(192.168.100.27)上的/www/longshuai/index.php内容如下:

<h1>page from php-fpm</h1>
<?php
phpinfo();
?>

LB1(192.168.100.28)和LB2(192.168.100.30)的web程序都是httpd。其中作为一般静态web服务器的LB2的配置文件没有任何修改,它的/var/www/html/index.html的内容如下:

<h1>LB2:static</h1>

作为图片服务器的LB1在配置文件中添加了如下几行。且其/var/www/html/下有一个图片文件a.png。

<Files ~ "\.(png|jpeg|jpg|bmp|gif)">
        Order allow,deny
        Allow from all
</Files>

经过以上的配置,可以实现如下图的功能。当访问www.longshuai.com的时候,任意以php结尾的文件请求都转发给nginx再由nginx交由php-fpm处理;任意以图片格式结尾(png/jpg/jpeg/bmp/gif)的请求都转发给LB1;任意非以上两种格式的请求都转发给LB2。

重载nginx-proxy/nginx/LB1/LB2上的nginx或者httpd。然后进行测试。

1.3 使用upstream模块实现分组反向代理

前面只使用了ngx_http_proxy_module来实现反向代理,但是其缺陷在于在nginx-proxy上定义的每条代理规则都只能转发到后台的某一台服务器上,即后端服务器无法分组。例如当图片服务器压力太大,添加一台服务器想要减轻图片服务器压力,但是仅使用proxy模块无法实现此类负载均衡到多台图片服务器上。

这时需要借助ngx_http_upstream_module模块,该模块用于定义后端服务器池,后端服务器也称为上游服务器(upstream),可以为每一种类型的后端服务器分一个组。然后在结合proxy_pass或其他代理指令将相应的请求转发到池内。

服务器池可以有多台服务器,多台服务器如何实现负载均衡和算法有关,默认是指定权重的加权均衡算法,还可以指定ip_hash算法实现同一个客户端IP发起的请求总是转发到同一台服务器上。还有一些其它算法,如最小连接数算法。最常用的还是加权算法,然后通过session共享的方式实现同一个客户端IP发起的请求转发到同一服务器上。

例如,下图描述的需求。当请求图片服务器时,可以将请求均衡到IP3和IP4两台服务器上,当请求其他静态内容,可以将请求均衡到IP5和IP6两台服务器上。

要实现这样的功能,nginx-proxy上的nginx配置文件内容大致如下:

http {
    include mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;

    # define server pool
    upstream dynamic_pool {
        server IP1:80;
    }
    upstream pic_pool {
        server IP3:80 weight=2;
        server IP4:80 weight=1;
    }
    upstream static_pool {
        server IP5:80 weight=1;
        server IP6:80 weight=1;
    }

    server {
        listen 80;
        server_name www.longshuai.com;

        # define how to proxy
        location ~ \.(php|php5)$ {
            proxy_pass http://dynamic_pool;
        }
        location ~ \.(png|jpeg|jpg|bmp|gif)$ {
            proxy_pass http://pic_pool;
        }
        location / {
            proxy_pass http://static_pool;
        }
    }
}

1.4 ngx_http_proxy_module模块

1.4.1 指令及其意义

该模块默认安装。以下是相关指令的说明。

指令指令意义
proxy_pass 定义代理到哪台服务器或哪个upstream池
proxy_set_header 在代理服务器上设置http报头信息。如加上真实客户端地址"proxy_set_header X_Forwarded_For $remote_addr"
proxy_connect_timeout 反向代理连接上游服务器节点的超时时间。发起方是proxy方,即等待握手成功的时间
proxy_send_timeout 上游服务器节点数据传给代理服务器的超时时间。即此时间段内,后端节点需要传完数据给代理服务器
proxy_read_timeout 定义代理服务器何时关闭和后端服务器连接的超时时长,默认为60秒,表示某次后端传输完数据给代理服务器后,如果60秒内代理服务器和后端服务器没有任何传输,则关闭此次连接。目的是避免代理服务器和后端服务器一直保持连接不断开而占用资源

1.4.2 proxy_pass

proxy_pass http[s]://{ [IP:PORT/uri/] | upstream_pool };

该指令在前文示例中已经演示过了。此处只说明注意点。

当proxy_pass所在的location中使用了正则匹配时,则proxy_pass(包括fastcgi_pass和memcached_pass)定义的转发路径都不能包含任何URI信息。另外,location中使用了尾随斜线,那么proxy_pass定义的转发路径也必须使用斜线,或者都不加尾随斜线。

例如下面的配置方式是允许的。当访问www.longshuai.com/forum/时将被转发到http://192.168.100.25:8080/bbs/上。

server_name www.longshuai.com;
location /forum/ {
    proxy_pass http://192.168.100.25:8080/bbs/;
}

而如果使用了正则匹配,将是不允许的。如下。

server_name www.longshuai.com;
location ~ ^/forum/ {
    proxy_pass http://192.168.100.25:8080/bbs/;
}

只能修改转发路径使其不包含URI。如下。此时请求www.longshuai.com/forum/将转发到http://192.168.100.25:8080/forum/。

server_name www.longshuai.com;
location ~ ^/forum/ {
    proxy_pass http://192.168.100.25:8080/;
}

1.4.3 proxy_set_header

可以在nginx配置文件中的http段、server段或location段设置proxy_set_header指令。设置该指令后,传输给上游服务器的报文首部将包含相关的信息,如设置客户端的真实IP地址。设置如下:

server {
    listen       80;
    server_name  www.longshuai.com;
    location ~ \.(png|jpg|jpeg|bmp|gif)$ {
        proxy_pass http://192.168.100.28:80;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
    location / {
        proxy_pass http://192.168.100.30:80/;
        proxy_set_header X-Forwarded-For $remote_addr;

    }
    location ~ \.(php|php5)$ {
        proxy_pass http://192.168.100.25:80;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
}

仅在代理服务器上设置了该头部字段后还不够,因为后端服务器仅仅只是获取到它,默认并没有将其记录下来。所以需要在后端的服务的日志格式设置上记录此项或者在其他有需求的地方设置它。nginx的日志格式和apache的日志设置格式不同,以下分别是两种web程序的设置方法:

# nginx上的日志设置格式
log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
              '$status $body_bytes_sent "$http_referer" '
              '"$http_user_agent" "$http_x_forwarded_for" ';
access_log logs/access.log main;

# apache上的日志设置格式
LogFormat "%{X-Forwarded-For}i %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

以下是nginx日志上记录的信息。

[root@xuexi nginx]# tail -1 logs/access.log 
192.168.100.29 - - [26/Apr/2017:14:35:30 +0800] "GET /index.php HTTP/1.0" 200 76990 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36" "192.168.100.1"

以下是apache日志中记录的信息。

[root@xuexi ~]# tail -1 /etc/httpd/logs/access_log
192.168.100.1 192.168.100.29 - - [26/Apr/2017:14:32:52 +0800] "GET /a.png HTTP/1.0" 200 2653 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36"

1.5 ngx_http_upstream_module模块

upstream模块定义上游服务器组。主要的指令有"upstream"、"server"、"ip_hash"。upstream指令必须定义在server段外面。

以下是一个综合示例定义方法,并非正确,只是放在一起方便比较用法。

upstream backend {
    server 192.168.100.25;
    server 192.168.100.26:80;
    server www.longshuai.com;
    server www.xiaofang.com:8080;
    server 192.168.100.27 weight=2 max_fails=2 fail_timeout=2s;
    server 192.168.100.28 down;
    server 192.168.100.29 backup;
    ip_hash;   # 定义此项后,前面的server附加项weight和backup功能失效。
}

默认使用加权均衡算法,使用ip_hash指令可设置为ip_hash算法,但使用ip_hash指令后,如果server指令中使用了weight和backup选项,这两个功能将会失效。

其中server指令后可以跟的附加选项有:

  • weight:定义该后端服务器在组中的权重,默认为1,权重越大,代理转发到此服务器次数越多。
  • max_fails和fail_timeout:定义代理服务器联系后端服务器的失败重联系次数和失败后重试等待时间。默认为1和10,如果设置为2和3,则表示只尝试联系2次,每次失败等待3秒后进行下一次重试,也就是说6秒后就能判定此后端服务器无法联系上,将负载均衡到下一台服务器上。常会配合proxy_next_upstream或者fastcgi_next_upstream或者memcached_next_upstream来指定失败时如何处理。
  • down:将此后端服务器定义为离线状态。此功能不同于直接在配置文件中删除此服务器配置或注释它。常用于ip_hash均衡算法。当使用ip_hash算法时,如果某台服务器坏了需要将其从服务器组中排除,直接从配置文件中直接删除该服务器将会导致此前分配到此后端服务器的客户端重新计算IP_HASH值,而使用down则不会。
  • backup:指定当其他非backup的server都联系不上时,将返回backup所定义的服务器内容。常用于显示sorry page。

当server指定后端服务器时使用的是主机名的方式时,需要在代理服务器上添加域名解析记录,如放到/etc/hosts中。

以下是一个配置示例。只定义了一个upstream组,所有请求都代理,权重为2比1,当192.168.100.28和192.168.100.30都断开联系时,将返回代理服务器本身配置的sorrypage。

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    upstream web_group {
        server 192.168.100.28 weight=2 max_fails=2 fail_timeout=2s;
        server 192.168.100.30 weight=1 max_fails=2 fail_timeout=2s;
        server 127.0.0.1:80 backup;
    }
    server {
        listen 127.0.0.1:80;
        root /www/longshuai/;
        index index.html;
    }
    server {
        listen       80;
        server_name  www.longshuai.com;
        location / {
                proxy_pass http://web_group;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
                root   html;
        }
    }
}

然后在反向代理服务器上创建/www/longshuai/目录,并向目录下的index.html文件中写入"sorry...."。

mkdir -p /www/longshuai/
echo "<h1>sorry pages...</h1>" >/www/longshuai/index.html

重载代理服务器。在浏览器上输入www.longshuai.com并不断刷新,结果应该是2:1的返回权重。再依次测试停掉某一台后端服务器和两台后端都停掉的情况。

1.6 反向代理的各种情况

反向代理时,可以根据uri的后缀来代理,也可以根据uri中的目录来代理,还可以根据客户端浏览器类型来代理。例如手机访问网站时转发到某个后端服务器组,IE浏览器访问的转发到某一个后端服务器组等。

# uri后缀代理。
location ~ \.(jpeg|jpg|png|bmp|gif)$ {
    proxy_pass ...
}

# 目录代理。
location ~ /forum/ {
    proxy_pass ...
}

# 浏览器类型代理。
location / {
    if ($http_user_agent ~ "MSIE") {
        proxy_pass...
    }
    if ($http_user_agent ~ "Chrome") {
        proxy_pass...
    }
}

1.7 nginx代理memcached

该模块可以从将请求代理至memcached server上,并立即从server上获取响应数据。例如:

location / {
                set $memcached_key "$uri?$args";
                memcached_pass     127.0.0.1:11211;
}

nginx代理memcached时,需要以变量$memcached_key作为key去访问memcached server上的数据。例如此处将$uri$args变量赋值给$memcached_key变量作为key去访问memcached服务器上对应的数据。

但这样的代理显然不符合真正的需求:没有实现memcached的分布式功能。当memcached server宕机时,nginx将无法从中获取任何数据。所以应该使用上游服务器组。例如:

upstream memcached {
    server 127.0.0.1:11211;
    server 127.0.0.1:11212;
    server 127.0.0.1:11213;
    server 127.0.0.1:11214;
}

server {
    listen       80;
    server_name  dev.hwtrip.com;

    location ^~ /cache/ {
        set            $memcached_key "$uri$args";
        memcached_pass memcached;

但这也不适合,因为memcached是基于一致性哈希算法的,而upstream模块默认并不支持一致性哈希算法。可以通过upstream模块的hash指令或者另外使用一个第三方模块ngx_http_upstream_consistent_hash

如果使用的是upstream模块的hash指令,配置如下:

upstream memcached {
    hash "$uri$args" consistent;
    server 127.0.0.1:11211;
    server 127.0.0.1:11212;
    server 127.0.0.1:11213;
    server 127.0.0.1:11214;
}

这样,各上游主机就通过hash一致性的算法进行负载均衡。

如果使用的是第三方模块ngx_http_upstream_consistent_hash,则在模块添加成功后如下配置upstream组:

upstream memcached {
    consistent_hash consistent;
    server 127.0.0.1:11211;
    server 127.0.0.1:11212;
    server 127.0.0.1:11213;
    server 127.0.0.1:11214;
}

2 nginx自带的缓存功能

nginx的ngx_http_proxy_module自带了缓存功能。有几种缓存:网页内容缓存,日志缓存,打开文件缓存,fastcgi缓存。fastcgi缓存功能应慎用,因为动态程序的前后逻辑可能改变了,缓存后的结果可能并非实际所需结果。

在说明nginx自带的缓存功能之前,需说明其缺陷。nginx的缓存功能主要用于缓存体积较小的页面资源,当数据较大时很容易出现瓶颈。在页面资源的缓存功能上,它属于业余玩家。而squid是科班出身,功能最全面,可以缓存大量数据,但架构太老,性能一般。varnish则是此类缓存的新贵,架构较新,内存管理完全交由操作系统内核,性能是squid的几倍之强,但缓存的内容不足squid

本节所讲的主要是nginx自带缓存的网页内容缓存。当实现网页内容缓存时,作为web服务程序,它可以缓存自身返回给客户端的数据,包括读取的图片、文件等;作为代理,它可以缓存来自后端的数据缓存后的数据在内存中有,也会放在设定的目录下。这样以后客户端继续请求相同资源时,可以直接从内存中或者自身的磁盘中获取并返回给客户端。当缓存超出指定的空间大小时,将会有一个专门的线程cache_manager来清理缓存。

定义的相关指令主要有3个:proxy_cache_path、proxy_cache、proxy_cache_valid。

  • proxy_cache_path:它的语法比较复杂,但用起来很简单。
    proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=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];
    

其中proxy_cache_path path [levels=levels] keys_zone=name:size [max_size=size]这几项是一般使用的选项和必需项。以下为一示例。

proxy_cache_path /usr/local/nginx/cache_dir levels=1:2 keys_zone=cache_one:20m max_size=1g;

其中:

  1. path:定义缓存放在磁盘的哪个目录下。此处表示定义在/usr/local/nginx/cache_dir目录下。目录不存在会自动创建。
  2. levels:定义缓存目录的级别,同时定义缓存目录名称的字符数。例如levels=1:2:2表示3级目录,且第一级目录名1个字符,第二级目录2个字符,第三级目录2个字符。目录最多3级,目录名最多为2个字符。例如上例中"levels=1:2"产生的缓存文件路径可能是这样的"/usr/local/nginx/cache_dir/d/f1/50a3269acaa7774c02d4da0968124f1d",注意其中加粗的字体。
  3. keys_zone:定义缓存标识名称和内存中缓存的最大空间。name部分必须唯一,在后面会引用name来表示使用该缓存方法。
  4. max_size:定义磁盘中缓存目录的最大空间。即path定义的文件最大空间。

该指令定义后只是定义了一种缓存方法,并非开启了缓存。

  • proxy_cache:定义要使用哪个缓存方法。使用proxy_cache_path中的name来引用。

例如引用上例定义的cache_one。

proxy_cache cache_one;
  • proxy_cache_valid:根据状态码来指定缓存有效期。

例如,下面的表示状态码为200和302的状态缓存1小时,状态码为404时即page not found的缓存只有1分钟,防止客户端请求一直错误,状态码为其他的则缓存5分钟。

proxy_cache_valid 200 302 1h;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5m;

如果不指定状态码,只指定时间,则默认只缓存状态码200、301、302各5分钟,其他的状态码不缓存。

以下是代理服务器192.168.100.29上定义的缓存示例。

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

        upstream web_group {
                server 192.168.100.28 weight=2 max_fails=2 fail_timeout=2s;
                server 192.168.100.30 weight=1 max_fails=2 fail_timeout=2s;
                server 127.0.0.1:80 backup;
        }
        proxy_cache_path /usr/local/nginx/cache_dir levels=1:2 keys_zone=cache_one:20m max_size=1g;
        server {
                listen 127.0.0.1:80;
                root /www/longshuai/;
                index index.html;
        }
        server {
                listen       80;
                server_name  www.longshuai.com;
              # 在响应报文中添加缓存首部字段
                add_header X-Cache "$upstream_cache_status from $server_addr";
                location / {
                        proxy_pass http://web_group;
                        proxy_cache cache_one;
                        proxy_cache_valid 200 1h;
                        proxy_cache_valid 404 1m;
                        proxy_cache_valid any 5m;
                }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

其中添加的一行"add_header X-Cache "$upstream_cache_status from $server_addr";"表示在响应报文的头部加上一字段X-Cache,其值为是否命中缓存的状态($upstream_cache_status),从哪台服务器上($server_addr)取得的缓存。 重载代理服务器的配置文件后,在客户端打开"开发者工具"进行测试。

由于是第一次提供缓存功能,所以结果是未命中缓存。此时已经将缓存保存下来了。 再进行测试,结果将命中缓存是"hit from 192.168.100.29"。

查看缓存目录。

[root@xuexi nginx]# tree /usr/local/nginx/cache_dir/  
/usr/local/nginx/cache_dir/
├── 3
│   └── 26
│       └── 3abcc5796b407cf3db2716539d256263
└── d
    └── f1
        └── 3b37290aabefe7369a4680875f763f1d

如果想要删除缓存,只需删除对应的目录即可。

posted @ 2017-10-17 23:56  骏马金龙  阅读(11967)  评论(6编辑  收藏  举报