我最近正在尝试建立一个推送系统。为了提高系统的可扩展性,最佳做法是使每个连接尽可能无状态。因此,当出现瓶颈时,通过添加更多机器可以轻松扩展整个系统的容量。说到负载平衡和反向代理,Nginx可能是最着名和最公认的。但是,TCP代理是一个相当新的事情。 Nginx从v1.9引入了TCP负载均衡和反向代理,v1.9于今年5月下旬发布,具有大量缺失功能。另一方面,HAProxy作为TCP加载balacing的先驱,相当成熟和稳定。我选择使用HAProxy来构建系统,最终我达到了300k并发tcp套接字连接的结果。如果不是因为我过时的客户端PC,我本可以获得更高的数字。
调整Linux系统
即使是高端服务器PC,300k并发连接也不是一件容易的事。首先,我们需要调整linux内核配置以充分利用我们的服务器。
文件描述符
由于套接字被认为等同于系统透视图中的文件,因此对于我们的300k目标,默认文件描述符限制相当小。修改/etc/sysctl.conf以添加以下行:1
2fs.file-max = 10000000
fs.nr_open = 10000000
这些行将文件描述符的总数增加到100万。 接下来,修改/etc/security/limits.conf以添加以下行:1
2
3
4* soft nofile 10000000
* hard nofile 10000000
root soft nofile 10000000
root hard nofile 10000000
TCP Buffer
持有如此大量的连接会耗费大量内存。要减少内存使用,请修改/etc/sysctl.conf以添加以下行.1
2
3net.ipv4.tcp_mem = 786432 1697152 1945728
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216
调整HAProxy
完成调整Linux内核后,我们需要调整HAProxy以更好地满足我们的要求。1
maxconn 2000000
然后我们在后端范围添加相同的行,这使得我们的后端看起来像这样:1
2
3
4backend pushserver
mode tcp
balance roundrobin
maxconn 2000000
调整超时
默认情况下,HAProxy将检测死连接并关闭不活动连接。但是,默认的keepalive阈值太低,并且适用于必须以长拉方式保持连接的情况。在我的客户端,我的长插槽连接到推送服务器始终由HAProxy关闭,因为我的客户端实现中的心跳是4分钟。太频繁的心跳对于客户端(实际上是Android设备)和服务器来说都是沉重的负担。要增加此限制,请将以下行添加到后端。默认情况下,这些数字都以毫秒为单位。1
2
3timeout connect 5000
timeout client 50000
timeout server 50000
配置多IP以解决端口耗尽问题
您面临同时30k连接时,您将遇到“端口耗尽”的问题。这是因为每个反向代理连接将占用本地IP的可用端口。可用于传出连接的默认IP范围约为30k~60k。换句话说,我们只有一个IP可用的30k端口。这还不够。我们可以通过修改/etc/sysctl.conf来增加此范围,以添加以下行。1
net.ipv4.ip_local_port_range = 1000 65535
但这并没有解决根本问题,当达到60k上限时,我们仍然会耗尽端口。
此端口耗尽问题的最终解决方案是增加可用IP的数量。首先,我们将新IP绑定到新的虚拟网络接口。1
2
3
4ifconfig eth0:1 192.168.8.1
ifconfig eth0:1 192.168.8.2
ifconfig eth0:1 192.168.8.3
...
此命令将Intranet地址绑定到其硬件接口为eth0的虚拟网络接口eth0:1。可以多次执行此命令以添加任意数量的虚拟网络接口。请记住,IP应该位于真实应用程序服务器的同一子网中。换句话说,您在HAProxy和应用程序服务器之间的链接中不能有任何类型的NAT服务。否则,这将无效。
接下来,我们需要配置HAProxy以使用这些新IP。有一个源命令可以在后端作用域中使用,也可以作为服务器命令的参数使用。在我们的实验中,后端范围似乎不起作用,因此我们选择了参数1。这就是HAProxy配置文件的样子。1
2
3
4
5
6
7
8
9
10
11backend mqtt
mode tcp
balance roundrobin
maxconn 2000000
server app1 127.0.0.1:1883 source 192.168.8.1
server app2 127.0.0.1:1883 source 192.168.8.2
server app3 127.0.0.1:1883 source 192.168.8.3
server app4 127.0.0.1:1884 source 192.168.8.4
server app5 127.0.0.1:1884 source 192.168.8.5
server app6 127.0.0.1:1884 source 192.168.8.6
server app6 127.0.0.1:1884 source 192.168.8.7:1025-65000
这是诀窍,您需要在多个条目中声明它们并为它们提供不同的应用程序名称。如果为所有四个条目设置相同的应用程序名称,则HAProxy将无效。如果您可以查看HAProxy状态报告的输出,您会看到即使这些条目具有相同的后端地址,HAProxy仍将它们视为不同的应用程序
这就是配置!现在你的HAProxy应该能够处理超过300k的并发TCP连接,就像我的一样。
复用处于TIME_WAIT的端口
1 | net.ipv4.tcp_tw_reuse = 1 |
第一个参数很安全,可以不用过多关注。需要注意的是第二个参数,某些情况下会导致数据包被丢弃。
例如:client通过NAT连接haproxy,并且haproxy端打开了tcp_tw_recycle,同时saw_tstamp也没有关闭,当第一个连接建立并关闭后,此端口(句柄)处于TIME_WAIT状态,在2*MSL时间内又一个client(相同IP,如果打开了xfrm还要相同PORT)发一个syn包,此时linux内核就会认为这个数据包异常,从而丢掉这个包,并发送rst包.
不过通常情况下,client都是通过内网直接连接haproxy,所以可以认为tcp_tw_recycle是安全的,只是需要记住此坑。
缩短TIME_WAIT时间
Linux系统默认MSL为60秒,也就是正常情况下,120秒后处于TIME_WAIT的端口(句柄)才会释放,可以将MSL的时间缩小,缩短端口的释放周期。1
2
3 cat /proc/sys/net/ipv4/tcp_fin_timeout
60
echo 15 > /proc/sys/net/ipv4/tcp_fin_timeout
这是一个折中的数值,太小也会导致其它问题