转载

nginx之upstream模块缓存系统详解

一般情况下,前端使用nginx做代理或7层负载并向后实现varish/squid做cache server的效果要好的多

nginx与squid做缓存比较,nginx比squid有着巨大的优势表现在:

nginx是异步

假如后端的web服务器,当用户的请求到达nginx的时候,nginx收到请求而不是立即将请求转发至web server; 
如果用户请求比较大,nginx将其在本地缓存,内存中不够用则在磁盘中缓存,当缓存完毕之后,再将请求一次性提交至后端web服务器,转发完成之后再向客户端响应也就意味着用户的连接只需要跟nginx建立连接即可,nginx与后端web一般都在内网中对接,只要带宽满足,很可能瞬间完成,因此来说对于后端服务器的压力及小,只需要建立几秒的连接就可以处理完成非常大的请求。

squid是同步

当用户请求到达squid中,在刚接收到用户请求的第一个报文立即与后端建立连接,因此在处理过程中,依旧保持着连接。 
所以说,nginx最大的优势就是在用户的连接处理的场景中,这样就使前端有大量的用户请求连接,但是在后端看上去只有少数部分,比如前端有1W并发进来,后端大概只能看到其10分之1 ,需要查找数据库的只有少数部分, 所以后端的web压力会小,但是前端分发器的压力会很大

nginx缓存机制

nginx要想实现反向代理那么就需要使用proxy_cache模块,以及配合其指令和参数,可以将用户的请求从上游服务器获取之后先存在本地磁盘。



缓存通常是键值对方式存储:

键 : 请求的rul
值 : 后端服务器响应的内容
所以当后续用户的请求到达之后,如果本地缓存服务器中存在的话则直接封装报文返回至用户;
但是如果服务器运行了很久,已保存N久的缓存数据,那在某一时刻后端服务器出现故障,nginx缓存已经无法找到其后端的服务器,那么缓存上游服务器对象是否还应该返回至用户;
这些都是可配置的,我们定义缓存的时候,像这些缓存 是否可缓存,缓存的位置都要自己去配置的,并且一旦服务器故障,缓存的数据还能否直接响应客户端等等

事实上用户所在浏览器上保存的缓存称为私有缓存,而服务器上缓存的数据叫做公共缓存


有些数据只能在私有缓存中进行缓存,比如:
用户登录网站的用户名和密码的信息,这类肯定不能在服务器上缓存;
而用户的cookie信息一般也不能缓存,所以缓存服务器公共缓存服务器只要发现用户请求中有cookie则不缓存,但是一般电商站点为了追中用户的行为规则,则将每个请求数都加cookie,但对于图片这种静态内容附加cookie是没有意义的。
所以对于缓存服务器来说必须处理这种机制,对于没必要加cookie的而用户已加cookie将其删掉并让缓存命中等,因此需要一系列缓存机制,这都是需要自己去定义的,比如缓存多久 否定缓存多久 重定向缓存多久 ...


配置Nginx缓存

参考:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path

语法:
proxy_cache_path path [levels=levels] keys_zone=name:size [inactive=time] [max_size=size] [loader_files=number] [loader_sleep=time] [loader_threshold=time];
配置格式:
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=one:10m;

#定义/data/nginx/cache为缓存目录;

levels=1:2 有2级子目录,最多为3级子目录,用冒号隔开定义3个数字,每个数字表示其目录名称;

keys_zone=one:10m 用多大的空间保存键值;

以下为缓存对象的名字,方便引用并且避免名称冲突
file names in a cache will look like this:
/data/nginx/cache/c/29/b7f54b2df7773722d382f4809d65029c

使用示例

定义缓存必须在全局配置上下文中去定义

http {
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:10m
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:10m
inactive=24h max_size=1g;
}

proxy_cache_path必须定义在全局配置中,定义完之后可以在各location中来引用

在location中来使用proxy_cache来指定是否使用缓存,也可以定义多个缓存在location中引用不同的缓存定义

server {
location / {
proxy_pass http://www.test.com;
proxy_set_header Host $host;
proxy_cache STATIC;
proxy_cache_valid 200 1d; #请求返回值为200的则缓存1day
proxy_cache_valid 301 302 10m; #请求返回值为301 302的则缓存10m
proxy_cache_vaild any 1m; #其他任何返回值缓存1m

#是否可以使用过期对象
proxy_cache_use_stale error timeout invalid_header updating
http_500 http_502 http_503 http_504;
}
}
}

实现缓存机制

步骤:

(1)定义缓存必须在http全局中定义

(2)而后在location中实现反向代理才有必要实现缓存

创建缓存目录

[root@node1 ~]# mkdir -p /data/cache/nginx/

编辑nginx配置文件

[root@node1 nginx]# vim nginx.conf

在http { }上下文中定义缓存

proxy_cache_path /data/cache/nginx/  levels=1:2 keys_zone=one:10m  max_size=1g;

定义/data/cache/nginx/为缓存目录;

levels=1:2 有2级子目录,最多为3级子目录,用冒号隔开定义3个数字,每个数字表示其目录名称;

keys_zone=one:10m 用多大的空间保存键值,最大为10M;

max_size=1g 表示缓存空间最大有1G;

在location中定义缓存规则并调用proy_cache_path
location / {
proxy_pass http://10.0.10.83; #将请求都转发至10.0.10.83上去
proxy_cache one; #明确说明使用名称one这个cache

#以下为缓存规则
proxy_cache_valid 200 1h;
proxy_cache_valid 302 10m;
proxy_cache_valid any 1m;
}


保存退出检查语法并重新加载

[root@node1 nginx]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@node1 nginx]# nginx -s reload

查看缓存目录

[root@node1 nginx]# ll -th /data/cache/nginx/
total 0

看到目录中是空的,那么我们来使用curl命令访问一下看其是否能生成缓存文件

[root@node1 nginx]# curl 10.0.10.61
<h1>10.0.10.83</h1>

再次查看路径

[root@node1 nginx]# ll -th /data/cache/nginx/f/63/
total 4.0K
-rw-------. 1 nginx nginx 350 May 9 16:06 681ad4c77694b65d61c9985553a2763f

这时候目录中已经存在缓存信息,说明缓存已经生效,但是在某些场合,我们不能保证其已经被命中

那么再将upstream模块开启,因为upstream模块会给我们引入一些新的服务器变量,编辑如下:

upstream webservers {   #定义名称
server 10.0.10.83;
#server 10.0.10.61; #将之前定义的注释
}

再定义location

location / {
proxy_pass http://webservers; #引用upstream名称
proxy_cache one; #引用定义的缓存模块名称
proxy_cache_valid 200 1h;
proxy_cache_valid 302 10m;
proxy_cache_valid any 1m;
}

这样表示我们已经启用了upstream模块了而upstream模块会给我们引入一些新的服务器变量进来

比如cache_status,如果我们访问某个缓存页面的时候到底是否命中会通过这个变量保存下来,如果将其传递给客户端,那么我们就知道是否命中了

再将其加入首部header

location / {
#root /web/htdocs/;
#index index.php index.html index.htm;
proxy_pass http://webservers;
proxy_cache one;
proxy_cache_valid 200 1h;
proxy_cache_valid 302 10m;
proxy_cache_valid any 1m;
add_header X-Via $server_addr; #定义这个header名为X-Via 通过变量$server_addr明确说明从哪个服务器来响应的 server_addr
add_header X-Cache-Status $upstream_cache_status; #明确说明是否命中 $upstream_cache_status为upstream模块
}

保存退出并检测语法

[root@node1 nginx]# /usr/local/nginx/sbin/nginx –t
[root@node1 nginx]# /usr/local/nginx/sbin/nginx –s reload

重新reload之后刷新测试,这里使用的是Google Chrome浏览器

nginx之upstream模块缓存系统详解

如上所示,提示已命中,X-Via是我们自定义的header标签

也可以使用curl -I来查

[root@node1 nginx]# curl -I http://10.0.10.61
HTTP/1.1 200 OK
Server: nginx/1.4.2
Date: Fri, 09 May 2014 08:22:41 GMT
Content-Type: text/html
Content-Length: 20
Connection: keep-alive
Last-Modified: Mon, 12 Aug 2013 10:48:13 GMT
ETag: "fd91-14-4e3bddc687540"
X-Via: 10.0.10.61
X-Cache-Status: HIT
Accept-Ranges: bytes

启动压缩功能

nginx将响应报文发送至客户端之前可以启用压缩功能,这能够有效地节约带宽,并提高响应至客户端的速度。通常编译nginx默认会附带gzip压缩的功能,因此,可以直接启用之。

http {
gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript application/json;
gzip_disable msie6; #如果对方是ie6的话,则不再使用压缩功能,,因为ie6浏览器不支持压缩
}

gzip_proxied指令可以定义对客户端请求哪类对象启用压缩功能,如“expired”表示对由于使用了expire首部定义而无法缓存的对象启用压缩功能,其它可接受的值还有“no-cache”、“no-store”、“private”、“no_last_modified”、“no_etag”和“auth”等,而“off”则表示关闭压缩功能

重新加载配置文件并访问

nginx之upstream模块缓存系统详解

upstream模块的使用

upstream模块常用的指令有:

  • ip_hash: 基于客户端IP地址完成请求的分发,它可以保证来自于同一个客户端的请求始终被转发至同一个上游服务器,与lvs的机制是一样的;
  • keepalive:每个worker进程为发送到upstream服务器的连接所缓存的个数;转发至服务器之后能否使用长连接,也尽可能避免三次握手与四次断开的次数,如果参数过大的话,会无形之中对后端服务器产生很大的压力,因此建议开启但不要太大
  • least_conn:最少连接调度算法;类似于lvs的wlc算法的效果,因此一般来尽量避免和ip_hash一起用
  • server:定义一个upstream服务器的地址,还可包括一系列可选参数,如:
  • weight:权重;
  • max_fails:最大失败连接次数,失败连接的超时时长由fail_timeout指定;
  • fail_timeout:等待请求的目标服务器发送响应的时长;
  • backup:用于fallback的目的,所有服务均故障时才启动此服务器;
  • down:手动标记其不再处理任何请求;

示例:

upstream backend {
server www.magedu.com weight=5;
server www2.magedu.com:8080 max_fails=3 fail_timeout=30s;
}

upstream模块也能为非http类的应用实现负载均衡,如下面的示例定义了nginx为memcached服务实现负载均衡

upstream memcachesrvs {      #明确定义了一组实现负载均衡的服务器
server 172.16.100.6:11211; #而这组服务器向后分发的端口都是11211 ,是memcache的服务器端口
server 172.16.100.7:11211;
}
server {
location / {
set $memcached_key "$uri?$args"; #向后端memcache查询的时候,查询键是$uri?和$args组合起来的值
memcached_pass memcachesrvs; #通过memcachepass传递至memcachesrvs组服务器
error_page 404 = @fallback; #如果没有命中则则发送至其上游的其他服务器
}
location @fallback {
proxy_pass http://127.0.0.1:8080;
}
}

这样一来,找缓存的时候先在memcached里查找,如果不存在则再去找真实服务器,这样将nginx将memcached结合在了一起,将数据直接缓存在memcached内存当中,而不是nginx自己的缓存当中

实现后端服务器健康状态检查,使其一旦出现故障不再将其加进来,定义upstream:

upstream webservers {
server 10.0.10.83 weight=1 max_fails=3 fail_timeout=2s; #最大允许3次失败,如果超过2秒则算超时
server 10.0.10.61 max_fails=3 fail_timeout=1s backup; #backup表示其主机始终不会生效除非组内所有主机全部故障
}

一般如果条件允许的情况下,本机也启动一个web服务器,但是这个服务器不是专门提供工作的,一旦后端服务器出现故障,那么则转至backup服务器,使其服务器专门为用户提供错误页面

proxy_cache通常也只能缓存本地服务器向后端服务器取得数据而进行缓存的,而后端通常都是静态服务器,如果基于fastcgi的方式获取数据的,而有希望对动态内容作缓存,那么就要涉及到fastcgi的自身缓存功能了

实现动态内容缓存

通过FastCGI协议取得的内容页可以缓存,但是时长需要自定义好

使用时将fastcgi_cache模块启用并定义参数即可,之前我们搭建了一套lnmp 这时我们要对fastcgi进行缓存

定义缓存目录
[root@node1 nginx]# mkdir -p /data/cache/fastcgi
编辑配置文件,在http{}中加入以下参数:
fastcgi_cache_path /data/cache/fastcgi levels=1:2:1 keys_zone=fcgi:20m  max_size=1g;

定义/data/cache/fastcgi为fastcgi缓存目录;

缓存目录分别为3级子目录;

Fcgi缓存大小为20M;

最大缓存为1G的空间;

而后location中启用fastcgi

location ~ /.php$ {
root /web/htdocs/;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi_params;
}
以上参数为nginx默认配置,将以上参数启用之后,我们还需要对其加入一些参数:
location ~ /.php$ {
root /web/htdocs/;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi_params;
fastcgi_cache fcgi;
fastcgi_cache_valid 200 10m ;
fastcgi_cache_valid 301 2m ;
fastcgi_cache_valid any 1m ;
}
保存退出并检查语法
[root@node1 nginx]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
重新加载配置文件
[root@node1 nginx]# nginx -s reload

这时我们再去请求动态内容

[root@mode ~]# curl http://10.0.10.61/index.php

再来查看是否生产缓存

[root@node1 ~]# ll /data/cache/fastcgi/e/27/4/
total 48
-rw-------. 1 nginx nginx 45873 May 10 12:55 d41d8cd98f00b204e9800998ecf8427e

使用ab压力测试其效果是否明显

首先将fastcgi缓存功能关闭
location ~ /.php$ {
root /web/htdocs;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi_params;
#将以下参数注释
#fastcgi_cache fcgi;
#fastcgi_cache_valid 200 10m ;
#fastcgi_cache_valid 301 2m ;
#fastcgi_cache_valid any 1m ;
}

重新加载配置文件

[root@node1 nginx]# nginx -s reload

使用ab命令对其进行压力测试

[root@mode ~]# ab -c 100 -n 2000 http://10.0.10.61/index.php
我们只关心Requests值 所以略过一部分信息
#···········略··············
Document Path: /index.php
Document Length: 45234 bytes

Concurrency Level: 100
Time taken for tests: 4.343977 seconds
Complete requests: 2000
Failed requests: 228
(Connect: 0, Length: 228, Exceptions: 0)
Write errors: 0
Total transferred: 90761752 bytes
HTML transferred: 90467752 bytes
Requests per second: 460.41 [#/sec] (mean)
Time per request: 217.199 [ms] (mean)
Time per request: 2.172 [ms] (mean, across all concurrent requests)
Transfer rate: 20403.88 [Kbytes/sec] received
#···········略··············
将缓存模块开启再次对其进行压力测试
得出数据:
Concurrency Level: 100
Time taken for tests: 1.576516 seconds
Complete requests: 2000
Failed requests: 0
Write errors: 0
Total transferred: 90896000 bytes
HTML transferred: 90602000 bytes
Requests per second: 1268.62 [#/sec] (mean)
Time per request: 78.826 [ms] (mean)
Time per request: 0.788 [ms] (mean, across all concurrent requests)
Transfer rate: 56304.53 [Kbytes/sec] received

正文到此结束
Loading...