ホームページ  >  記事  >  PHPフレームワーク  >  PHP Swoole の長い接続に関する一般的な問題の概要

PHP Swoole の長い接続に関する一般的な問題の概要

藏色散人
藏色散人転載
2020-01-26 13:48:342806ブラウズ

PHP Swoole の長い接続に関する一般的な問題の概要

#接続失敗の問題

その中で、Redisエラー レポートは次のとおりです:

構成項目:

timeout

エラー メッセージ:

サーバーからの行の読み取り中にエラーが発生しました# #Redis can Configure クライアントが一定の秒数が経過しても Redis サーバーにデータを送信しない場合、接続は閉じられます。

推奨される学習:

swoole チュートリアル

MySQL の一般的なエラー:

設定項目:

wait_timeout & interaction_timeout

エラー メッセージ:

は消えてしまいました

Redis サーバーと同様に、MySQL も不要な接続を定期的にクリーンアップします。

解決方法

1. 再接続を使用する場合は

2. 定期的にハートビートを送信して接続を維持する

再接続を使用する場合

利点はシンプルなことですが、欠点は接続が短いという問題に直面していることです。

接続を維持するために定期的にハートビートを送信します

推奨。

長時間の接続を維持する方法

tcp プロトコルに実装された tcp_keepalive

オペレーティング システムの最下層は、次の機能を提供します。一連の tcp

keepalive

Configuration: <pre class="brush:php;toolbar:false">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</pre>Swoole の最下層では、次のような構成が開かれています。例:

?php
 
$server = new \Swoole\Server(&#39;127.0.0.1&#39;, 6666, SWOOLE_PROCESS);
 
$server->set([
&#39;worker_num&#39; => 1,
&#39;open_tcp_keepalive&#39; => 1,
&#39;tcp_keepidle&#39; => 4, // 对应tcp_keepalive_time
&#39;tcp_keepinterval&#39; => 1, // 对应tcp_keepalive_intvl
&#39;tcp_keepcount&#39; => 5, // 对应tcp_keepalive_probes
]);

その中には:

&#39;open_tcp_keepalive&#39; => 1, // 总开关,用来开启tcp_keepalive
&#39;tcp_keepidle&#39; => 4, // 4s没有数据传输就进行检测
// 检测的策略如下:
&#39;tcp_keepinterval&#39; => 1, // 1s探测一次,即每隔1s给客户端发一个包(然后客户端可能会回一个ack的包,如果服务端收到了这个ack包,那么说明这个连接是活着的)
&#39;tcp_keepcount&#39; => 5, // 探测的次数,超过5次后客户端还没有回ack包,那么close此连接

実際のテストを体験してみましょう サーバースクリプトは次のとおりです:

<?php
 
$server = new \Swoole\Server(&#39;127.0.0.1&#39;, 6666, SWOOLE_PROCESS);
 
$server->set([
&#39;worker_num&#39; => 1,
&#39;open_tcp_keepalive&#39; => 1, // 开启tcp_keepalive
&#39;tcp_keepidle&#39; => 4, // 4s没有数据传输就进行检测
&#39;tcp_keepinterval&#39; => 1, // 1s探测一次
&#39;tcp_keepcount&#39; => 5, // 探测的次数,超过5次后还没有回包close此连接
]);
 
$server->on(&#39;connect&#39;, function ($server, $fd) {
var_dump("Client: Connect $fd");
});
 
$server->on(&#39;receive&#39;, function ($server, $fd, $reactor_id, $data) {
var_dump($data);
});
 
$server->on(&#39;close&#39;, function ($server, $fd) {
var_dump("close fd $fd");
});
 
$server->start();

このサーバーを起動しましょう:

 ~/codeDir/phpCode/hyperf-skeleton # php server.php

そして、tcpdump を通じてパケットをキャプチャします:

~/codeDir/phpCode /hyperf-skeleton # tcpdump -i lo port 6666

tcpdump: 詳細な出力は抑制されます。完全なプロトコル デコードには -v または -vv を使用してください

lo でリッスン、リンク タイプ EN10MB (イーサネット)、キャプチャ サイズ 262144 バイト

現時点では、lo のポート 6666 でデータ パケットをリッスンしています。

次に、クライアントを使用して接続します:

~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666

このとき、サーバーはメッセージを出力します:

~/codeDir/phpCode/hyperf-skeleton # php server.php
string(17) "Client: Connect 1"

tcpdump の出力情報は次のとおりです。 :

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
// 省略了其他的输出

最初に、スリーウェイ ハンドシェイク パケットが出力されることがわかります:

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

その後、パケットは出力されずに 4 秒間残ります。

その後、グループは約 1 秒ごとに出力されます:

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

実際、これは私たちが設定した戦略です:

 &#39;tcp_keepinterval&#39; => 1, // 1s探测一次
 &#39;tcp_keepcount&#39; => 5, // 探测的次数,超过5次后还没有回包close此连接

オペレーティング システムは自動的にクライアントから ack で応答するため、5 回のプローブ後に接続は閉じられません。オペレーティング システムの最下層は、次のようなパケットのグループを継続的に送信します:

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

5 回テストした後に接続を閉じたい場合は、ポート 6666 のパケットを無効にします:

 ~/codeDir/phpCode/hyperf-skeleton # iptables -A INPUT -p tcp --dport 6666 -j DROP

これにより、ポート 6666 から送信されるすべてのパケットが無効になります。当然、サーバーはクライアントから送信された ack パケットを受信できなくなります。

その後、サーバーは 5 秒後に close を出力します (サーバーは積極的に close メソッドを呼び出し、FIN パケットをクライアントに送信します):

 ~/codeDir/phpCode/hyperf-skeleton # php server.php
 string(17) "Client: Connect 1"
 string(10) "close fd 1"

iptables ルールを復元しましょう:

 ~/codeDir/phpCode # iptables -D INPUT -p tcp -m tcp --dport 6666 -j DROP

つまり、設定したルールは削除されます。

ハートビート機能は tcp_keepalive によって実装されており、コードを書かずにこの機能を完了でき、送信されるハートビート パケットが小さいというシンプルな利点があります。欠点は、システムのネットワーク環境に依存することであり、サーバーとクライアントの両方がそのような機能を実装していることを確認する必要があり、クライアントはハートビート パケットの送信に協力する必要があります。

もう 1 つのより深刻な欠点は、クライアントとサーバーが直接接続されておらず、socks5 プロキシなどのプロキシを介して接続されている場合、アプリケーション層のパケットのみが転送され、パケットは転送されないことです。 -レベルのTCP検出パケットを受信した場合、ハートビート機能は無効になります。

そこで、Swoole は他のソリューション、つまり切断された接続を検出するための一連の構成を提供します。

 &#39;heartbeat_check_interval&#39; => 1, // 1s探测一次
 &#39;heartbeat_idle_time&#39; => 5, // 5s未发送数据包就close此连接

ハートビートは swoole によって実装されています

テストしてみましょう:

<?php
 
$server = new \Swoole\Server(&#39;127.0.0.1&#39;, 6666, SWOOLE_PROCESS);
 
$server->set([
&#39;worker_num&#39; => 1,
&#39;heartbeat_check_interval&#39; => 1, // 1s探测一次
&#39;heartbeat_idle_time&#39; => 5, // 5s未发送数据包就close此连接
]);
 
$server->on(&#39;connect&#39;, function ($server, $fd) {
var_dump("Client: Connect $fd");
});
 
$server->on(&#39;receive&#39;, function ($server, $fd, $reactor_id, $data) {
var_dump($data);
});
 
$server->on(&#39;close&#39;, function ($server, $fd) {
var_dump("close fd $fd");
});
 
$server->start();

次にサーバーを起動します:

~/codeDir/phpCode/hyperf-スケルトン # php server.php

次に、tcpdump を開始します:

 ~/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

次に、クライアントを開始します:

~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666

この時点で、サーバーは次のように出力します:

~/codeDir/phpCode/hyperf-skeleton # php server.php
 string(17) "Client: Connect 1"

その後、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

これは 3 ウェイ ハンドシェイク情報です。

5 秒後、tcpdump は次のように出力します:

02:48:36.985027 IP localhost.6666 > localhost.42123: Flags [F.], seq 1, ack 1、勝利 342、オプション [nop,nop,TS val 10193789 ecr 10193342]、長さ 0

02:48:36.992172 IP localhost.42123 > localhost.6666: フラグ [.]、ack 2、勝利342、オプション [nop,nop,TS val 10193790 ecr 10193789]、長さ 0

つまり、サーバーは FIN パケットを送信しました。クライアントがデータを送信しなかったため、Swoole は接続を閉じました。

その後、サーバーは次のように出力します:

 ~/codeDir/phpCode/hyperf-skeleton # php server.php
 string(17) "Client: Connect 1"
 string(10) "close fd 1"

したがって、ハートビートと tcp キープアライブには特定の違いがあります。tcp キープアライブには接続を維持する機能がありますが、ハートビートはsaves データのない接続を単に検出して閉じます. サーバー側でのみ設定できます. 維持する必要がある場合, クライアントも連携してハートビートを送信できます.

如果我们不想让服务端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(&#39;receive&#39;, function (\Swoole\Server $server, $fd, $reactor_id, $data)
{
if ($data == &#39;ping&#39;)
{
checkDB();
checkServiceA();
checkRedis();
$server->send(&#39;pong&#39;);
}
});

 

结论

1、tcp的keepalive最简单,但是有兼容性问题,不够灵活

2、swoole提供的keepalive最实用,但是需要客户端配合,复杂度适中

3、应用层的keepalive最灵活但是最麻烦

以上がPHP Swoole の長い接続に関する一般的な問題の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。