我正在开发一些模拟网络设备的代码。我需要运行数千个模拟"代理",每个代理都需要连接到一个服务。问题在于,在打开1023个连接后,连接开始超时,整个系统崩溃了。
主要的代码是用Go语言编写的,但我编写了一个非常简单的Python脚本来重现这个问题。
有些不寻常的是,我们需要在创建套接字时设置本地地址。这是因为代理连接到的设备希望显式IP与我们所说的IP相匹配。为了实现这一点,我配置了10000个虚拟接口(eth0:1到eth0:10000),并为它们分配了私有网络中的唯一IP地址。
Python脚本只是这样的(仅运行到2000个连接):
如果我不使用本地地址运行,则AF_INET绑定会消失,它可以工作。
因此,似乎必须有一些限制可以进行绑定。我已经浏览了关于Linux上TCP调整的各种链接,并尝试了tcp_tw_reuse/recycle和减少fin_timeout等操作,以及其他一些我无法记住的操作。
这是在Ubuntu Linux上运行的(11.04,内核2.6.38(64位)。它是VMWare ESX集群上的虚拟机。
就在发布这篇文章之前,我尝试了运行第二个Python脚本实例,其中一个附加在1.30.1.1处。第一个脚本通过了1023个连接,但第二个脚本甚至无法完成第一个脚本,这表明问题与大量虚拟接口有关。某些内部数据结构可能受到限制吗?某些最大内存设置在某个地方?
有谁能想到Linux中可能会导致这种情况的限制吗?
更新:
今天早上我决定尝试一个实验。我修改了Python脚本,将“main”接口IP用作源IP,并在10000+范围内使用临时端口。现在脚本看起来像这样:
主要的代码是用Go语言编写的,但我编写了一个非常简单的Python脚本来重现这个问题。
有些不寻常的是,我们需要在创建套接字时设置本地地址。这是因为代理连接到的设备希望显式IP与我们所说的IP相匹配。为了实现这一点,我配置了10000个虚拟接口(eth0:1到eth0:10000),并为它们分配了私有网络中的唯一IP地址。
Python脚本只是这样的(仅运行到2000个连接):
import socket
i = 0
for b in range(10, 30):
for d in range(1, 100):
i += 1
ip = "1.%d.1.%d" % (b, d)
print("Conn %i %s" % (i, ip))
s = socket.create_connection(("1.6.1.1", 5060), 10, (ip, 5060))
如果我删除socket.create_connection的最后一个参数(源地址),那么我可以获得所有2000个连接。
使用本地地址不同的是,必须在建立连接之前进行绑定,因此在strace下运行此程序的输出如下:
Conn 1023 1.20.1.33
bind(3, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(5060), sin_addr=inet_addr("1.20.1.33")}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(5060), sin_addr=inet_addr("1.6.1.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
如果我不使用本地地址运行,则AF_INET绑定会消失,它可以工作。
因此,似乎必须有一些限制可以进行绑定。我已经浏览了关于Linux上TCP调整的各种链接,并尝试了tcp_tw_reuse/recycle和减少fin_timeout等操作,以及其他一些我无法记住的操作。
这是在Ubuntu Linux上运行的(11.04,内核2.6.38(64位)。它是VMWare ESX集群上的虚拟机。
就在发布这篇文章之前,我尝试了运行第二个Python脚本实例,其中一个附加在1.30.1.1处。第一个脚本通过了1023个连接,但第二个脚本甚至无法完成第一个脚本,这表明问题与大量虚拟接口有关。某些内部数据结构可能受到限制吗?某些最大内存设置在某个地方?
有谁能想到Linux中可能会导致这种情况的限制吗?
更新:
今天早上我决定尝试一个实验。我修改了Python脚本,将“main”接口IP用作源IP,并在10000+范围内使用临时端口。现在脚本看起来像这样:
import socket
i = 0
for i in range(1, 2000):
print("Conn %i" % i)
s = socket.create_connection(("1.6.1.1", 5060), 10, ("1.1.1.30", i + 10000))
这个脚本很好用,所以我认为问题与大量别名IP地址有关。
bind
上,你的strace
返回了非零值吗? - Karl Bielefeldtselect()
(如果是,则切换到poll()
)? - ninjaljbind(3, {sa_family=AF_INET, sin_port=htons(5060), sin_addr=inet_addr("1.40.1.33")}, 16) = 0 connect(3, {sa_family=AF_INET, sin_port=htons(5060), sin_addr=inet_addr("1.6.1.1")}, 16) = -1 EINPROGRESS (Operation now in progress) poll([{fd=3, events=POLLOUT}], 1, 10000) = 0 (Timeout)
- Hisnessness