Nginx有两种方式实现TCP代理功能:
一种是使用nginx_tcp_proxy_module模块,一般用于Nginx早期版本。
一种是使用ngx_stream_core_module模块,用于1.9及其以后版本。
本文介绍后者,即使用stream的方式来实现TCP代理。
一、Linux CentOS安装与使用
1.搭建nginx编译环境:
yum -y install gcc gcc-c++ autoconf automake
yum -y install zlib zlib-devel openssl openssl-devel pcre-devel
# 安装 pcre库是为了使 nginx 支持具备 URI 重写功能的 rewrite 模块,如果不安装 pcre 库,则 nginx 无法使用 rewrite 模块功能。
# nginx 在使用HTTPS服务的时候要用到此模块,如果不安装 openssl 相关包,安装 nginx 的过程中会报错。openssl 系统默认已经安装,只需要安装 openssl-devel 即可。
2.下载nginx源文件,http://nginx.org/download/,选择文件nginx-1.12.2.tar.gz
tar -zxf nginx-1.12.2.tar.gz
3.编译,添加tcp支持
参照官方说明,编译参数中添加--with-steam即可,只有在configure时使用了--with-stream参数,编译出来的nginx程序才支持stream方式实现TCP代理。
进入到nginx源文件目录下,默认路径是nginx path prefix: "/usr/local/nginx",不建议修改。
./configure --prefix=/opt/nginx --with-stream --with-http_stub_status_module
make
make install
4.进入/opt/nginx/conf/nginx.conf,参数配置如下,注意在末尾添加一个stream模块(它和http等同级),这个模块,类似于http和mail模块,允许我们配置一组监听TCP连接的服务。允许你配置多个服务的TCP连接,通过在upstream的server组中配置proxy_pass指令。
event {
worker_connections 1024;
multi_accept on;
use epoll;
}
stream {
upstream backend {
server 172.16.6.161:1883 max_fails=3 fail_timeout=30s;
server 172.16.6.161:1888;
server 172.16.6.162:1883;
}
server {
listen 1884;
proxy_connect_timeout 3s;
proxy_timeout 300s;
proxy_pass backend;
}
}
上述event配置中各参数的含义如下:
(1)worker_connections 指定最大可以同时接收的连接数量,这里一定要注意,最大连接数量是和worker processes共同决定的。
(2)multi_accept 配置指定nginx在收到一个新连接通知后尽可能多的接受更多的连接
(3)use epoll 配置指定了线程轮询的方法,如果是linux2.6+,使用epoll,如果是BSD如Mac请使用Kqueue
上述stream配置中各参数的含义如下:
(1)listen指定Nginx将在tcp 1884端口上监听
(2)proxy_connect_timeout指定Nginx与被代理的主机之间建立连接的超时时间。
Syntax: proxy_connect_timeout;
Default:
proxy_connect_timeout 60s;
Context: stream, server
(3)proxy_timoute指定Nginx与客户端,以及Nginx与被代理的主机之间在两个相邻的读或写数据的操作之间的最大时间间隔。超过此时间间隔而没有数据读或写操作发生,则断开连接。--firecat个人建议时间设置长一点,否则tcp连接会断开。
Syntax: proxy_timeout ;
Default:
proxy_timeout 10m;
Context: stream, server
(4)proxy_pass指定了被代理的主机的地址和端口号。
5.进入/opt/nginx/sbin/,有nginx执行文件。查看编译时的参数
./nginx -V
6.检查nginx.conf配置文件语法,可以防止因配置错误导致网站重启或重新加载配置等对用户的影响
./nginx -t
7.启动nginx
./nginx
8.查看nginx是否启动成功
netstat -lnpt | grep nginx
或者ps -ef |grep nginx 查看nginx进程是否启动【推荐】
或者netstat -nalp|grep 80 查看80端口的进程
9.重启nginx
./nginx -s reload
10.终止nginx
kill -9 <nginx进程号>(一般有2~3个进程,通过netstat -lnpt | grep nginx命令可以查询进程号)
例如kill -9 36533
二、实测情况
1、我只拿一台服务器做测试,IP地址是172.16.6.161。
(1)配置Nginx服务器的TCP端口是1884
(2)配置工作服务器1的TCP端口是1883
(3)配置工作服务器2的TCP端口是1888
客户端向Nginx服务器发起TCP连接(172.16.6.161,1884),一切正常工作起来。
2、我把event的参数worker_connections调整为10,结果tcp连接成功的只有3个,说明Linux文件描述符1~10之间,可使用的只有3个。
#查看文件描述符使用情况的命令是:
lsof -i -n -P
lsof -i -n -P | grep :1884
#查看1884端口的连接情况,观察TCP状态图
netstat -nalp|grep 1884
#查看1884端口的客户端连接数
netstat -nalp|grep 1884|wc -l
#修改当前进程的最大文件数
ulimit -n 102400
3、我实测用client向nginx服务器发起100个tcp连接,nginx也会相应与上游real server建立100个tcp连接。另外,发起1万个,2万个和4万个,结论一致。我用netstat -nalp|grep 1884|wc -l 和 netstat -na|grep ESTABLISHED|wc -l 命令以及wireshark工具发现:client会向nginx建立tcp连接,nginx也会向上游服务器建立tcp连接,且前端和后端tcp连接数目相同;wireshark可以抓包,查看三次握手的过程。
4、测试使用的工具和源码
我个人的Linux TCP Server测试源码,C语言
TCP_UDP_PerformanceTest
强大的TcpServer压力测试工具源码(附突破连接限制的方法和工具)
TCPCOPY:https://github.com/session-replay-tools/tcpcopy
三、个人总结
1、HTTP负载均衡,也就是我们通常所有“七层负载均衡”,工作在第七层“应用层”。而TCP负载均衡,就是我们通常所说的“四层负载均衡”,工作在“网络层”和“传输层”。例如,LVS(Linux Virtual Server,Linux虚拟服务)和F5(一种硬件负载均衡设备),也是属于“四层负载均衡”。
四层负载均衡:仅仅建立一次TCP连接。
七层负载均衡:负载均衡器与客户端及后端的服务器会分别建立一个TCP连接。即两次TCP连接。
关于四层负载均衡,我发现这个结论对Nginx而言不是,Nginx仍然是分别建立一次TCP连接。但是这样会有一个问题?Nginx服务器向工作服务器发起TCP连接,会受到端口号65535的局限?即nginx作为四层负载时,它与client,上游服务器都是维持tcp长连接吗?是否client每来一个,nginx就分别与client和上游连接一次?两边连接数目相同?那这样,nginx主动连接上游岂不是会有tcp端口数的限制,65535?
2、个人认为,haproxy/Nginx都存在TCP源端口耗尽问题,haproxy/Nginx作为反向代理,会使用自己的IP地址作为源地址连接后端的服务器。根据TCP协议,无论任何类型操作系统都只能拥有64K个左右的源TCP端口,用于向外发起TCP连接。
3、那怎么解决端口耗尽问题?
Haproxy是可以的,Nginx不妨也试一试,详情见博客:
我个人的Haproxy-1.7.9实践:安装,编译与测试(★firecat推荐,针对TCP四层负载均衡★)
配置haproxy支持使用多个lan内网ip做负载均衡以突破haproxy机只支持64k连接(突破单ip 65535端口限制)haproxy TCP源端口耗尽问题(单个网卡最多65535个端口)
物联网架构成长之路(10)-Nginx负载均衡 -- 里面有讲通过添加虚拟网卡来实现多IP
4、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参数,防止过早地断开连接。
5、服务健壮性监控
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相对比较重。
四、别人家的配置
worker_processes auto;
worker_cpu_affinity auto;
error_log logs/error.log error;
pid logs/nginx.pid;
worker_rlimit_nofile 100000;
events
{
use epoll;
worker_connections 5000;
}
stream {
upstream server {
hash $remote_addr consistent;
server 172.16.1.11:8081 weight=1 max_fails=3 fail_timeout=10s;
server 172.16.1.22:8081 weight=1 max_fails=3 fail_timeout=10s;
}
server {
listen 8081;
proxy_connect_timeout 1s;
proxy_timeout 30s;
proxy_pass server;
} }
ps -ef |grep nginx 查看nginx进程是否启动