深入理解连接池

长连接最大的问题就是连接失效。

redis/mysql等服务器为什么会限制最大连接数

内核角度,过多的TCP连接非常的不环保

首先,过多的内存占用,尤其是阻塞的情况下。

例如如下代码。

服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

$serv = new Swoole\Server("127.0.0.1", 6666, SWOOLE_BASE);
$serv->set(array(
'worker_num' => 1,
));

$serv->on('connect', function ($serv, $fd) {
var_dump("Client: Connect $fd");
});

$serv->on('receive', function ($serv, $fd, $reactor_id, $data) {
var_dump(strlen($data));
sleep(100000);
});

$serv->on('close', function ($serv, $fd) {
});

$serv->start();

代码很简单,服务器在第一次收到数据的时候,就调用sleep函数阻塞起来。(因为只有一个worker进程,所以后面不会处理客户端发来的其他数据)

客户端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

use Swoole\Coroutine\Socket;

$socket = new Socket(AF_INET, SOCK_STREAM, 0);

go(function () use ($socket) {
$socket->connect('127.0.0.1', 6666);

$i = 0;
while ($i++ < 100000000) {
var_dump($socket->send(str_repeat("1", 1024)));
}
echo "done\n";
});

代码很简单,就是创建一个协程,然后创建一个客户端,往服务器发送数据。一共发送了100000000 * 1024字节的数据。

我们先启动服务器:

1
2
[root@aaeb2c267d0f test]# php server.php

然后再启动客户端:

1
2
3
4
5
6
7
8
9
[root@aaeb2c267d0f test]# php client.php
# 省略其他的输出
int(1024)
int(1024)
int(1024)
int(1024)
int(1024)
int(617)

我们发现,客户端程序会阻塞住。

我们再来看看服务器端这边的输出:

1
2
3
4
[root@aaeb2c267d0f test]# php server.php
string(17) "Client: Connect 1"
int(6144)

我们发现,服务器读取到了6144字节的数据之后,就没有继续再读取了。(我们发现,服务器端读取到的字节大小不一定是1024,这也说明了TCP是面向字节流的,每次调用send函数,数据不一定就会立马发送过去。同理,服务器端也不会收到数据后就立马读取)

此时,我们关闭客户端这边的进程:

1
2
^C
[root@aaeb2c267d0f test]#

然后查看socket内核缓存区的状态:

1
2
3
4
5
6
7
8
[root@aaeb2c267d0f test]# cat /proc/net/sockstat
sockets: used 177
TCP: inuse 7 orphan 1 tw 0 alloc 15 mem 1211
UDP: inuse 1 mem 2
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0
[root@aaeb2c267d0f test]#

我们发现内核缓冲区是有数据没有被清空的。

所以我们发现,当服务器进程因为一个socket而被阻塞起来了,那么后续的连接都无法被处理,导致内存占用严重。

其次,过多的端口占用、fd占用。我们来看一个TCP自连接的问题。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

use Swoole\Coroutine\Socket;

$socket = new Socket(AF_INET, SOCK_STREAM, 0);

go(function () use ($socket) {
for ($i = 1; $i < 65536; $i++) {
if ($socket->connect('127.0.0.1', $i)) {
echo "connected\n";
break;
}
}

var_dump($i);
});

执行结果如下:

1
2
3
4
[root@aaeb2c267d0f test]# php client.php
connected
int(43991)
[root@aaeb2c267d0f test]#

我们发现,如果我们不限制连接的个数,那么客户端很可能就会耗尽可用的端口。可用的端口范围可以通过如下命令查看:

1
2
[root@aaeb2c267d0f test]# sysctl -a | grep ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999

传输效率问题

因为长连接可以减少TCP三次握手的时间,所以长连接的效率会高于短连接(短连接每次发送数据需要进行三次握手)。

综上,我们要控制长连接的数量,也就是说在限制了最大连接数的情况下,连接弥足珍贵。那我们确实有那么多进程怎么办,只能尽可能提高利用率,共享连接。

共享连接池

多协程共享连接

当协程需要使用一个连接的时候,可以从Channel里面pop出来一个连接。

多进程共享连接

跨机器共享连接