Verbindungsfehlerproblem
Beispiel
Unter ihnen Redis kommt häufig vor. Der Fehlerbericht lautet:
Konfigurationselement: timeout
Fehlermeldung: Error while reading line from the server
Redis kann konfigurieren, wie viele Sekunden vergehen, wenn der Client nicht sendet Daten an den Redis-Server senden, dann wird die Verbindung geschlossen.
Empfohlenes Lernen: Swoole-Tutorial
Häufige MySQL-Fehler:
Konfigurationselemente: wait_timeout & interactive_timeout
Fehlermeldung: has gone away
Wie der Redis-Server bereinigt auch MySQL regelmäßig unnötige Verbindungen.
So lösen Sie das Problem
1. Stellen Sie die Verbindung wieder her, wenn Sie
2 verwenden
Der Vorteil ist, dass es einfach ist, aber der Nachteil ist, dass es mit dem Problem kurzer Verbindungen konfrontiert ist.
Senden Sie regelmäßig Heartbeats, um die Verbindung aufrechtzuerhalten
Empfohlen.
So halten Sie lange Verbindungen aufrecht
tcp_keepalive
im TCP-Protokoll implementiert
Die unterste Schicht des Betriebssystems stellt eine Reihe von TCP bereit
Konfigurationen:tcp_keepalive_time (integer; default: 7200; since Linux 2.2) The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes. Keep-alives are sent only when the SO_KEEPALIVE socket option is enabled. The default value is 7200 seconds (2 hours). An idle connection is terminated after approximately an additional 11 minutes (9 probes an interval of 75 seconds apart) when keep-alive is enabled. Note that underlying connection tracking mechanisms and application timeouts may be much shorter. tcp_keepalive_intvl (integer; default: 75; since Linux 2.4) The number of seconds between TCP keep-alive probes. tcp_keepalive_probes (integer; default: 9; since Linux 2.2) The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other end. 8
keepalive
Die unterste Schicht von Swoole hat diese Konfigurationen geöffnet, zum Beispiel: ?php $server = new \Swoole\Server('127.0.0.1', 6666, SWOOLE_PROCESS); $server->set([ 'worker_num' => 1, 'open_tcp_keepalive' => 1, 'tcp_keepidle' => 4, // 对应tcp_keepalive_time 'tcp_keepinterval' => 1, // 对应tcp_keepalive_intvl 'tcp_keepcount' => 5, // 对应tcp_keepalive_probes ]);
Darunter:
'open_tcp_keepalive' => 1, // 总开关,用来开启tcp_keepalive 'tcp_keepidle' => 4, // 4s没有数据传输就进行检测 // 检测的策略如下: 'tcp_keepinterval' => 1, // 1s探测一次,即每隔1s给客户端发一个包(然后客户端可能会回一个ack的包,如果服务端收到了这个ack包,那么说明这个连接是活着的) 'tcp_keepcount' => 5, // 探测的次数,超过5次后客户端还没有回ack包,那么close此连接
Lasst uns testen es in der Praxis und erleben Sie es, serverseitiges Skript wie folgt:
<?php $server = new \Swoole\Server('127.0.0.1', 6666, SWOOLE_PROCESS); $server->set([ 'worker_num' => 1, 'open_tcp_keepalive' => 1, // 开启tcp_keepalive 'tcp_keepidle' => 4, // 4s没有数据传输就进行检测 'tcp_keepinterval' => 1, // 1s探测一次 'tcp_keepcount' => 5, // 探测的次数,超过5次后还没有回包close此连接 ]); $server->on('connect', function ($server, $fd) { var_dump("Client: Connect $fd"); }); $server->on('receive', function ($server, $fd, $reactor_id, $data) { var_dump($data); }); $server->on('close', function ($server, $fd) { var_dump("close fd $fd"); }); $server->start();
Wir starten diesen Server:
~/codeDir/phpCode/hyperf-skeleton # php server.php
Dann erfassen Sie das Paket über tcpdump:
~ /codeDir/phpCode/hyperf-skeleton # tcpdump -i lo Port 6666tcpdump: ausführliche Ausgabe unterdrückt, verwenden Sie -v oder -vv für die vollständige Protokolldekodierung
Abhören auf lo, Link-Typ EN10 MB (Ethernet), Erfassungsgröße 262144 Bytes
Wir warten derzeit auf Paketen auf Port 6666 auf lo.
Dann verwenden wir den Client, um eine Verbindung herzustellen:
~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666
Zu diesem Zeitpunkt druckt der Server die Nachricht aus:
~/codeDir/phpCode/hyperf-skeleton # php server.php string(17) "Client: Connect 1"
Die Ausgabeinformationen von tcpdump lauten wie folgt :
01:48:40.178439 IP localhost.33933 > localhost.6666: Flags [S], seq 43162537, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 0,nop,wscale 7], length 0 01:48:40.178484 IP localhost.6666 > localhost.33933: Flags [S.], seq 1327460565, ack 43162538, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 9833698,nop,wscale 7], length 0 01:48:40.178519 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9833698 ecr 9833698], length 0 01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0 01:48:44.229951 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0 01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0 01:48:44.229951 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0 01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0 // 省略了其他的输出
Wir werden feststellen, dass zu Beginn das Drei-Wege-Handshake-Paket gedruckt wird:
01:48:40.178439 IP localhost.33933 > localhost.6666: Flags [S], seq 43162537, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 0,nop,wscale 7], length 0 01:48:40.178484 IP localhost.6666 > localhost.33933: Flags [S.], seq 1327460565, ack 43162538, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 9833698,nop,wscale 7], length 0 01:48:40.178519 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9833698 ecr 9833698], length 0
Dann bleibt es 4 Sekunden lang ohne Paketausgabe.
Danach wird etwa alle 1 Sekunde eine Gruppe ausgedruckt:
01:52:54.359341 IP localhost.6666 > localhost.43101: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9858736], length 0 01:52:54.359377 IP localhost.43101 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9855887], length 0
Tatsächlich ist dies die Strategie, die wir konfiguriert haben:
'tcp_keepinterval' => 1, // 1s探测一次 'tcp_keepcount' => 5, // 探测的次数,超过5次后还没有回包close此连接
Weil die unterste Schicht von Unser Betriebssystem antwortet automatisch mit einer Bestätigung, sodass die Verbindung nach 5 Tests nicht geschlossen wird. Die unterste Schicht des Betriebssystems sendet kontinuierlich eine Gruppe von Paketen wie diese:
01:52:54.359341 IP localhost.6666 > localhost.43101: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9858736], length 0 01:52:54.359377 IP localhost.43101 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9855887], length 0
Wenn wir die Verbindung nach dem Testen von 5 Erkennungen schließen möchten, können wir die Pakete auf Port 6666 deaktivieren:
~/codeDir/phpCode/hyperf-skeleton # iptables -A INPUT -p tcp --dport 6666 -j DROP
Dadurch werden alle Pakete deaktiviert, die von Port 6666 kommen. Natürlich kann der Server keine vom Client gesendeten Bestätigungspakete empfangen.
Dann druckt der Server nach 5 Sekunden „Close“ aus (der Server ruft aktiv die Close-Methode auf und sendet ein FIN-Paket an den Client):
~/codeDir/phpCode/hyperf-skeleton # php server.php string(17) "Client: Connect 1" string(10) "close fd 1"
Lassen Sie uns die iptables-Regeln wiederherstellen:
~/codeDir/phpCode # iptables -D INPUT -p tcp -m tcp --dport 6666 -j DROP
löscht die von uns festgelegten Regeln.
Die Heartbeat-Funktion wird über tcp_keepalive implementiert. Der Vorteil besteht darin, dass sie einfach ausgeführt werden kann, ohne Code zu schreiben, und das gesendete Heartbeat-Paket klein ist. Der Nachteil besteht darin, dass es von der Netzwerkumgebung des Systems abhängt. Es muss sichergestellt werden, dass sowohl der Server als auch der Client solche Funktionen implementieren, und der Client muss beim Senden von Heartbeat-Paketen zusammenarbeiten.
Ein noch schwerwiegenderes Manko besteht darin, dass, wenn der Client und der Server nicht direkt verbunden sind, sondern über einen Proxy wie den Socks5-Proxy verbunden sind, nur Pakete der Anwendungsschicht weitergeleitet werden, nicht für niedrigere. Ebene TCP-Erkennungspakete ist die Heartbeat-Funktion ungültig.
Daher bietet Swoole weitere Lösungen an, eine Reihe von Konfigurationen zur Erkennung toter Verbindungen.
'heartbeat_check_interval' => 1, // 1s探测一次 'heartbeat_idle_time' => 5, // 5s未发送数据包就close此连接
Swoole implementierte Heartbeat
Testen wir es:
<?php $server = new \Swoole\Server('127.0.0.1', 6666, SWOOLE_PROCESS); $server->set([ 'worker_num' => 1, 'heartbeat_check_interval' => 1, // 1s探测一次 'heartbeat_idle_time' => 5, // 5s未发送数据包就close此连接 ]); $server->on('connect', function ($server, $fd) { var_dump("Client: Connect $fd"); }); $server->on('receive', function ($server, $fd, $reactor_id, $data) { var_dump($data); }); $server->on('close', function ($server, $fd) { var_dump("close fd $fd"); }); $server->start();
Dann starten Sie den Server:
~/codeDir/phpCode/hyperf-skelett # php server.phpDann tcpdump starten:
~/codeDir/phpCode # tcpdump -i lo port 6666 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
Dann den Client starten:
~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666
Zu diesem Zeitpunkt druckt der Server:
~/codeDir/phpCode/hyperf-skeleton # php server.php string(17) "Client: Connect 1"
Dann druckt tcpdump:
02:48:32.516093 IP localhost.42123 > localhost.6666: Flags [S], seq 1088388248, win 43690, options [mss 65495,sackOK,TS val 10193342 ecr 0,nop,wscale 7], length 0 02:48:32.516133 IP localhost.6666 > localhost.42123: Flags [S.], seq 80508236, ack 1088388249, win 43690, options [mss 65495,sackOK,TS val 10193342 ecr 10193342,nop,wscale 7], length 0 02:48:32.516156 IP localhost.42123 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 10193342 ecr 10193342], length 0
Dies sind die Drei-Wege-Handshake-Informationen.
Dann wird tcpdump nach 5 Sekunden Folgendes ausgeben:
02:48:36.985027 IP localhost.6666 > localhost.42123: Flags [F.], seq 1, ack 1, win 342, Optionen [nop,nop,TS val 10193789 ecr 10193342], Länge 002:48:36.992172 IP localhost.42123 > localhost.6666: Flags [.], ack 2, win 342, Optionen [nop,nop,TS val 10193790 ecr 10193789], Länge 0
Das heißt, der Server hat ein FIN-Paket gesendet. Da der Client keine Daten gesendet hat, hat Swoole die Verbindung geschlossen.
Dann gibt der Server Folgendes aus:
~/codeDir/phpCode/hyperf-skeleton # php server.php string(17) "Client: Connect 1" string(10) "close fd 1"
Es gibt also einen gewissen Unterschied zwischen Heartbeat und TCP-Keepalive, aber die Funktion besteht darin, die Verbindung aufrechtzuerhalten Heartbeat-Speicherung Erkennt einfach eine Verbindung ohne Daten und schließt sie. Sie kann nur auf der Serverseite konfiguriert werden. Wenn sie am Leben gehalten werden muss, kann der Client auch kooperieren, um Heartbeats zu senden.
如果我们不想让服务端close掉连接,那么就得在应用层里面不断的发送数据包来进行保活,例如我在nc客户端里面不断的发送包:
~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666 ping ping ping ping ping ping ping ping ping
我发送了9个ping包给服务器,tcpdump的输出如下:
// 省略了三次握手的包 02:57:53.697363 IP localhost.44195 > localhost.6666: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 10249525 ecr 10249307], length 5 02:57:53.697390 IP localhost.6666 > localhost.44195: Flags [.], ack 6, win 342, options [nop,nop,TS val 10249525 ecr 10249525], length 0 02:57:55.309532 IP localhost.44195 > localhost.6666: Flags [P.], seq 6:11, ack 1, win 342, options [nop,nop,TS val 10249686 ecr 10249525], length 5 02:57:55.309576 IP localhost.6666 > localhost.44195: Flags [.], ack 11, win 342, options [nop,nop,TS val 10249686 ecr 10249686], length 0 02:57:58.395206 IP localhost.44195 > localhost.6666: Flags [P.], seq 11:16, ack 1, win 342, options [nop,nop,TS val 10249994 ecr 10249686], length 5 02:57:58.395239 IP localhost.6666 > localhost.44195: Flags [.], ack 16, win 342, options [nop,nop,TS val 10249994 ecr 10249994], length 0 02:58:01.858094 IP localhost.44195 > localhost.6666: Flags [P.], seq 16:21, ack 1, win 342, options [nop,nop,TS val 10250341 ecr 10249994], length 5 02:58:01.858126 IP localhost.6666 > localhost.44195: Flags [.], ack 21, win 342, options [nop,nop,TS val 10250341 ecr 10250341], length 0 02:58:04.132584 IP localhost.44195 > localhost.6666: Flags [P.], seq 21:26, ack 1, win 342, options [nop,nop,TS val 10250568 ecr 10250341], length 5 02:58:04.132609 IP localhost.6666 > localhost.44195: Flags [.], ack 26, win 342, options [nop,nop,TS val 10250568 ecr 10250568], length 0 02:58:05.895704 IP localhost.44195 > localhost.6666: Flags [P.], seq 26:31, ack 1, win 342, options [nop,nop,TS val 10250744 ecr 10250568], length 5 02:58:05.895728 IP localhost.6666 > localhost.44195: Flags [.], ack 31, win 342, options [nop,nop,TS val 10250744 ecr 10250744], length 0 02:58:07.150265 IP localhost.44195 > localhost.6666: Flags [P.], seq 31:36, ack 1, win 342, options [nop,nop,TS val 10250870 ecr 10250744], length 5 02:58:07.150288 IP localhost.6666 > localhost.44195: Flags [.], ack 36, win 342, options [nop,nop,TS val 10250870 ecr 10250870], length 0 02:58:08.349124 IP localhost.44195 > localhost.6666: Flags [P.], seq 36:41, ack 1, win 342, options [nop,nop,TS val 10250990 ecr 10250870], length 5 02:58:08.349156 IP localhost.6666 > localhost.44195: Flags [.], ack 41, win 342, options [nop,nop,TS val 10250990 ecr 10250990], length 0 02:58:09.906223 IP localhost.44195 > localhost.6666: Flags [P.], seq 41:46, ack 1, win 342, options [nop,nop,TS val 10251145 ecr 10250990], length 5 02:58:09.906247 IP localhost.6666 > localhost.44195: Flags [.], ack 46, win 342, options [nop,nop,TS val 10251145 ecr 10251145], length 0
有9组数据包的发送。(这里的Flags [P.]代表Push的含义)
此时服务器还没有close掉连接,实现了客户端保活连接的功能。然后我们停止发送ping,过了5秒后tcpdump就会输出一组:
02:58:14.811761 IP localhost.6666 > localhost.44195: Flags [F.], seq 1, ack 46, win 342, options [nop,nop,TS val 10251636 ecr 10251145], length 0
02:58:14.816420 IP localhost.44195 > localhost.6666: Flags [.], ack 2, win 342, options [nop,nop,TS val 10251637 ecr 10251636], length 0
服务端那边发送了FIN包,说明服务端close掉了连接。服务端的输出如下:
~/codeDir/phpCode/hyperf-skeleton # php server.php string(17) "Client: Connect 1" string(5) "ping " string(5) "ping " string(5) "ping " string(5) "ping " string(5) "ping " string(5) "ping " string(5) "ping " string(5) "ping " string(5) "ping " string(10) "close fd 1"
然后我们在客户端那边ctrl + c来关闭连接:
~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666 ping ping ping ping ping ping ping ping ping ^Cpunt! ~/codeDir/phpCode/hyperf-skeleton #
此时,tcpdump的输出如下:
03:03:02.257667 IP localhost.44195 > localhost.6666: Flags [F.], seq 46, ack 2, win 342, options [nop,nop,TS val 10280414 ecr 10251636], length 0 03:03:02.257734 IP localhost.6666 > localhost.44195: Flags [R], seq 2678621620, win 0, length 0
应用层心跳
1、制定ping/pong协议(mysql等自带ping协议)
2、客户端灵活的发送ping心跳包
3、服务端OnRecive检查可用性回复pong
例如:
$server->on('receive', function (\Swoole\Server $server, $fd, $reactor_id, $data) { if ($data == 'ping') { checkDB(); checkServiceA(); checkRedis(); $server->send('pong'); } });
结论
1、tcp的keepalive最简单,但是有兼容性问题,不够灵活
2、swoole提供的keepalive最实用,但是需要客户端配合,复杂度适中
3、应用层的keepalive最灵活但是最麻烦
Das obige ist der detaillierte Inhalt vonZusammenfassung häufiger Probleme mit langen PHP-Swoole-Verbindungen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!