Nginx基于TCP/UDP端口的四层负载均衡(stream模块)配置梳理
通过我们会用Nginx的upstream做基于http/https端口的7层负载均衡,由于Nginx老版本不支持tcp协议,所以基于tcp/udp端口的四层负载均衡一般用LVS或Haproxy来做。至于4层负载均衡和7层负载均衡的区别,可以参考:http://www.cnblogs.com/kevingrace/p/6137881.html。然而Nginx从1.9.0版本开始,新增加了一个stream模块,用来实现四层协议的转发、代理或者负载均衡等,鉴于Nginx在7层负载均衡和web service上的成功,和Nginx良好的框架,stream模块前景一片光明。官方文档:http://nginx.org/en/docs/stream/ngx_stream_core_module.html
Nginx的stream模块默认不会自带安装,需要编译安装的时候手动添加上这个模块。废话不多说了,下面介绍下一个自己使用stream做四层负载均衡(socket协议代理)的案例:
在此之前已经使用Ningx+Keepalived(主从模式)做了7层负载均衡的LB环境,之前编译的时候,没有加上这个stream模块,所以需要后续手动添加该模块。 由于Nginx的LB已经有业务跑在上面,可以选择平滑添加stream模块,并不会对线上业务造成多大影响。步骤如下: 1)先在LB的slave从机上进行平滑添加,然后再将vip切换到从机上,随即在对master主机进行平滑添加该模块。 2)平滑添加即是重新configure编译的时候加上--with-stream,接着make。 3)千万注意,make之后,不要make install,否则会覆盖掉之前的配置!!! -------------------------------------------------------------------------------------------------- 由于本人的LB环境升级了openssl版本,再添加--with-stream重新编译的时候报了错误,具体可参考: http://www.cnblogs.com/kevingrace/p/8058535.html -------------------------------------------------------------------------------------------------- 检查下,发现nginx没有安装stream模块 [root@external-lb01 ~]# /data/nginx/sbin/nginx -V nginx version: nginx/1.12.2 built by gcc 4.4.7 20120313 (Red Hat 4.4.7-18) (GCC) built with OpenSSL 1.1.0g 2 Nov 2017 TLS SNI support enabled configure arguments: --prefix=/data/nginx --user=www --group=www --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre 操作之前,一定要备份一下之前的nginx安装目录,防止操作失败进行回滚! [root@external-lb01 ~]# cp -r /data/nginx /mnt/nginx.bak 之前的编译命令是: [root@external-lb01 vhosts]# cd /data/software/nginx-1.12.2 [root@external-lb01 nginx-1.12.2]# ./configure --prefix=/data/nginx --user=www --group=www --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre 现在需要手动添加stream,编译命令如下: [root@external-lb01 vhosts]# cd /data/software/nginx-1.12.2 [root@external-lb01 nginx-1.12.2]# ./configure --prefix=/data/nginx --user=www --group=www --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre --with-stream [root@external-lb01 nginx-1.12.2]# make [root@external-lb01 nginx-1.12.2]# cp -f /data/software/nginx-1.12.2/objs/nginx /data/nginx/sbin/nginx [root@external-lb01 nginx-1.12.2]# pkill -9 nginx [root@external-lb01 nginx-1.12.2]# /data/nginx/sbin/nginx 检查下,发现nginx已经安装了stream模块了 [root@external-lb01 nginx-1.12.2]# /data/nginx/sbin/nginx -V nginx version: nginx/1.12.2 built by gcc 4.4.7 20120313 (Red Hat 4.4.7-18) (GCC) built with OpenSSL 1.1.0g 2 Nov 2017 TLS SNI support enabled configure arguments: --prefix=/data/nginx --user=www --group=www --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre --with-stream --with-openssl=/usr/local/ssl ====================================================================================================================== stream的4层负载均衡和upstream的7层负载均衡可以共同配置在nginx中,stream模块用法和http模块差不多,关键的是语法几乎一致。 具体如下: [root@external-lb01 ~]# cat /data/nginx/conf/nginx.conf user www; worker_processes 8; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 65535; } stream { upstream kk5 { server 10.0.58.22:5100; server 10.0.58.23:5100; } upstream kk5http { server 10.0.58.22:8000; server 10.0.58.23:8000; } upstream kk5https { server 10.0.58.22:8443; server 10.0.58.23:8443; } server { listen 5100; proxy_connect_timeout 1s; proxy_pass kk5; } server { listen 8000; proxy_connect_timeout 1s; proxy_pass kk5http; } server { listen 8443; proxy_connect_timeout 1s; proxy_pass kk5https; } } http { include mime.types; default_type application/octet-stream; charset utf-8; ###### ## set access log format ###### log_format main '$remote_addr $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '$http_user_agent $http_x_forwarded_for $request_time $upstream_response_time $upstream_addr $upstream_status'; ####### ## http setting ####### sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; proxy_cache_path /var/www/cache levels=1:2 keys_zone=mycache:20m max_size=2048m inactive=60m; proxy_temp_path /var/www/cache/tmp; fastcgi_connect_timeout 3000; fastcgi_send_timeout 3000; fastcgi_read_timeout 3000; fastcgi_buffer_size 256k; fastcgi_buffers 8 256k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; fastcgi_intercept_errors on; # client_header_timeout 600s; client_body_timeout 600s; # client_max_body_size 50m; client_max_body_size 100m; client_body_buffer_size 256k; gzip on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_http_version 1.1; gzip_comp_level 9; gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php; gzip_vary on; ## includes vhosts include vhosts/*.conf; } [root@external-lb01 ~]# cd /data/nginx/conf/vhosts/ [root@external-lb01 vhosts]# ls -rw-r-xr-- 1 root root 889 12月 26 15:18 bpm.kevin.com.conf -rw-r-xr-- 1 root root 724 12月 26 14:38 mobi.kevin.com.conf [root@external-lb01 vhosts]# cat bpm.kevin.com.conf upstream os-8080 { ip_hash; server 10.0.58.20:8080 max_fails=3 fail_timeout=15s; server 10.0.58.21:8080 max_fails=3 fail_timeout=15s; } server { listen 80; server_name bpm.kevin.com; access_log /data/nginx/logs/bpm.kevin.com-access.log main; error_log /data/nginx/logs/bpm.kevin.com-error.log; location / { proxy_pass http://os-8080; proxy_set_header Host $host; proxy_redirect http://os-8080/ http://bpm.kevin.com/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_next_upstream error timeout invalid_header http_502 http_503 http_504; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } [root@external-lb01 vhosts]# cat mobi.kevin.com.conf upstream mobi_cluster{ server 10.0.54.20:8080; } server { listen 80; server_name mobi.kevin.com; access_log /data/nginx/logs/mobi.kevin.com-access.log main; error_log /data/nginx/logs/mobi.kevin.com-error.log; location / { proxy_pass http://mobi_cluster; proxy_set_header Host $host; proxy_redirect http://mobi_cluster/ http://mobi.kevin.com/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } 关闭防火墙,否则要依次打开如上配置中的端口! [root@external-lb01 vhosts]# /data/nginx/sbin/nginx -s reload 重启nginx后,发现http端口80、8080、8000、8443都起来了(lsof命令可以查看到),而tcp/udp端口5100没有起来,这是正常的。
==========顺便总结下Nginx的四大模块——proxy、headers、upstream、stream模块梳理=========
一、ngx_http_proxy_module模块
1)proxy_pass URL; Context: location, if in location, limit_except 注意:proxy_pass后面的路径不带uri时,其会将location的uri传递给后端主机 server { … server_name HOSTNAME; location /uri/ { proxy http://hos[:port]; } … } http://HOSTNAME/uri –> http://host/uri proxy_pass后面的路径是一个uri时,其会将location的uri替换为proxy_pass的uri server { … server_name HOSTNAME; location /uri/ { proxy http://host/new_uri/; } … } http://HOSTNAME/uri/ –> http://host/new_uri/ 如果location定义其uri时使用了正则表达式的模式,则proxy_pass之后必须不能使用uri; 用户请求时传递的uri将直接附加代理到的服务的之后 server { … server_name HOSTNAME; location ~|~* /uri/ { proxy http://host; } … } http://HOSTNAME/uri/ –> http://host/uri/ 2)proxy_set_header field value; 设定发往后端主机的请求报文的请求首部的值;Context: http, server, location proxy_set_header X-Real-IP $remote_addr; $remote_addr:记录的是上一台主机的IP,而上一台主机有可能也是代理服务器 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; $proxy_add_x_forwarded_for:记录的是源IP地址 在http客户端还有修改/etc/httpd/conf/httpd.conf文件 LogFormat "%{X-Real-IP}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 通过上述方法则可以在后端主机上记录真实的httpd资源请求者,而不再是只记录前端代理服务器的IP地址 3)proxy_cache_path 定义可用于proxy功能的缓存;Context: http 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 /var/cache/nginx/proxy_cache levels=1:2:1 keys_zone=gmtest:20M max_size=1G; 4)proxy_cache zone | off; 指明要调用的缓存,或关闭缓存机制;Context: http, server, location proxy_cache gmtest; 5)proxy_cache_key string; 缓存中用于“键”的内容; 默认值:proxy_cache_key $scheme$proxy_host$request_uri; 建议定义成方法和url 6)proxy_cache_valid [code …] time; 定义对特定响应码的响应内容的缓存时长; 定义在http{…}中; proxy_cache_path /var/cache/nginx/proxy_cache levels=1:1:1 keys_zone=gmtest:20m max_size=1g; 定义在需要调用缓存功能的配置段,例如server{…},或者location中; proxy_cache gmtest; proxy_cache_key $request_uri; proxy_cache_valid 200 302 301 1h; proxy_cache_valid any 1m; 7)proxy_cache_use_stale proxy_cache_use_stale error | timeout | invalid_header | updating | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | off …; Determines in which cases a stale cached response can be used when an error occurs during communication with the proxied server. 后端服务器的故障在那种情况下,就使用缓存的功能对客户的进行返回 8)proxy_cache_methods GET | HEAD | POST …; If the client request method is listed in this directive then the response will be cached. “GET” and “HEAD” methods are always added to the list, though it is recommended to specify them explicitly. 默认方法就是GET HEAD方法 9)proxy_hide_header field; By default, nginx does not pass the header fields “Date”, “Server”, “X-Pad”, and “X-Accel-…” from the response of a proxied server to a client. The proxy_hide_header directive sets additional fields that will not be passed. 10)proxy_connect_timeout time; Defines a timeout for establishing a connection with a proxied server. It should be noted that this timeout cannot usually exceed 75 seconds. 默认为60s 11)buffer相关的配置 a:proxy_buffer_size size; Sets the size of the buffer used for reading the first part of the response received from the proxied server. This part usually contains a small response header. By default, the buffer size is equal to one memory page. 默认为4k|8k b:proxy_buffering on | off; Enables or disables buffering of responses from the proxied server. 默认为on c:proxy_buffers number size; Sets the number and size of the buffers used for reading a response from the proxied server, for a single connection. By default, the buffer size is equal to one memory page. 默认为8 4k|8k d:proxy_busy_buffers_size size; When buffering of responses from the proxied server is enabled, limits the total size of buffers that can be busy sending a response to the client while the response is not yet fully read. 默认为8k|16k
二、ngx_http_headers_module模块
The ngx_http_headers_module module allows adding the “Expires” and “Cache-Control” header fields, and arbitrary fields, to a response header. 向由代理服务器响应给客户端的响应报文添加自定义首部,或修改指定首部的值; 1)add_header name value [always]; 添加自定义首部; add_header X-Via $server_addr; 经由的代理服务器地址 add_header X-Accel $server_name; 2)expires [modified] time; expires epoch | max | off; 用于定义Expire或Cache-Control首部的值; 可以把服务器定义的缓存时长修改了;
三、ngx_http_upstream_module模块
The ngx_http_upstream_module module is used to define groups of servers that can be referenced by the proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, and memcached_pass directives. 1)upstream name { … } 定义后端服务器组,会引入一个新的上下文;Context: http upstream httpdsrvs { server … server… … } 2)server address [parameters]; 在upstream上下文中server成员,以及相关的参数;Context: upstream address的表示格式: unix:/PATH/TO/SOME_SOCK_FILE IP[:PORT] HOSTNAME[:PORT] parameters: weight=number 权重,默认为1;默认算法是wrr max_fails=number 失败尝试最大次数;超出此处指定的次数时,server将被标记为不可用 fail_timeout=time 设置将服务器标记为不可用状态的超时时长 max_conns 当前的服务器的最大并发连接数 backup 将服务器标记为“备用”,即所有服务器均不可用时此服务器才启用 down 标记为“不可用” 先在nginx前端配置down,然后在下架后端服务器,上架新的web程序,然后上架,在修改配置文件立马的down 3)least_conn; 最少连接调度算法,当server拥有不同的权重时其为wlc 要在后端服务器是长连接时,效果才好,比如mysql 4)ip_hash; 源地址hash调度方法 5)hash key [consistent]; 基于指定的key的hash表来实现对请求的调度,此处的key可以直接文本、变量或二者的组合 作用:将请求分类,同一类请求将发往同一个upstream server If the consistent parameter is specified the ketama consistent hashing method will be used instead. 示例: hash $request_uri consistent; hash $remote_addr; hash $cookie_name; 对同一浏览器的请求,发往同一个upstream server 6)keepalive connections; 为每个worker进程保留的空闲的长连接数量 nginx的其它的二次发行版: tengine OpenResty 1.9版本之后可以反代tcp/udp的协议,基于stream模块,工作与传输层
四、ngx_stream_core_module模块(实现Nginx的TCP负载均衡)
HTTP负载均衡,也就是我们通常所有"七层负载均衡",工作在第七层"应用层"。而TCP负载均衡,就是我们通常所说的"四层负载均衡",工作在"网络层"和"传输层"。例如,LVS(Linux Virtual Server,Linux虚拟服务)和F5(一种硬件负载均衡设备),也是属于"四层负载均衡"
nginx-1.9.0 已发布,该版本增加了stream 模块用于一般的TCP 代理和负载均衡,ngx_stream_core_module 这个模块在1.90版本后将被启用。但是并不会默认安装, 需要在编译时通过指定 --with-stream 参数来激活这个模块。 1)配置Nginx编译文件参数 ./configure --with-http_stub_status_module --with-stream ------------------------------------------------------------------ 2)编译、安装,make && make install ------------------------------------------------------------------ 3)配置nginx.conf文件 stream { upstream kevin { server 192.168.10.10:8080; #这里配置成要访问的地址 server 192.168.10.20:8081; server 192.168.10.30:8081; #需要代理的端口,在这里我代理一一个kevin模块的接口8081 } server { listen 8081; #需要监听的端口 proxy_timeout 20s; proxy_pass kevin; } } 创建最高级别的stream(与http同一级别),定义一个upstream组 名称为kevin,由多个服务组成达到负载均衡 定义一个服务用来监听TCP连接(如:8081端口), 并且把他们代理到一个upstream组的kevin中,配置负载均衡的方法和参数为每个server;配置些如:连接数、权重等等。 首先创建一个server组,用来作为TCP负载均衡组。定义一个upstream块在stream上下文中,在这个块里面添加由server命令定义的server,指定他的IP地址和 主机名(能够被解析成多地址的主机名)和端口号。下面的例子是建立一个被称之为kevin组,两个监听1395端口的server ,一个监听8080端口的server。 upstream kevin { server 192.168.10.10:8080; #这里配置成要访问的地址 server 192.168.10.20:8081; server 192.168.10.30:8081; #需要代理的端口,在这里我代理一一个kevin模块的接口8081 } 需要特别注意的是: 你不能为每个server定义协议,因为这个stream命令建立TCP作为整个 server的协议了。 配置反向代理使Nginx能够把TCP请求从一个客户端转发到负载均衡组中(如:kevin组)。在每个server配置块中 通过每个虚拟server的server的配置信息和在 每个server中定义的监听端口(客户端需求的代理端口号,如我推流的的是kevin协议,则端口号为:8081)的配置信息和proxy_passs 命令把TCP通信发送到 upstream的哪个server中去。下面我们将TCP通信发送到kevin 组中去。 server { listen 8081; #需要监听的端口 proxy_timeout 20s; proxy_pass kevin; } 当然我们也可以采用单一的代理方式: server { listen 8081; #需要监听的端口 proxy_timeout 20s; proxy_pass 192.168.10.30:8081; #需要代理的端口,在这里我代理一一个kevin模块的接口8081 } ------------------------------------------------------------------ 4)改变负载均衡的方法: 默认nginx是通过轮询算法来进行负载均衡的通信的。引导这个请求循环的到配置在upstream组中server端口上去。 因为他是默认的方法,这里没有轮询命令, 只是简单的创建一个upstream配置组在这儿stream山下文中,而且在其中添加server。 a)least-connected :对于每个请求,nginx plus选择当前连接数最少的server来处理: upstream kevin { least_conn; server 192.168.10.10:8080; #这里配置成要访问的地址 server 192.168.10.20:8081; server 192.168.10.30:8081; #需要代理的端口,在这里我代理一一个kevin模块的接口8081 } b)least time :对于每个链接,nginx pluns 通过几点来选择server的: 最底平均延时:通过包含在least_time命令中指定的参数计算出来的: connect:连接到一个server所花的时间 first_byte:接收到第一个字节的时间 last_byte:全部接收完了的时间 最少活跃的连接数: upstream kevin { least_time first_byte; server 192.168.10.10:8080; #这里配置成要访问的地址 server 192.168.10.20:8081; server 192.168.10.30:8081; #需要代理的端口,在这里我代理一一个kevin模块的接口8081 } c)普通的hash算法:nginx plus选择这个server是通过user_defined 关键字,就是IP地址:$remote_addr; upstream kevin { hash $remote_addr consistent; server 192.168.10.10:8080 weight=5; #这里配置成要访问的地址 server 192.168.10.20:8081 max_fails=2 fail_timeout=30s; server 192.168.10.30:8081 max_conns=3; #需要代理的端口,在这里我代理一一个kevin模块的接口8081 }
=================Nginx的TCP负载均衡====================
Nginx的TCP负载均衡的执行原理
当Nginx从监听端口收到一个新的客户端链接时,立刻执行路由调度算法,获得指定需要连接的服务IP,然后创建一个新的上游连接,连接到指定服务器。
TCP负载均衡支持Nginx原有的调度算法,包括Round Robin(默认,轮询调度),哈希(选择一致)等。同时,调度信息数据也会和健壮性检测模块一起协作,为每个连接选择适当的目标上游服务器。如果使用Hash负载均衡的调度方法,你可以使用$remote_addr(客户端IP)来达成简单持久化会话(同一个客户端IP的连接,总是落到同一个服务server上)。
和其他upstream模块一样,TCP的stream模块也支持自定义负载均和的转发权重(配置“weight=2”),还有backup和down的参数,用于踢掉失效的上游服务器。max_conns参数可以限制一台服务器的TCP连接数量,根据服务器的容量来设置恰当的配置数值,尤其在高并发的场景下,可以达到过载保护的目的。
Nginx监控客户端连接和上游连接,一旦接收到数据,则Nginx会立刻读取并且推送到上游连接,不会做TCP连接内的数据检测。Nginx维护一份内存缓冲区,用于客户端和上游数据的写入。如果客户端或者服务端传输了量很大的数据,缓冲区会适当增加内存的大小。
当Nginx收到任意一方的关闭连接通知,或者TCP连接被闲置超过了proxy_timeout配置的时间,连接将会被关闭。对于TCP长连接,我们更应该选择适当的proxy_timeout的时间,同时,关注监听socke的so_keepalive参数,防止过早地断开连接。
Nginx的TCP负载均衡服务健壮性监控
TCP负载均衡模块支持内置健壮性检测,一台上游服务器如果拒绝TCP连接超过proxy_connect_timeout配置的时间,将会被认为已经失效。在这种情况下,Nginx立刻尝试连接upstream组内的另一台正常的服务器。连接失败信息将会记录到Nginx的错误日志中。
如果一台服务器,反复失败(超过了max_fails或者fail_timeout配置的参数),Nginx也会踢掉这台服务器。服务器被踢掉60秒后,Nginx会偶尔尝试重连它,检测它是否恢复正常。如果服务器恢复正常,Nginx将它加回到upstream组内,缓慢加大连接请求的比例。
之所"缓慢加大",因为通常一个服务都有"热点数据",也就是说,80%以上甚至更多的请求,实际都会被阻挡在"热点数据缓存"中,真正执行处理的请求只有很少的一部分。在机器刚刚启动的时候,"热点数据缓存"实际上还没有建立,这个时候爆发性地转发大量请求过来,很可能导致机器无法"承受"而再次挂掉。以mysql为例子,我们的mysql查询,通常95%以上都是落在了内存cache中,真正执行查询的并不多。
其实,无论是单台机器或者一个集群,在高并发请求场景下,重启或者切换,都存在这个风险,解决的途径主要是两种:
1)请求逐步增加,从少到多,逐步积累热点数据,最终达到正常服务状态。
2)提前准备好"常用"的数据,主动对服务做"预热",预热完成之后,再开放服务器的访问。
TCP负载均衡原理上和LVS等是一致的,工作在更为底层,性能会高于原来HTTP负载均衡不少。但是,不会比LVS更为出色,LVS被置于内核模块,而Nginx工作在用户态,而且,Nginx相对比较重。另外一点,令人感到非常可惜,这个模块竟然是个付费功能。