什么是负载均衡
我们知道单台服务器的性能是有上限的,当流量很大时,就需要使用多台服务器来共同提供服务,这就是所谓的集群。
负载均衡服务器,就是用来把经过它的流量,按照某种方法,分配到集群中的各台服务器上。这样一来不仅可以承担
更大的流量、降低服务的延迟,还可以避免单点故障造成服务不可用。一般的反向代理服务器,都具备负载均衡的功能。
负载均衡功能可以由硬件来提供,比如以前的F5设备。也可以由软件来提供,LVS可以提供四层的负载均衡(利用IP和端口),
Haproxy和Nginx可以提供七层的负载均衡(利用应用层信息)。
来看一个最简单的Nginx负载均衡配置。
- http {
- upstream cluster {
- server srv1;
- server srv2;
- server srv3;
- }
- server {
- listen 80;
- location / {
- proxy_pass http://cluster;
- }
- }
- }
通过上述配置,Nginx会作为HTTP反向代理,把访问本机的HTTP请求,均分到后端集群的3台服务器上。
此时使用的HTTP反向代理模块是ngx_http_proxy_module。
一般在upstream配置块中要指明使用的负载均衡算法,比如hash、ip_hash、least_conn。
这里没有指定,所以使用了默认的HTTP负载均衡算法 - 加权轮询。
负载均衡流程图
在描述负载均衡模块的具体实现前,先来看下它的大致流程:
负载均衡模块
Nginx目前提供的负载均衡模块:
ngx_http_upstream_round_robin,加权轮询,可均分请求,是默认的HTTP负载均衡算法,集成在框架中。
ngx_http_upstream_ip_hash_module,IP哈希,可保持会话。
ngx_http_upstream_least_conn_module,最少连接数,可均分连接。
ngx_http_upstream_hash_module,一致性哈希,可减少缓存数据的失效。
以上负载均衡模块的实现,基本上都遵循一套相似的流程。
1. 指令的解析函数
比如least_conn、ip_hash、hash指令的解析函数。
这些函数在解析配置文件时调用,主要用于:
检查指令参数的合法性
指定peer.init_upstream函数指针的值,此函数用于初始化upstream块。
2. 初始化upstream块
在执行完指令的解析函数后,紧接着会调用所有HTTP模块的init main conf函数。
在执行ngx_http_upstream_module的init main conf函数时,会调用所有upstream块的初始化函数,
即在第一步中指定的peer.init_upstream,主要用于:
创建和初始化后端集群,保存该upstream块的数据
指定peer.init,此函数用于初始化请求的负载均衡数据
来看下ngx_http_upstream_module。
- ngx_http_module_t ngx_http_upstream_module_ctx = {
- ...
- ngx_http_upstream_init_main_conf, /* init main configuration */
- ...
- };
- static char *ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf)
- {
- ...
- /* 数组的元素类型是ngx_http_upstream_srv_conf_t */
- for (i = 0; i < umcf->upstreams.nelts; i++) {
- /* 如果没有指定upstream块的初始化函数,默认使用round robin的 */
- init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream :
- ngx_http_upstream_init_round_robin;
- if (init(cf, uscfp[i] != NGX_OK) {
- return NGX_CONF_ERROR;
- }
- }
- ...
- }
3. 初始化请求的负载均衡数据块
当收到一个请求后,一般使用的反向代理模块(upstream模块)为ngx_http_proxy_module,
其NGX_HTTP_CONTENT_PHASE阶段的处理函数为ngx_http_proxy_handler,在初始化upstream机制的
函数ngx_http_upstream_init_request中,调用在第二步中指定的peer.init,主要用于:
创建和初始化该请求的负载均衡数据块
指定r->upstream->peer.get,用于从集群中选取一台后端服务器(这是我们最为关心的)
指定r->upstream->peer.free,当不用该后端时,进行数据的更新(不管成功或失败都调用)
请求的负载均衡数据块中,一般会有一个成员指向对应upstream块的数据,除此之外还会有自己独有的成员。
"The peer initialization function is called once per request.
It sets up a data structure that the module will use as it tries to find an appropriate
backend server to service that request; this structure is persistent across backend re-tries,
so it's a convenient place to keep track of the number of connection failures, or a computed
hash value. By convention, this struct is called ngx_http_upstream_<module_name>_peer_data_t."
4. 选取一台后端服务器
一般upstream块中会有多台后端,那么对于本次请求,要选定哪一台后端呢?
这时候第三步中r->upstream->peer.get指向的函数就派上用场了:
采用特定的算法,比如加权轮询或一致性哈希,从集群中选出一台后端,处理本次请求。
选定后端的地址保存在pc->sockaddr,pc为主动连接。
函数的返回值:
NGX_DONE:选定一个后端,和该后端的连接已经建立。之后会直接发送请求。
NGX_OK:选定一个后端,和该后端的连接尚未建立。之后会和后端建立连接。
NGX_BUSY:所有的后端(包括备份集群)都不可用。之后会给客户端发送502(Bad Gateway)。
5. 释放一台后端服务器
当不再使用一台后端时,需要进行收尾处理,比如统计失败的次数。
这时候会调用第三步中r->upstream->peer.free指向的函数。
函数参数state的取值:
0,请求被成功处理
NGX_PEER_FAILED,连接失败
NGX_PEER_NEXT,连接失败,或者连接成功但后端未能成功处理请求
一个请求允许尝试的后端数为pc->tries,在第三步中指定。当state为后两个值时:
如果pc->tries不为0,需要重新选取一个后端,继续尝试,此后会重复调用r->upstream->peer.get。
如果pc->tries为0,便不再尝试,给客户端返回502错误码(Bad Gateway)。
在upstream模块的回调
负载均衡模块的功能是从后端集群中选取一台后端服务器,而具体的反向代理功能是由upstream模块实现的,
比如和后端服务器建立连接、向后端服务器发送请求、处理后端服务器的响应等。
我们来看下负载均衡模块提供的几个钩子函数,是在upstream模块的什么地方回调的。
Nginx的HTTP反向代理模块为ngx_http_proxy_module,其NGX_HTTP_CONTENT_PHASE阶段的处理函数为
ngx_http_proxy_handler,每个请求的upstream机制是从这里开始的。
- ngx_http_proxy_handler
- ngx_http_upstream_create /* 创建请求的upstream实例 */
- ngx_http_upstream_init /* 启动upstream机制 */
- ngx_htp_upstream_init_request /* 负载均衡模块的入口 */
- uscf->peer.init(r, uscf) /* 第三步,初始化请求的负载均衡数据块 */
- ...
- ngx_http_upstream_connect /* 可能会被ngx_http_upstream_next重复调用 */
- ngx_event_connect_peer(&u->peer); /* 连接后端 */
- pc->get(pc, pc->data); /* 第四步,从集群中选取一台后端 */
- ...
- /* 和后端建连成功后 */
- c = u->peer.connection;
- c->data = r;
- c->write->handler = ngx_http_upstream_handler; /* 注册的连接的读事件处理函数 */
- c->read->handler = ngx_http_upstream_handler; /* 注册的连接的写事件处理函数 */
- u->write_event_handler = ngx_http_upstream_send_request_handler; /* 写事件的真正处理函数 */
- u->read_event_handler = ngx_http_upstream_process_header; /* 读事件的真正处理函数 */
选定后端之后,在和后端通信的过程中如果发生了错误,会调用ngx_http_upstream_next来继续尝试其它的后端。
- ngx_http_upstream_next
- if (u->peer.sockaddr) {
- if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_403 ||
- ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404)
- state = NGX_PEER_NEXT;
- else
- state = NGX_PEER_FAILED;
- /* 第五步,释放后端服务器 */
- u->peer.free(&u->peer, u->peer.data, state);
- u->peer.sockaddr = NULL;
- }
Reference
[1]. http://www.evanmiller.org/nginx-modules-guide.html#proxying
[2]. http://tengine.taobao.org/book/chapter_05.html#id5
---------------------
下载nginx源码包,编译nginx需要指定pcre,zlib,openssl,到官网上下载源代码包:
http://www.zlib.NET/
http://www.openssl.org/
http://www.pcre.org/
将这三个包下载放到/opt目录,tar -xzvf *.gz解压,然后也将nginx-0.6.32的包解压到/opt目录下,进入nginx目录,执行:
#./configure --sbin-path=/usr/local/sbin --with-http_ssl_module --with-pcre=../pcre-7.7 --with-zlib=../zlib-1.2.3 --with-openssl=../openssl-0.9.8g
#make && make install
如果在./configure时出现在错误,可能是没有安装gcc g+编译器的缘故。单独安装这两个编译器比较麻烦,ubuntu提供了build-essential来安装gcc和g++编译器
apt-get install build-essential
采用aptitude安装的时候,系统会将所有依赖的包都一并装上,但是使 用源码编译的时候就没这么智能了,我们需要找到需要的依赖包,然后手工安装,幸运的是,这并不复杂,也不多,例如pcre, ssl 和zlib,安装方法比较简单:
sudo aptitude install libpcre3 libpcre3-dev libpcrecpp0 libssl-dev zlib1g-dev
三、安装Nginx
ok,准备工作做完了,是时候开始下载、 安装Nginx了。
1、下载源代码
到Nignx官网去下载源代码
2、 解压
tar -zxvf nginx-0.6.31.tar.gz
...
cd nginx-0.6.31/
3、选择需要的编译选项
Nginx的编译选项还是不少 的,基本上都是定制位置和模块,分别是:
1)--sbin-path=/usr/local/sbin
从源码编译Nginx会安装在/usr/local/nginx目录下(你可以使用参数--prefix=<path>指定自己需要的位置),然后会将bin文件放在/usr/local/nginx/sbin/nginx,虽然比较清晰,但是和我们一般的习惯不同
(作为程序员的我们更习惯在/usr/local /sbin下寻找bin文件);
2)--with-http_ssl_module
主要是提供https访问支持的。
5、 Compile
./configure --sbin-path=/usr/local/sbin --with-http_ssl_module
编 译的时间不会很长,完成的时候你会在屏幕上看到类似下面的信息:
...
nginx path prefix: "/usr/local/nginx"
nginx binary file: "/usr/local/sbin"
nginx configuration file: "/usr/local/nginx/conf/nginx.conf"
...
留 意上面的提示,涉及到nginx的几个重要文件的路径信息。
6、Make && make install
差不多快完成了,现在make一下。
make
sudo make install
在使用make install 命令时可能会提示当前执行的命令权限不够,sudo su命令然后输入密码提升下权限就ok
7、chmod(可选)
还需要给bin文件有执行权限,也比较简单:
chmod +x /usr/local/sbin/nginx
8、Nginx重要文件安装目录
默认nginx安装的目录在/usr/local/nginx下,包括:
/usr/local/nginx/sbin #nginx启动文件
/usr/local/nginx/conf #配置文件
/usr/local/nginx/html #默认网页文件
/usr/local/nginx/logs #日志文件
四、使用
安装完成后,试着使用下。
1、启动
sudo /usr/local/sbin/nginx
然后把自己的浏览器导航到http://IP就可 以看到默认的欢迎界面了
Welcome to nginx!
If you see this page, the nginx web server is successfully installed andworking. Further configuration is required.
For online documentation and support please refer tonginx.org.
Commercial support is available atnginx.com.
Thank you for using nginx.
2、停止
sudo /usr/local/sbin/nginx
-s stop
六、附录:Compile-time options
(原文参考:http://wiki.codemongers.com/NginxInstallOptions )
configure 脚本确定系统所具有一些特性,特别是 nginx 用来处理连接的方法。然后,它创建 Makefile 文件。
configure 支持下面的选项:
1)目录属性
--prefix=<path> - Nginx安装路径。如果没有指定,默认为 /usr/local/nginx。
--sbin-path=<path> - Nginx可执行文件安装路径。只能安装时指定,如果没有指定,默认为<prefix>/sbin/nginx。
--conf-path=<path> - 在没有给定-c选项下默认的nginx.conf的路径。如果没有指定,默认为<prefix>/conf/nginx.conf。
--pid-path=<path> - 在nginx.conf中没有指定pid指令的情况下,默认的nginx.pid的路径。如果没有指定,默认为 <prefix>/logs/nginx.pid。
--lock-path=<path> - nginx.lock文件的路径,默认为<prefix>/logs/nginx.lock
--error-log-path=<path> - 在nginx.conf中没有指定error_log指令的情况下,默认的错误日志的路径。如果没有指定,默认为 <prefix>/logs/error.log。
--http-log-path=<path> - 在nginx.conf中没有指定access_log指令的情况下,默认的访问日志的路径。如果没有指定,默认为 <prefix>/logs/access.log。
--user=<user> - 在nginx.conf中没有指定user指令的情况下,默认的nginx使用的用户。如果没有指定,默认为 nobody。
--group=<group> - 在nginx.conf中没有指定user指令的情况下,默认的nginx使用的组。如果没有指定,默认为 nobody。
--builddir=DIR - 指定编译的目录
2)模块
--with-rtsig_module - 启用 rtsig 模块
--with-select_module --without-select_module -允许或不允许开启SELECT模式,如果 configure 没有找到更合适的模式,比如:kqueue(sun os),epoll (Linux kenel 2.6+), rtsig(实时信号)或者/dev/poll(一种类似select的模式,底层实现与SELECT基本相 同,都是采用轮训方法) SELECT模式将是默认安装模式
--with-poll_module --without-poll_module - Whether or not to enable the poll module. This module is enabled by default if a more suitable method such as kqueue, epoll, rtsig or /dev/poll is not discovered by configure.
--with-http_ssl_module -开启HTTP SSL模块,使NGINX可以支持HTTPS请求。这个模块需要已经安装了OPENSSL,在DEBIAN上是libssl
--with-http_realip_module - 启用 ngx_http_realip_module
--with-http_addition_module - 启用 ngx_http_addition_module
--with-http_sub_module - 启用 ngx_http_sub_module
--with-http_dav_module - 启用 ngx_http_dav_module
--with-http_flv_module - 启用 ngx_http_flv_module
--with-http_stub_status_module - 启用 "server status" 页
--without-http_charset_module - 禁用 ngx_http_charset_module
--without-http_gzip_module - 禁用 ngx_http_gzip_module. 如果启用,需要 zlib 。
--without-http_ssi_module - 禁用 ngx_http_ssi_module
--without-http_userid_module - 禁用 ngx_http_userid_module
--without-http_access_module - 禁用 ngx_http_access_module
--without-http_auth_basic_module - 禁用 ngx_http_auth_basic_module
--without-http_autoindex_module - 禁用 ngx_http_autoindex_module
--without-http_geo_module - 禁用 ngx_http_geo_module
--without-http_map_module - 禁用 ngx_http_map_module
--without-http_referer_module - 禁用 ngx_http_referer_module
--without-http_rewrite_module - 禁用 ngx_http_rewrite_module. 如果启用需要 PCRE 。
--without-http_proxy_module - 禁用 ngx_http_proxy_module
--without-http_fastcgi_module - 禁用 ngx_http_fastcgi_module
--without-http_memcached_module - 禁用 ngx_http_memcached_module
--without-http_limit_zone_module - 禁用 ngx_http_limit_zone_module
--without-http_empty_gif_module - 禁用 ngx_http_empty_gif_module
--without-http_browser_module - 禁用 ngx_http_browser_module
--without-http_upstream_ip_hash_module - 禁用 ngx_http_upstream_ip_hash_module
--with-http_perl_module - 启用 ngx_http_perl_module
--with-perl_modules_path=PATH - 指定 perl 模块的路径
--with-perl=PATH - 指定 perl 执行文件的路径
--http-log-path=PATH - Set path to the http access log
--http-client-body-temp-path=PATH - Set path to the http client request body temporary files
--http-proxy-temp-path=PATH - Set path to the http proxy temporary files
--http-fastcgi-temp-path=PATH - Set path to the http fastcgi temporary files
--without-http - 禁用 HTTP server
--with-mail - 启用 IMAP4/POP3/SMTP 代理模块
--with-mail_ssl_module - 启用 ngx_mail_ssl_module
--with-cc=PATH - 指定 C 编译器的路径
--with-cpp=PATH - 指定 C 预处理器的路径
--with-cc-opt=OPTIONS - Additional parameters which will be added to the variable CFLAGS. With the use of the system library PCRE in FreeBSD, it is necessary to indicate --with-cc-opt="-I /usr/local/include". If we are using select() and it is necessary to increase the number of file descriptors, then this also can be assigned here: --with-cc-opt="-D FD_SETSIZE=2048".
--with-ld-opt=OPTIONS - Additional parameters passed to the linker. With the use of the system library PCRE in FreeBSD, it is necessary to indicate --with-ld-opt="-L /usr/local/lib".
--with-cpu-opt=CPU - 为特定的 CPU 编译,有效的值包括:pentium, pentiumpro, pentium3, pentium4, athlon, opteron, amd64, sparc32, sparc64, ppc64
--without-pcre - 禁止 PCRE 库的使用。同时也会禁止 HTTP rewrite 模块。在 "location" 配置指令中的正则表达式也需要 PCRE 。
--with-pcre=DIR - 指定 PCRE 库的源代码的路径。
--with-pcre-opt=OPTIONS - Set additional options for PCRE building.
--with-md5=DIR - Set path to md5 library sources.
--with-md5-opt=OPTIONS - Set additional options for md5 building.
--with-md5-asm - Use md5 assembler sources.
--with-sha1=DIR - Set path to sha1 library sources.
--with-sha1-opt=OPTIONS - Set additional options for sha1 building.
--with-sha1-asm - Use sha1 assembler sources.
--with-zlib=DIR - Set path to zlib library sources.
--with-zlib-opt=OPTIONS - Set additional options for zlib building.
--with-zlib-asm=CPU - Use zlib assembler sources optimized for specified CPU, valid values are: pentium, pentiumpro
--with-openssl=DIR - Set path to OpenSSL library sources
--with-openssl-opt=OPTIONS - Set additional options for OpenSSL building
--with-debug - 启用调试日志
--add-module=PATH - Add in a third-party module found in directory PAT
---------------------
前言:
通过给回源服务器配置缓存的案例,详细讲解一整套缓存配置机制,并且可沿用到其他任何缓存配置场景中。
- 回源服务器的工作是啥
- 为啥需要给回源服务器加缓存
- 如何配置缓存
- 如何针对业务场景配置完备的缓存机制
回源服务器的工作:
源站架构:源站是nginx+PHP的webserver架构,如图所示:
proxy_cache原理:
如何配置proxy_cache模块
- http{
- ......
- proxy_cache_path/data/nginx/tmp-test levels=1:2 keys_zone=tmp-test:100m inactive=7d max_size=1000g;
- }
proxy_cache_path 缓存文件路径
levels 设置缓存文件目录层次;levels=1:2 表示两级目录
keys_zone 设置缓存名字和共享内存大小
inactive 在指定时间内没人访问则被删除
当配置好之后,重启nginx,如果不报错,则配置的proxy_cache会生效
如何使用proxy_cache
- location /tmp-test/ {
- proxy_cache tmp-test;
- proxy_cache_valid 200 206 304 301 302 10d;
- proxy_cache_key $uri;
- proxy_set_header Host $host:$server_port;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_passhttp://127.0.0.1:8081/media_store.php/tmp-test/;
- }
proxy_cache_valid 200 206 304 301 302 10d; 对httpcode为200…的缓存10天
proxy_cache_key $uri 定义缓存唯一key,通过唯一key来进行hash存取
proxy_set_header 自定义http header头,用于发送给后端真实服务器。
添加proxy_cache之后,请求过程的变化:
1、第一次访问:
3、同一个url第二次访问,当同一个文件再次到达源站,proxy_cache就会找到其对应的缓存文件(命中缓存HIT)直接返回给请求端,无需再执行php程序,如图所示:
提出疑问:
- 需要主动清理缓存文件
- 写入路径为一块磁盘,如果磁盘打满该怎么解决?
- 如何让源站支持断点续传,以及断点续传的缓存策略
- 如果请求端 range 请求(分片下载)一个大资源,同样的uri,如何区别请求?
- 还需要告诉请求端,资源的过期时间
- 日志统计,如何配置命中与不命中字段,如何做统计?
问题一:主动清理缓存
- location /tmp-test/ {
- allow 127.0.0.1; //只允许本机访问
- deny all; //禁止其他所有ip
- proxy_cache_purge tmp-test $uri; //清理缓存
- }
问题二:缓存文件强磁盘打满该怎么办?
问题三:支持range(断点续传)
- location /tmp-test/ {
- proxy_cache tmp-test;
- proxy_cache_valid 200 206 304 301 302 10d;
- proxy_cache_key $uri;
- <span style="color:#ff0000;">proxy_set_header Range $http_range;</span>
- proxy_pass http://127.0.0.1:8081/media_store.php/tmp-test/;
- }
问题四,当支持range加载后,proxy_cache_key,则需要重新配置:
问题五:如何配置-返回过期时间
参数 | 正常请求 | range请求 |
返回过期时间 | 返回 | 不返回 |
- location /media_store.php {
- fastcgi_pass 127.0.0.1:9000;
- fastcgi_index media_store.php;
- fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
- include fastcgi_params;
- if ( $http_range = ''){
- expires 2592000s;
- }
- }
问题七:缓存命中情况如何在http头中体现,以及在nginx日志中查看
总结:
整个一套完备的缓存策略就介绍到此,这套方案中不仅实现了基本的缓存配置,还解决了实际场景应用中会遇到的,磁盘扩展,缓存清理,断点续传,缓存过期时间,缓存命中提示等问题,只要将这套方案灵活运用,不管是再复杂的场景,基本都能满足需求。以上都是我在工作中爬过的坑,不断完善总结出的结果,希望对读者能有帮助。