基于nginx实现7层负载均衡和反向代理
当让内网用户通过一个有外网地址的网关访问互联网时,内网主机的网关都指向其中有外网的主机的内网地址,一般在网关处添加一个snat实现,内网所有客户端只要访问外网,经由网关时发送时都将原地址改为这个网关的外网IP地址;从而实现内网用户访问互联网,这种转发内核级完成,没有用户参与;
还有第二种方式实现内网用户访问外网:
正向代理
让内网用户的网关可指向一个有外网地址的网关访问互联网时,这个网关主机并没有开启snat机制,仅安装了一个web服务的守护进程,但并不提供web服务,仅接收内网用户访问外网的请求;由于它工作在应用层,会拆封用户的报文,从而根据里面的url、host等等,来判断出用户到底访问的哪个主机,根据用户请求的资源,判断是否允许用户访问,可以在里面做拒绝访问控制,例如访问的是MP3而不是html网页就拒绝;因为可清楚的知道访问的是什么资源了;
如果允许内网用户访问,自己要当做客户端,向后端真正提供服务的主机发起请求,web服务将响应给这个代理主机,代理主机再封装响应报文给内网用户;
通常一个web代理服务器,只代理80、443等相关协议;因此,第一,它的功能能力有限;第二,用户要在客户端程序上指明代理服务器的地址和端口;这种机制就叫正向代理;
为了提高用户的性能,通常在代理服务器上设置缓存,用户访问时,先检查本地是否有缓存条目,如果有直接用缓存的结构响应给客户端,这样性能会好很多,如果缓存放在固态硬盘或内存中效果还会更好;
缓存中的任何缓存项在缓存时是有有效期的,有效期限是在原始服务器上定义的,每一次在缓存时都会告诉客户端这个资源可以缓存且能缓存多长时间;这样用户访问页面有可能访问的结果都是来自于缓存的,尤其是静态内容;
如果不想用缓存内容,就想访问原始服务器的内容,则要在浏览器上使用shift+F5,强行刷新,表示在代理服务器发不要缓存内容就要原始服务器内容;
如果代理服务器联系不上原始服务器,但是本地缓存已经过期,是否使用该过期的缓存项响应给客户端,这取决于管理员的设置;
另外,缓存是有多级的,浏览器自己的缓存叫私有缓存,而代理服务器上的缓存叫公共缓存,很多数据私有缓存可缓存,但共有缓存不可缓存;例如,个人邮箱的内容;
反向代理
正向代理是帮助用户访问互联网的任何服务器,是代表的用户或代表的是请求者的身份访问互联网的任何服务器;
反向代理代表的是被请求者的身份,而且通常指代表被请求者的有限个被请求者;通常工作在某个服务器的前端,目的是自己工作在互联网上著名端口上,只要有请求发来,自己代表了某网站的服务器,为了能够保证后端服务器安全,不让用户直接访问后端服务器;同dnat一样,也是工作内核中的,能够用于隐藏后端服务器,但是,不能检查应用层,客户端请求的是什么样的资源,如果要做更精确的控制,使用应用层的代理服务器,在完成应用层分析之后向后端进行代理,才是更为安全的做法;
既然工作在应用层,像iptabes基于dnat也可以在forward链上添加filter规则实现过滤,代理服务器反向代理也可以;跟正向代理一样甚至可以做到,根据客户端请求的资源类型进行控制;根据请求文件的url后缀名就能判断请求的是什么样的资源,这在iptabes上是很难做到的;
反向代理也是工作在应用层的一个应用程序,这个程序文件应监听在某个套接字上才能接收网络上的客户端的请求,这样必须要基于某种并发响应模型,才能响应并发响用户的请求;响应模型是什么取决于程序的并发响应机制;有可能是httpd的prefork或worker,也可能是nginx的master/worker模型响应的epoll机制;
不管使用什么机制,必须要基于套接字进行响应,就意味着:
- 第一,每一个连接都要维护一个套接字文件;
- 第二,由于代表了服务器在工作,每个用户请求到来,如果本地没有缓存,就要向后端服务器发请求,此时,扮演为客户端的角色,既然是客户端,同样,每个客户端也都需要打开一个套接字,然后连接服务器端;如果代理服务器收到并发1万个请求,这1万个请求,都要把自己扮成客户端到后端服务器请求资源,也就爱意味着要打开1万个打开,向后端服务器去取内容的;
实际在Linux可用的端口只有2万个而已,这样,代理服务器的并发响应能力受限于可用端口数;
如果要工作在内核级,比如lvs完全不需要端口,所以lvs能轻易突破端口数的限制;但是,这种反代服务器是突破不了的;对于前端正向代理只需一个80端口即可,而后端的反向代理则不能完成;
这就是反向代理服务器,自己首先是个进程,能分析用户请求资源是什么,分析完成之后把自己扮演为客户端向后端转发;
所面临的报文类型有两种:
- 第一种类型,是自己作为服务器时收到的报文,源ip为CIP,目标ip为VIP;
- 第二种类型,向后端进行代理时是自己做为客户端,此时收到的报文,源ip为RIP,目标ip为DIP;后端服务器是把报文响应给代理服务器的,代理服务器收到后,查看里面的内容封装响应报文再响应给客户端;
从这个角度,代理服务器比dnat性能要差很多,因为,它要拆服务器响应报文的所有封装,还有再重新封装起来;而对于dnat只需拆到ip首部即可,如果不做端口映射只把原ip目标ip改变就可,而应用层首部不会识别也不会改变里面的内容;反代则是必须把应用层首部拆封了再封装,多了几个步骤,所以从性能角度要差很多,姑且不论端口数量是否够用,但带来的好处是,由于它所探查的深度远远大于dnat功能,可以控制机制就要强大和灵活很多;于是,在并发要求不是特别高的场景中,反代服务器还是有这存在意义的;
为了给反代提速,也跟正向代理一样,可以加缓存;把从后端服务器取得的内容缓存在本地,缓存时间受限于缓存策略,缓存中条目的管理也需要管理接口进行控制;
有缓存时,反代服务器就不用每次向后端服务器取资源,而是直接查本地缓存,如果有直接从缓存中取出结果响应给客户端,从而给上游服务减轻了访问压力;
假如有1万个请求,其中有7千个是命中在缓存中,只有3千个是从后端服务器读取的,大大减小了服务器压力;
所以,在这种场景中,可以做到前端主机是nginx,而后端主机是http,相比较http所提供的功能还是比nginx多,nginx特性在于轻量但特性就少很多;有些应用程序是依赖于httpd的特性的;但可以在httpd前面加个nginx反代,把从httpd取得的结果缓存下来,充分发挥nginx的优势;
在应用场景中可做lnamp建构,动态内容发给httpd,httpd也是启动也是启动一个php模块来php请求处理,静态内容可以在nginx反代上完成;实现了动态内容、静态内容分开的效果;而且反代不是fastcgi协议,而是http协议;
既然nginx能分析用户请求的资源类型,nginx中的location可匹配用户请求的url类型的后缀名;例如在代理时,写一个location ~匹配.php结尾的都发给指定的服务器,是其它内容就发给另一服务器,这种就叫动静分离;而这种分离的方式就是通过请求资源的类型后缀名来实现的;
对nginx来讲做动静分离,很轻易结合location+反代功能来实现;
静态服务器可为一个组,动态服务器可为一个组,做成两组不同的服务器,这样达到的效果是:对于动态服务器才需要做会话保持,静态服务器就可直接轮询了;
两组服务器还可做负载均衡,负载均衡时还可根据不同的调度算法进行,更重要的是不同内容可缓存性不一样;很多动态内容不能缓存,但绝大多数都能缓存;因此,在静态内容前端,添加一个专门的缓存服务器,而动态内容如果不需要缓存可不添加缓存服务器;
对于静态内容,用户期望无论被负载均衡的哪个节点上,所访问的内容都应该是一样的,但上传内容时,需要动态实现的,因为动静分开,所以要找一组分布式存储;
上传时通过动态服务器上的应用程序执行上传操作,传完以后url固定,用户访问时可通过静态服务器加载后进行访问,而且无论通过那个前端服务器加载,都是通过公共存储进行加载的保证访问的是同样的数据;
nginx的反代,可以反代一个主机,也可负载均衡到多个主机上来,但是,这是两种不同的机制;反代只需反代模块就能工作,但反代到多个主机上这需要借助于另一模块,称为负载均衡模块或叫upstream模块来实现;
nginx在做高可用反代负载均衡时,用一个专门模块需要将多个服务器在前端服务器定义成一个逻辑组,在组内定义了逻辑算法,而后反代时不是反代在主机上,而是反代在这个组上,而这个逻辑组就可理解为专门用于实现负载均衡的模块,对nginx这个模块就叫upstream模块;
访问网页时会遇到502错误(bad gateway)原因
既然是代理服务器,整个过程是分成两段的,第一段从客户端到代理服务器,第二段从代理服务器到后端服务器,如果后端主机非常繁忙,在有限时间内,代理服务器没能从后端服务器取得资源,客户端就等待超时(例如3秒钟),代理服务器在3秒钟内没取得资源就告诉客户端,代理被拒绝;是这样原因导致的502;
bad gateway的更多原因并不是因为让客户端少等时间,而是代理服务器等待后端服务器的超时时间太短导致;
ngx_http_proxy_module
用于反代用户请求到后端主机,同时又支持缓存功能;工作方式同fastcgi使用fastcgi_pass指令一样;
1、proxy_pass URL;
- 可实现动静分离
- 能够实现将请求发给后端URL指定的主机地址,这里之所以使用URL可以完成URL映射;例如前端的url为bbs,后端url可以是forum;
- 应用在location, if in location, limit_except的上下文;
例如:
proxy_pass http://localhost:8000/uri/;
这个uri可以不带,如果后面没有url时,proxy_pass会将location的url传递给后端主机;
proxy_pass后面的路径不带uri时,其会将location的uri传递给后端主机;
server {
...
server_name HOSTNAME;
location /uri/ {
proxy http://hos[:port];
}
...
}
前端访问是:http://www.magedu.com/bbs --> 转给真正访问后端时:http://172.18.11.111/bbs
注意:HOST决不能带/;
如果是proxy_pass http://HOST/;
则:前端访问是:http://www.magedu.com/bbs --> http://172.18.11.111/
proxy_pass后面的路径是一个uri时,其会将location的uri替换为proxy_pass后端主机的uri;
server {
...
server_name HOSTNAME;
location /uri/ {
proxy http://host/new_uri/;
}
...
}
前端访问是:http://HOST/uri --> 替换为后端主机:http://HOST/new_uri/
如果location定义其uri时使用了正则式表达模式匹配机制,或在if语句或limt_execept中使用proxy_pass指令,则proxy_pass后的路径必须不能使用uri;否则有语法错误;
server {
...
server_name HOSTNAME;
location ~|~* /uri/ {
proxy http://host;
}
...
}
http://HOSTNAME/uri/ --> http://host/uri/;
示例
后端RS1服务器内网:192.168.1.3
前端A服务器:
外网:172.18.11.111
内网:192.168.1.2
RS1启动httpd并提供页面:
[root@rs1 ~]# systemctl start httpd.serivce
[root@rs1 ~]# vim /var/www/html/index.html
<h1>RS1-192.168.1.3</h1>
在A主机安装nginx
[root@hosta ~]# rpm -ivh nginx-1.8.0-1.el7.ngx.x86_64.rpm
[root@hosta ~]# rpm -ql nginx
[root@hosta ~]# vim /etc/nginx/nginx.conf
其中:
include /etc/nginx/conf.d/*.conf;
表示每个*.conf定义了一个服务器;
只需在/etc/nginx/conf.d/default.conf
配置即可:
[root@hosta ~]# cp /etc/nginx/conf.d/default.conf{,.bak}
[root@hosta ~]# vim /etc/nginx/conf.d/default.conf
只需添加代理:
location / {
#root /usr/share/nginx/html;
proxy_pass http://192.168.1.3;
index index.html index.htm;
}
在浏览器访问:http://172.18.11.111
显示:RS1-192.168.1.3
表明已经把请求代理到后端的RS1上了;
在RS1上创建一bbs的页面:
[root@rs1 ~]# mkdir /var/www/html/bbs
[root@rs1 ~]# vim /var/www/html/bbs/index.html
<h1>BBS</h1>
在浏览器访问:http://172.18.11.111/bbs/
显示:BBS
表示直接映射到后端;其实,也可以只映射某一个url;
编辑location有url:
[root@hosta ~]# vim /etc/nginx/conf.d/default.conf
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /bbs/ {
proxy_pass http://192.168.1.3;
}
在浏览器访问:http://172.18.11.111
显示:nginx提供的欢迎页;
如果在浏览器访问:http://172.18.11.111/bbs
显示:BBS
[root@hosta ~]# vim /etc/nginx/conf.d/default.conf
location /bbs/ {
proxy_pass http://192.168.1.3/;
}
如果在浏览器访问:http://172.18.11.111/bbs
显示:RS1-172.18.11.8
完成路径映射;
[root@hosta ~]# vim /etc/nginx/conf.d/default.conf
location ~* \.(jpg|gif|png)$ {
proxy_pass http://192.168.1.3;
}
把jpg等结尾的资源都传到后端;
如果在浏览器访问:http://172.18.11.111/1.jpg
显示:图片在RS1的/var/www/html/1.jpg
如果定义php结尾的动态资源传给后端主机,其它资源发往另一主机,如果能匹配动静的资源就可实现动静分离;
演示动静分离:
开启一台RS2主机,可以只接把它配置成php-fpm,前端nginx代理时,就可使用fastcgi;
也可配置成http+php,前端nginx可通过基于http向后端代理;
以http+php,前端nginx可通过基于http向后端代理为模型演示;
RS2:192.168.1.4
安装http+php
[root@rs2 ~]# yum -y install php httpd
...
并提供php测试页:
[root@rs2]# vim /var/www/html/index.php
<h1>RS2-172.18.11.9</h1>
<?php
phpinfo();
?>
在前端DR1代理上配置:
只把php发给RS2
[root@hosta ~]# vim /etc/nginx/conf.d/default.conf
location / {
# root /usr/share/nginx/html;
proxy_pass http://192.168.1.3;
index index.html index.htm;
}
location ~* \.php$ {
proxy_pass http://192.168.1.4;
}
开启RS1和RS2的httpd服务
[root@rs1 ~]# systemctl start httpd.service
[root@rs2 ~]# systemctl start httpd.service
在浏览器访问:http://172.18.11.111/
显示:RS1-172.18.11.8
在浏览器访问:http://172.18.11.111/1.jpg
显示:显示图片
在浏览器访问:http://172.18.11.111/index.php
显示:RS2-172.18.11.9和php的测试页;
以上实现了nginx当做代理服务器,把http的静态资源和php的动态资源分离转发给后端不同的主机;
注:在做实际实验时,把php安装一个phpmyadmin有可能无法完成显示正常,因为有些静态页面是在动态页面基础上生成的;需要在前端进行url重写;
2、proxy_set_header field value;
设定向后端主机发送的请求报文的首部及其值;或是在原有首部后添加新值;
用在http, server, location上下文;
能抓取终端用户IP地址,field可自定义;默认为$proxy_host代理主机的IP地址;可以设定任何首部的值;
示例:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwared_for;
server {
listen 80;
server_name www.ilinux.io;
location /blog/blog.html {
#在代理服务器上将远程客户端的Ip设置为X-Real-IP
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://10.10.10.11/news/news.html;
}
}
#在后端服务器,设置access日志的格式,将原本的$remote_addr替换为$http_x_real_ip
log_format main '$http_x_real_ip - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
#在测试访问几次后,查看后端服务器的access_log文件即可查看到相应的访问客户端IP
表示如果X-Forwarded-For有首部值,会把$remote_addr的值补充在后面;为什么要补充,因为真正的代理费服务器是可以多级代理的;一个客户端可以有多级正向代理,反向代理也可有多级,这样在后端服务器日志记录的有可能不是最终用户的iP地址;所以像这种多级代理场景,每一个代理服务器都会附加一个新首部值,最终会知道最左侧的值是终端用户的iP地址;
在http的核心模块中引入了很多内建变量:
其中,$remote_addr代表客户端的IP地址;可把这个地址设置成请求报文的首部,nginx代理可把这个首部传递给后端服务器;从而实现后端服务器从中记录终端用户的IP地址到自己的日志记录中;
编辑在一个server中生效:
[root@hosta ~]# vim /etc/nginx/conf.d/default.conf
server {
...
proxy_set_header X-real-ip $remote_addr;
...
}
还要在RS1中更改,日记记录的格式:
[root@rs1 ~]# vim /etc/httpd/conf/httpd.conf
LogFormat "%{X-real-ip}i %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
在浏览器输入:http://172.18.11.111/
在RS1上查看httpd的日志信息:
[root@rs1 ~]# tail /var/log/httpd/access_log
可见原地址为win7系统物理主机的ip地址:172.18.11.1;
跟nginx缓存相关的选项(缓存要先定义,后调用)
3、proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size];
只能用于http上下文;
可选项:[loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time]
选项参数 | |
---|---|
keys_zone=name:size | 指明内存空间名称和大小 |
path | 磁盘上缓存的文件系统路径 |
levels=levels | 缓存目录层级,最多有3级,每级最多2个字符(目录) |
use_temp_path=on|off | 是否启用临时文件路径 |
inactive=time | 缓存中缓存项(k/v数据)的非活动时间;默认10分钟 |
max_size=size | 缓存文件系统路径的大小;一旦存满了基于LRU(最近最少使用)算法做清理 |
示例:proxy_cache_path /usr/local/nginx/proxy_cache_dir/cache1 levels=1:2 keys_zone=cache1:100m inactive=1d max_size=10g;
4、proxy_cache zone | off;
指明要调用的缓存,或关闭缓存机制;
配置段:http, server, location
示例:
server {
...
server_name HOSTNAME;
location /uri/ {
proxy http://hos[:port];
proxy_cache cache1;
}
...
}
5、proxy_cache_key string;
定义缓存关键字;
示例:
proxy_cache_key $request_uri
proxy_cache_key $scheme$proxy_host$request_uri
多个变量的值作为一个字符串使用,这样,即便访问的是同一url,如果使用的协议不同,对应的缓存项也不一样;因此,key就决定了两个缓存项是否为同一个;
6、proxy_cache_valid [code ...] time;
为不同的响应码设定其缓存的时长;
示例:
proxy_cache_valid 200 303 10m;
proxy_cache_valid 404 1m;
响应码没定义的表示不缓存;
定义缓存:
[root@hosta ~]# vim /etc/nginx/nginx.conf
在http中定义:
proxy_cache_path /var/cache/nginx/myproxy levels=2:1:1 keys_zone=mycache:10m max_size=1g;
表示:
/var/cache/nginx/myproxy:定义缓存保存路径,如果目录不存在,要事先创建;
levels=2:1:1 创建3级缓存目录,每一级用1个字符;
max_size=1g:缓存的文件可用空间大小为1G;
keys_zone=mycache:10m:缓存名为pcache,大小为10M;
在server中定义调用缓存的名称:
[root@hosta ~]# vim /etc/nginx/conf.d/default.conf
location / {
# root /usr/share/nginx/html;
proxy_cache mycache;
proxy_cache_key $request_uri;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_pass http://192.168.1.3;
index index.html index.htm;
}
其中:
proxy_cache pcache 调用缓存名称;
proxy_cache_key $request_uri 指定缓存中的key为请求的uri;
proxy_cache_valid 200 302 10m 缓存响应码为200,302的资源10分钟;
proxy_cache_valid 404 1m 缓存响应码为404的资源为1分钟;
在浏览器测试:http://172.18.11.111/
多次刷新页面,以便生成缓存;
查看前端nginx代理中的是否有缓存生成:
[root@hosta ~]# ls /var/cache/nginx/myroxy
可查看到有2个目录,每个目录下只一个目录,到第三级目录即是文件;
可使用tree命令查看目录层级结构:
[root@hosta]# tree /var/cache/nginx/myproxy/
/var/cache/nginx/myproxy/
├── 01
│ └── 9
│ └── c
│ └── 8dfbf1f445e33bc957e156659229c901
└── d9
├── 7
│ └── c
│ └── 6666cd76f96956469e7be39d750cc7d9
└── a
└── 8
└── e0bd86606797639426a92306b1b98ad9
7、proxy_cache_use_stale error | timeout | invalid_header | updating | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | off ...;
指明在什么情况下是否使用过期缓存;
当代理服务器后后端主机通信发生错误时,在哪种错误情况下可以使用缓存中的内容,直接响应给客户端;
代理可使用的选项:
proxy_cache_min_uses 最少使用次数;
proxy_cache_methods 为哪些请求方法缓存,默认只为get缓存;
proxy_cache_bypass 绕过缓存;
proxy_cache_purge 对缓存进行修剪;
在前端代理服务器设置缓存可缓存2天,但后端服务器刚缓存半天,缓存就变化了;客户端请求时发现,都是从缓存中响应的,而原始数据实际已经改过了,作为管理员,只能手动连接到缓存服务器上把缓存项清除;这个操作就叫缓存修剪;
8、proxy_cache_methods GET | HEAD | POST ...;
配置段:http, server, location
当客户端使用指定的请求方法发送请求时,该请求会被缓存下来。GET和HEAD是默认就被指定的缓存方法。
9、proxy_hide_header field;
配置段:http, server, location
默认情况下,nginx不会将报文首部“Date”,“Server”等字段从代理服务器传递给客户端。而此指令,可让管理员设置额外的不进行传递的字段。
10、proxy_connect_timeout time;
与后端服务器建立连接的超时时长,默认为60秒,最长为75秒;
11、proxy_read_timeout time;
等待后端主机发送响应报文的超时时长,默认为60秒;此处定义的时长指的是连续两次响应报文等待之间的时长;很有可能需要调整;两个等待后端响应报文的时长;
12、proxy_send_timeout time;
向后端服务器发送请求报文的超时时长,默认为60秒,指的是连续两次请求报文(写操作)之间的时长,不是整个请求本身;两个请求报文发送后端的时长;
ngx_http_headers_module
用于在响应给客户端的报文中添加首部值
1、add_header name value [always];
向响应给客户端的报文添加自定义首部,并赋值;
可用于http, server, location, if in location上下文;
例如:$server_name或$server_addr
$server_addr
表示接收请求报文服务的地址即代理服务器地址;
server {
listen 80;
server_name www.ilinux.io;
location /blog/blog.html {
proxy_set_header X-Real-IP $remote_addr;
#添加自定义首部X-via表示代理服务器IP
add_header X-Via $server_addr;
#添加自定义首部X-Accel表示代理服务器的主机名
add_header X-Accel $server_name;
proxy_pass http://10.10.10.11/news/news.html;
}
}
2、expires [modified] time;
expires epoch | max | off;
控制与缓存相关的首部;
用于添加Expire及Cache-Control首部或修改首部的值;后面讲到缓存服务时具体讲缓存细节,这里不做过多介绍;
当响应码为200, 201, 204, 206, 301, 302, 303, 304, or 307添加一个首部,或修改一个值;就是控制如何缓存,包括缓存时间
#表示指定路径上的图片在客户端上缓存3天才失效
location ~* \.(gif|jpg|jpeg|png) {
root /var/mywww/html/public/
expires 3d;
}
ngx_http_fastcgi_module模块
LAMP(fpm):
httpd+php:3种
modules:把php编译成httpd自己的模块;
cgi
fastcgi
httpd依赖于proxy_fastcgi_module模块
LNMP():
nginx+php(fpm):只有1种
nginx编译时只支持fastcgi模块,因此php也只能使用php-fpm机制;fpm就是fastcig processor manager进程管理器;
当有大量数据进行保存时,要放在专用的存储中,需要专用的协议客户端,php程序代码,要与后端mysql通信,就要php链接mysql的驱动,叫php链接器;不同程序员使用不同的链接器,存储mysql数据库;
当用户请求有动态资源时,就交由后台的php服务器进行处理,如果还要有数据存储,就由php再与myslq通信,从而,将结果返回给客户端;
后端存储会存在多个客户端与数据库服务器通信,此时会遇到资源征用,可能会导致资源响应较慢;
以后,优化时,可根据应用来判断,问题的所在;
1、fastcgi_pass address;
指明后端php-fpm服务器的address;是fpm服务器监听的地址和端口;
示例:fastcgi 127.0.0.1:9000;
2、fastcgi_index name;
配置段:http, server, location
设置fastcgi的默认主页资源,如果URI以斜线结尾,文件名将追加到URI后面,这个值将存储在变量$fastcgi_script_name中。
例如:
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /home/www/scripts/php$fastcgi_script_name;
请求uri/page.php
的参数SCRIPT_FILENAME将被设置为/home/www/scripts/php/page.php
,但是"/"为"/home/www/scripts/php/index.php"。
3、fastcgi_param parameter value [if_not_empty];
配置段:http, server, location
此指令用于指定要传递给FastCGI服务器的参数,参数可以为文本、变量和两者之间的组合。
示例1:
#将匹配的php内容送到fastcgi服务器的/usr/share/nginx/html目录下进行处理,即指定fastcgi服务器上用于存放PHP内容的目录
location ~* \.php$ {
root /usr/share/nginx/html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html$fastcgi_script_name;
include fastcgi_params; #调用nginx的变量定义;
}
示例2:
#将状态页面status和检测页面ping送到fastcgi进行处理
location ~* ^/(status|ping)$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
}
4、fastcgi_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time];
定义缓存空间的名称;只能用于http上下文;
参数 | |
---|---|
path | 文件系统路径,用于存放缓存的文件数据;在此目录下分多级目录 |
max_size=size | 定义此路径下多大空间用于存储缓存数据 |
其它使用默认设置即可 | |
levels=#[:#[:#]] | 缓存目录的层级定义;一般为1或2;如:leves=1:2:2 |
keys_zone=name:size | 定义内存中用于缓存k/v映射关系的空间名称及大小 |
inactive=time | 定义非活动时间 |
示例:fastcgi_cache_path /data/nginx/cache levels=1:2 keys_zone=one:10m;
nginx缓存实现加速,php执行代码的结果保存在nginx的缓存空间中,用户再请求时就不会交给后端的php在执行了,而是直接从缓存返回给客户端,这就相当于静态资源了;httpd基于模块也能缓存;
-
缓存结果内容,有可能刚存下来,资源被删了,客户端请求时发现资源仍然可以响应,这样就不真实了,所以,缓存要及时清理;
-
缓存下来的内容,得有命中率,要有客户端经常访问,否则没有意义;同一资源被多次请求的经历;
nginx把缓存分为两段:
首先在内存空间中保存缓存文件的元数据;然后在磁盘中保存元数据所指向的文件内容;
所以,对于nginx缓存保存的是键值数据,把文件系统上的层级文件数据,转换成Key-Value数据,key在内存中,value在磁盘中是一个个的文件对象;而文件的名字等信息是在内存中的;
key也是分层级的但不同于文件系统的层级;k是文件的名字或是访问的URL,而v是真正的数据;数据文件的名字是这个文件内容的校验码;
缓存下来的文件的文件名是文件内容做md5校验计算后的校验码,md5校验码是128位二进制定长输出;每四位二进制对应一个十六位进制数,以md5为例,128/4=32,即32个十六进制数;文件内容不同校验码就不同;把这些十六进制数字组成的文件名进行层级划分;
可把前两个字符当作一级子目录的名字,再两字符当二级子目录的名字,再一个字符当三级子目录的名字;这样意味着,一级子目录有(两个十六进制数字表示的个数)即256个,二级子目录有256个。三级子目录有16个;
想要分几级,就用多少个十六进制数所能划分的个数来表示;
5、fastcgi_cache zone|off;
是否启用cache功能,如果启用,要提前定义缓存空间的名字;默认off不缓存;
6、fastcgi_cache_key string;
定义要使用的缓存键;
例如:fastcgi_cache_key $request_uri
7、fastcgi_cache_methods GET | HEAD | POST ...;
缓存哪些类型的请求的相关数据;是请求方法;
8、fastcgi_cache_min_uses number;
缓存最少使用次数;在指定时长内,如果小于此值就为非活动;
9、fastcgi_cache_valid [code ...] time;
缓存数据时,对不同响应码设定其可缓存的时长;
注意:调用缓存时,至少应该制定3个参数
fastcgi_cache
fastcgi_cache_key
fastcgi_cache_valid
例如:
启用缓存前,使用ab命令压测
[root@VM_0_2_centos ~]# ab -c 10 -n 100 https://172.18.11.114/info.php
平均响应请求为300个左右;
开启nginx缓存功能:
[root@VM_0_2_centos ~]# vim /etc/nginx/nginx.conf
在http配置段:
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=fcgicache:10m;
调用缓存:
location ~ \.php$ {
root html;
fastcgi_cache fcgicache; #调用fache缓存空间
fastcgi_cache_valid 200 302 10m; #状态码为200和302的页面缓存10分钟
fastcgi_cache_valid 301 1h; #状态码为301的页面缓存1小时
fastcgi_cache_valid any 1m; #剩下的都缓存1分钟
fastcgi_cache_key $request_uri; #设置缓存的key为$request_uri
fastcgi_pass 127.0.0.1:9000; #指定fastcgi服务器
fastcgi_index index.php; #指定默认的fastcgi主页
fastcgi_param SCRIPT_FILENAME /usr/local/nginx/html/$fastcgi_script_name;
include fastcgi_params; #调用nginx的变量定义
}
[root@VM_0_2_centos ~]# nginx -t
报错,创建目录;
[root@VM_0_2_centos ~]# mkdir /var/cache/nginx/fastcgi -pv
[root@VM_0_2_centos ~]# nginx -s reload
在浏览器:https://www1.stu11.com/info.php
多次刷新页面后,查看生成的缓存文件:
[root@VM_0_2_centos ~]# ls /var/cache/nginx/fastcgi/
5 7 b
[root@VM_0_2_centos ~]# tree /var/cache/nginx/fastcgi/
/var/cache/nginx/fastcgi/
├── 5
│ └── 34
│ └── 751b46a83f276bdacf136a8e2ee9f345
├── 7
│ └── 3a
│ └── e57acb73a5225739581dfc2a8f48b3a7
└── b
└── 23
└── 41b24196f66c4d900cc5c11aa2d3a23b
[root@VM_0_2_centos ~]# ab -c 10 -n 100 https://172.18.11.114/info.php
有显著提升,平均响应为400左右;
ngx_http_upstream_conf_module模块
将后端主机定义为服务器组(用来定义服务器组),而后可有proxy_pass,fastcgi_pass,memcached_pass等进行引用;
是动态配置upstream服务器的一个接口;
Module ngx_http_upstream_module
将多个后端主机定义为服务器组,而后可由proxy_pass,fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass等进行引用;
用于定义服务器组,用一组服务器可由代理服务器向外发布服务接收、请求;这组服务器可基于某种方式定义调度算法(如ip_hash,least_conn),来实现所谓的负载均衡调度;
此模块才是真正能把nginx当做反向代理负载均衡器的模块,定义成组不光能被proxy_pass使用,还可被fastcgi_pass, uwsgi_pass, scgi_pass, and memcached_pass使用;
1、upstream name { ... }
定义后端服务器组;只需指定组名,引入新的上下文,上下文就叫upstream上下文;
只能用于http上下文;
name:名称,直接字符串;
示例:
upstream httpdsrvs {
server 10.10.10.11:80;
server 10.10.10.12:80;
...
}
2、server address [parameters];
添加服务器,定义服务器的地址和相关参数;默认为80端口;
address地址格式 | |
---|---|
IP[:port] | 指定后端主机IP地址和端口 |
HOSTNAME[:port] | 指定后端主机的某虚拟主机,要在前端代理的hosts文件中定义代理的虚拟主机名对应的ip地址是什么,避免dns挂了不能被访问 |
unix://PATH/TO/SOME_SOCK_FILE | 开头是unix的路径 |
parameters参数 | |
---|---|
weight=number | 服务器主机的权重值;在最少连接和轮询调度时,默认的意义是加权轮询或加权最少连接; |
max_fails=number | 最大失败尝试次数(与后端服务器连接最大不成功尝试次数) |
fail_timeout=time | 设置服务器被识别为不可用的超时时长 |
backup | 备用主机,相当于定义为sorry server;所有后端主机都不可用时才生效 |
down | 手动标记为下线(不再处理任何用户请求),维护时使用 |
后端如果是商城程序的应用程序服务器时,当要进行新版本程序发布,设置要应用此设置;在摘除前确保本服务器没有会话保持,可用session服务器做会话保持,这样即便摘除,客户访问时再次刷新会重新调度到在线服务器上;就在前端代理服务器对应的server上添加down,标记为下线,就可摘除后端服务器进行升级等操作,完成后再接入系统中,把down标记删除即可;观察一段时间后没有问题,就可以此方法,对其它服务器进行升级操作,这种机制就称灰度发布模型;数量多时可一批一批进行,有能力会写脚本自动完成;但是脚本完成比较麻烦,可使用python程序完成;
一般先标记为down,然后重载nginx,就不会有新的请求进来,但是老的请求还在,因为是平滑摘除的;一个请求对服务器最长不能超过5秒钟;5秒钟以后,就可把后端服务停掉了;
例如:nginx实现为后端2主机做反代
后端服务器:
RS1:192.168.1.3
RS2:192.168.1.4
前端nginx反代:
VIP:172.18.11.111
DIP:192.168.1.2
后端服务器:
创建网页提供http服务
[root@rs1 ~]# vim /var/www/html/index.html
<h1>RS1-172.18.11.8</h1>
[root@rs2 ~]# vim /var/www/html/index.html
<h1>RS2-172.18.11.9</h1>
在nginx反代服务器上基于负载均衡,要编辑http的上下文;
[root@niginx]# vim /etc/nginx/nginx.conf
http {
...
upstream websrvs {
server 192.168.1.3:80 weight=1;
server 192.168.1.4:80 weight=1;
}
...
}
在指定的server中调用;
[root@niginx ~]# vim /etc/nginx/conf.d/default.conf
server {
...
location / {
# root /usr/share/nginx/html;
#add_header X-Via $server_addr;
# proxy_cache mycache;
proxy_cache_key $request_uri;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_pass http://websrvs; 注意此处要改为主配置文件中定义的服务器组名称;
index index.html index.htm;
}
...
}
在浏览器输入:http://172.18.11.111/
一次次刷新,可发现响应的结果是RS1和RS2中的页面,交替提供响应;
也可把权重改下,如:server 192.168.1.3:80 weight=2;
则在浏览器输入:http://172.18.11.111/
一次次刷新,可发现,响应结果为,RS1响应2次后转为RS2响应1次,按照2:1的比率,依次交替响应;
3、ip_hash;
原地址哈希调度算法;
只能用于upstream上下文;
能够使来自于同一个客户端请求始终到同一个RS;
哈希的是客户端ip,相当于lvs中的sh算法,哈希表中的key是客户端ip,位于同一个nat服务器后面的所有客户端(内网用户),会始终被调度到一个服务器响应,调度粒度过于粗糙;
例如:
[root@niginx ~]# vim /etc/nginx/nginx.conf
http {
...
upstream websrvs {
server 192.168.1.3:80 weight=2;
server 192.168.1.4:80 weight=1;
ip_hash;
}
...
}
使用curl命令测试
[root@niginx]# curl http://172.18.11.111
发现,使用同一终端ip,始终由一个服务器响应;实现了原地址哈希,绑定了客户端与服务器;
4、least_conn;
如果weight有值且不相同,就相当于加权最少连接调度算法;
只能用于upstream上下文;
考虑后端服务器当前负载进行调度;计算方法跟lvs中很相似,活动数连接/权重;有可能结果不是很对称,这跟超时时间有关;
例如:
[root@niginx ~]# vim /etc/nginx/nginx.conf
http {
...
upstream websrvs {
server 192.168.1.3:80 weight=2;
server 192.168.1.4:80 weight=1;
least_conn;
}
...
}
使用curl命令测试
[root@niginx ~]# curl http://172.18.11.111
发现,由两台RS交替响应,比例为2:1
5、keepalive connections;
设置保持连接个数;可节约套接字和端口;激活后端服务器组缓存,主要设置保持连接时长(实际是连接个数);超出连接的个数时,最近最少使用的连接LRU被关闭;
通常,在前端反代服务器与后端服务器间打开保持连接功能,可节约前端反代服务器的端口(套接字);
upstream http_backend {
server 127.0.0.1:8080;
keepalive 16;
}
#对于fastcgi服务器来说,keepalive要结合fastcgi_keep_conn指令一起使用才能生效
upstream fastcgi_backend {
server 127.0.0.1:9000;
keepalive 8;
}
server {
...
location /fastcgi/ {
fastcgi_pass fastcgi_backend;
fastcgi_keep_conn on;
...
}
}
6、health_check [parameters];
定义后端主机的健康状态检测机制;只能用于location上下文;
如果不定义此处,默认在定义server时也会有最大失败尝试次数,失败的超时时长等,已经有一定意义的健康状态检测机制;
要使用match判断匹配健康机制;
parameters可用参数 | |
---|---|
interval=time | 检测的频度,默认为5秒 |
fails=number | 判定为失败的检测次数;判断服务器不可用的检测次数;默认1次,尽量使用3次; |
passes=number | 判定为成功的检测次数;判断服务器为可用的检测次数;默认1次; |
uri=uri | 执行健康状态检测时试图请求的uri;不指明默认为主页/;表明在请求这个url时,其响应结果必须是在match中匹配的 |
match=name | 基于哪个match做检测结果为成功或失败的判定;用谁进行评估检测结果;指明调用哪个macth(做健康检测判断)判断成功或失败; |
port=number | 指明向服务器的哪个端口发起健康状态检测请求 |
服务器可监听在2个端口,例如用8080端口用来接收健康状态检测,80端口向外提供服务;这样可以把8080端口做一个虚拟主机,而后对这个内容不记录在访问日志中;所以作为健康状态检测,有可能对于后端主机要关闭日志功能;
如果不定义此参数,后端主机也会自动由在线主机负责所有响应;
可把RS2主机关闭http服务,在浏览器上测试,依然能访问主页;
再把RS2主机启动http服务,再进入浏览器测试,会发现恢复到原来依次交替负责响应;
7、match name{...}
只用于http上下文,对后端主机做健康状态检测时,定义其结果判断标准;
结果 | |
---|---|
status 200; | 响应码为200时认为是成功 |
status ! 500; | 响应码只要不是500就认为是成功 |
status 200 204; | 响应码为200或204认为是成功 |
status ! 301 302; | 响应码只要不是301,302就认为是成功 |
status 200-399; | 响应码为200至399之间的值认为是成功 |
status ! 400-599; | 响应码为不是400至999之间的值认为是成功 |
status 301-303 307; | 响应码为301至303之间或307的值认为是成功 |
header Content-Type = text/html; | 头部是text/html认为是成功 |
header Content-Type != text/html; | 头部不是text/html认为是成功 |
header Connection ~ close; | 头部能被close匹配认为是成功 |
header Connection !~ close; | 头部不能被close匹配认为是成功 |
header Host; | 头部存在host头部认为是成功 |
header ! X-Accel-Redirect; | 头部不存在此头部认为是成功 |
body ~ "Welcome to nginx!"; | 被此处匹配的body认为是成功 |
body !~ "Welcome to nginx!"; | 不能被此处匹配的body认为是成功 |
专用指令:
status:期望的响应码:
status CODE
status ! CODE
status CODE-CODE
header:基于响应首部进行判断
header HEADER=VALUE
header HEADER!=VALUE
header [!]HEADER
header HEADER ~ VALUE
body:期望的响应报文的主体部分应该有的内容
body ~ "CONTENT"
body !~ "CONTENT"
例如:
[root@nginx ~]# vim /etc/nginx/nginx.conf
http {
...
match health {
status 200;
body ~ "OK";
}
...
}
[root@nginx ~]# vim ./conf.d/default.conf
在server中调用
server{
...
location / {
# root /usr/share/nginx/html;
#add_header X-Via $server_addr;
# proxy_cache mycache;
proxy_cache_key $request_uri;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_pass http://websrvs;
index index.html index.htm;
health_check match=health interval=2 fails=3 uri=/.health.html;
}
...
}
注意:nginx2.0+以上版本才支持health_check相关功能;
8、sticky cookie name [expires=time] [domain=domain] [httponly] [secure] [path=path];
启用session绑定,基于cookie(会话)的绑定机制;
只用于upstream上下文;
比ip_hash更为精细的精细会话保持绑定;
参数选项:
cookie name 定义server id
expires=time 定义cookie有效时长,即会话绑定时长;
domain=domain 定义cookie应用在哪个域上;
httponly 定义cookie仅用在http协议上;
secure 给cookie添加安全首部
path=path 指定某个域中的某个路径下哪些内容做cookie绑定
例如:
sticky cookie srv_id expires=1h domain=.example.com path=/;
srv_id 把哪个server id当做cookie的内容插入的用户的作业中;
expires=1h 指明有效时长;
domain=.example.com path=/ 应用在哪个域上的哪个路径上;
例如:
[root@nginx ~]# vim /etc/nginx/nginx.conf
http {
...
upstream websrvs {
server 192.168.1.3:80 weight=2;
server 192.168.1.4:80 weight=1;
sticky cookie srv_id expires=1h path=/;
}
...
}
注意:在nginx1.8版本不支持此功能;
9、hash key [consistent];
定义调度方法,可自定义基于何种信息(key)进行绑定;如nginx可基于cookie、用户请求的host首部、用户请求的uri绑定;
例如:
hash $remote_addr
基于请求的客户端地址进行绑定;相当于ip_hash;
hash $request_uri
客户端请求的url
hash $cookie_username
来自同一用户账号进行绑定
例如:基于客户端源IP的绑定
[root@niginx ~]# vim /etc/nginx/nginx.conf
http {
...
upstream websrvs {
server 192.168.1.3:80 weight=2;
server 192.168.1.4:80 weight=1;
hash $remote_addr;
}
...
}
在浏览器输入:http://172.18.11.111
多次刷新,仍然为同一RS响应;实现基于客户端源IP的绑定;
在各RS上创建多个网页,测试基于url的绑定:
[root@rs1 ~]# for i in {1..10};do echo "$i page on RS1" > /var/www/html/$i.html;done
[root@rs2 ~]# for i in {1..10};do echo "$i page on RS2" > /var/www/html/$i.html;done
例如:基于用户请求的url的绑定
[root@niginx ~]# vim /etc/nginx/nginx.conf
http {
...
upstream websrvs {
server 192.168.1.3:80 weight=2;
server 192.168.1.4:80 weight=1;
hash $request_uri;
}
...
}
在浏览器输入不同的资源:
http://172.18.11.111/1.html
http://172.18.11.111/2.html
http://172.18.11.111/3.html
可观察,由于使用的2:1的权重,在uri进行绑定时也可看出效果来;
注意:基于url绑定时的好处在于,后端如果是缓存服务器时提高缓存命中率;
其中内置变量:
$upstream_addr upstream
服务器自己的地址
$upstream_cache_status
缓存命中与否,引用后可知道是从缓存中响应的,还是从后端服务器响应的;
注意:只要能进行哈希的值都可作为绑定对象;
补充:内置变量的调用,向客户端展示缓存命中与否;
add_header X-Cache $upstream_cache_status;
nginx也可实现更高级的基于方法的分离,get,put叫读写分离等;
例如:开启缓存,查看请求是由缓存响应还是由后端服务器响应,引入$upstream_cache_status
[root@niginx ~]# vim /etc/nginx/nginx.conf
server {
...
location / {
# root /usr/share/nginx/html;
add_header X-Via $server_addr;
add_header X-Cache $upstream_cache_status;
proxy_cache mycache;
proxy_cache_key $request_uri;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_pass http://websrvs;
index index.html index.htm;
# health_check match=health interval=2 fails=3 uri=/.health.html;
}
...
}
在浏览器F12打开调试模式:
输入:http://172.18.11.111/1.html
点击第一次,可见为:X-Cache:MISS 表示由后端服务器响应的;
点击第二次,可见为:X-Cache:HIT 表示由缓存响应的;