PHP の MySQL 永続的接続は素晴らしい目標ですが、評判が悪く、敬遠されることが多いです。どうしてこれなの?よく観察した結果、この男は Apache の顔に依存しており、MySQL のコマンドを聞かなければならないことがわかりました。
Apache モジュールとして実行されている PHP の場合、MySQL 永続接続を実装するには、まず Apache Web サーバーがキープアライブをサポートしているかどうかによって決まります。
キープアライブとは何ですか?これは http プロトコルの一部であり、キープアライブを使用しない http リクエストを見てみましょう。顧客がブラウザに有効な URL アドレスを入力すると、ブラウザはソケットを使用して、対応する Web サーバーに TCP リクエストを送信します。このリクエストは成功すると、確認のために 3 回握手する必要があります。成功後、ブラウザはソケット TCP 接続リソースを使用して Web サーバーからの http プロトコルを要求します。送信後、Web サーバーが送信するのを待ちます。 http リターン ヘッダーとボディを返送した後、ブラウザはソケット接続を閉じ、http リターン ヘッダーとボディを解析すると、最終的に美しいページがブラウザ上に表示されます。ここで何が問題になっているのでしょうか? TCP 接続には 3 ウェイ ハンドシェイクが必要です。つまり、TCP リクエストが成功したかどうかを判断するのに 3 回の往復リクエストが必要で、その後 TCP が閉じられます。往復を完了するには 4 回のリクエストが必要です。各 http リクエストには 3 回のハンドシェイクと 4 回の別れが必要です。ソケット接続を閉じるのにどれくらいの時間とリソースが浪費されますか? 1 つのソケット TCP 接続で複数の http リクエストを送信できますか?そこで、Keep-Alive が登場しました。これを実現するには、クライアントはリクエスト ヘッダーに Connection:Keep-alive を追加する必要があります。ここでは、Apache を設定してデフォルトにするだけで済みます。キープアライブ永続接続モード (キープアライブをサポートするには、Apache が 1.2 以降である必要があります)。 httpd.conf で KeepAive 構成項目を見つけて On に設定し、MaxKeepAliveRequests を 0 (永続的な TCP によって許可されるリクエストの最大数) に設定します。この値が小さすぎると、TCP が正常に動作していないときに最大接続に達しやすくなります。ここで 0 を設定すると、制限がないことを意味します)、mysql_pconnect の最も重要なオプションである KeepAliveTimeout は 15 (15 秒を意味します) に設定されます。
それでは、Apache を再起動してテストし、手早く何かを書いてみましょう:
リーリー非常に簡単です。現在の PHP 実行プログラム (Apache) のプロセス番号を取得し、ブラウザでこのページを参照します。はい、一連のプロセス番号が表示されました。15 秒以内にページを継続的に更新して、プロセス番号が変化するかどうかを確認してください。いいえ?次に手を離し、胸の上で交差させ、1秒、2秒、3、...15、16と時間を刻みます。 15 秒後にページを更新してください。プロセス番号は変更されましたか?かわった! 15 秒後にまた新しい Apache プロセスになるのはなぜですか? Apache で設定した KeepAliveTimeout を覚えていますか?その値は 15 秒です。これで、Web サーバーがデフォルトで KeepAlive をオンにした場合、クライアントの最初の http リクエストが成功した後、Apache はすぐにソケットを切断しませんが、このクライアントからのリクエストを常に監視します。 KeepAliveTimeout オプションで設定された時間に応じて、この時間を超えると、Apache はソケットを切断し、次に同じクライアントが再度リクエストしたときに、Apache は新しいプロセスを開いて応答します。そのため、過去 15 年間ページを更新し続けましたが、表示されたプロセス番号はすべて同じでした。これは、ブラウザが同じ Apache プロセスを要求したことを示しています。
ブラウザは、TCP 接続を再確立せずに http リクエストを直接送信できることをどのようにして知るのでしょうか? http リターン ヘッダーには Connection:keep-alive と Keep-alive:15 の 2 行があるため、クライアント ブラウザはこのソケット接続がこちら側で閉じられていないことを理解しており、15 以内は続行できることを意味します。この接続を使用します。そして http リクエストを送信することで、ブラウザは何をすべきかを認識します。
では、PHP の MySQL 接続リソースはどのように保持されているのでしょうか? これには、PHP の mysql_pconnect の関数コードを確認する必要があります。一般的な方法は、mysql_pconnect が現在の Apache プロセス番号に基づいてハッシュ キーを生成し、それをハッシュ テーブル内で見つけることです。対応する接続リソースがあるかどうか、ない場合はハッシュ テーブルにプッシュし、存在する場合はそれを直接使用します。いくつかのコード スニペットについては説明できます (詳細については、PHP5.3.8 ソース コード ext/mysql/PHP_mysql.c ファイル行 690 PHP_mysql_do_connect 関数を参照してください)
リーリーzend_hash_find の方が理解しやすいです。プロトタイプは zend_hash_find (ハッシュ テーブル、キー名、キーの長さ、値) です。それが見つかった場合、その値は価値のあるものになります。
Keep-Alive について話した後は、MySQL の家を訪問して、mysql_pconnect と MySQL の設定をバイパスする方法について話します。 mysql_pconnect に影響を与える 2 つの最も重要なパラメータは wait_timeout と interaction_timeout です。それはさておき、まずは上記のコードをPHPのコードに変更してみましょう
<?php $conn = mysql_pconnect("localhost","root","123456") or die("Can not connect to MySQL"); echo "MySQL线程号:". MySQL_thread_id($conn). "<br />"; echo "Apache进程号". getmypid(); ?>
以上的代码没啥好解释的,让我们用浏览器浏览这个页面,看到什么?看到两个显眼的数字。一个是MySQL线程号,一个是Apache进程号,好了,15秒后再刷新这个页面,发现这两个id都变了,因为已经是新的Apache进程了,进程id是新的,hash key就变了,PHP只好重新连接MySQL,连接资源推入persistent list。如果15内刷新呢?Apache进程肯定不变,MySQL线程号会变吗?答案得问MySQL了。首先这个MySQL_thread_id是什么东西?shell方式登录MySQL后执行命令'show processlist;',看到了什么?
mysql> show processlist; +-----+------+-----------+------+--------+-----+------+-----------------+ | Id | User | Host | db | Command| Time| State| Info | +-----+------+-----------+------+--------+-----+------+-----------------+ | 348 | root | localhost | NULL | Query | 0| NULL | show processlist| | 349 | root | localhost | NULL | Sleep | 2| | NULL | +-----+------+-----------+------+--------+-----+------+-----------------+
发现了很重要的信息,这个processlist列表就是记录了正在跑的线程,忽略Info列为show processlist那行,那行是你当前shell登录MySQL的线程。PHP连接MySQL的线程就是Id为349那行,如果读者自己做测试,应该知道这个Id=349在你的测试环境里是另外一个值,我们把这个值和网页里输出的MySQL_thread_id($conn)做做比较,对!他们是一样的。接下来最重要的是观察Command列和Time列,Command = Sleep,表明什么?表明我们mysql_pconnect连接后就一直在sleep,Time字段就告诉我们,这个线程Sleep了多久,那么Sleep了多久这个线程才能作废呢?那就是wait_timeout或者interactive_timeout要做的工作了,他们默认的值都是8小时,天啊,太久了,所以如果说web服务器关掉KeepAlive支持,那个这个processlist很容易就被撑爆,就爆出那个Too many connections的错误了,max_connectiosns配置得再多也没用。为了观察这两个参数,我们可以在MySQL配置文件my.cnf里设置这两个值,找到[MySQLd]节点,在里面设置多两行
interactive_timeout = 60 wait_timeout = 30
配置完后,重启MySQL,shell登录MySQL,这时候show processlist可以发现只有当前线程。然后运行那个带有mysql_pconnect的PHP页面,再回来MySQL端show processlist可发现,多了一个Commond为Sleep的线程,不停的show processlist(方向键上+enter键)观察Time列的变化2,5,10...14!,突然那个Sleep线程程被kill掉了,咋回事,还没到30秒呢,噢!忘了修改一下Apache keepalive的参数了,把KeepAliveTimeOut从15改成120(只为观察,才这么改),重启Apache。刷新那个页面,好,开始不停的show processlist,2..5..10..14,15,..20...26....28,29!线程被kill,这次是因为wait_timeout起了作用,浏览器那边停了30秒,30内如果浏览器刷新,那这个Time又会从0开始计时。这种连接不属于interactive connection(MySQL shell登录那种连接就属于interactive connection),所以采用了wait_timeout的值。如果mysql_pconnect的第4个参数改改呢
<?php $conn = mysql_pconnect('localhost','root','123456',MySQL_CLIENT_INTERACTIVE); echo "MySQL线程号:".MySQL_thread_id($conn)."<br />"; echo "Apache进程号:".getmypid(); ?>
刷新下页面,MySQL那边开始刷show processlist,这回Time > 30也不会被kill,>60才被kill了,说明设置了MySQL_CLIENT_INTERACTIVE,就会被MySQL视为interactive connection,那么这次PHP的MySQL连接在120秒内未刷新的情况下,何时作废将取决于MySQL的interactive_timeout的配置值。
PHP的mysql_pconnect要达到功效,首先必须保证Apache是支持keep alive的,其次KeepAliveTimeOut应该设置多久呢,要根据自身站点的访问情况做调整,时间太短,keep alive没啥意义,时间太长,就很可能为一个闲客户端连接牺牲很多服务器资源,毕竟hold住socket监听进程是要消耗cpu内存的。最后Apache的KeepAliveTimeOut配置得和MySQL的time out配置要有个平衡点,联系以上的观察,假设mysql_pconnect未带上第4个参数,如果Apache的KeepAliveTimeOut设置的秒数比wait_timeout小,那真正对mysql_pconnect起作用的是Apache而不是MySQL的配置。这时如果MySQL的wait_timeout偏大,并发量大的情况下,很可能就一堆废弃的connection了,MySQL这边如果不及时回收,那就很可能Too many connections了。可是如果KeepAliveTimeOut太大呢,又回到之前的问题,所以貌似Apache。KeepAliveTimeOu不要太大,但比MySQL。wait_timeout 稍大,或者相等是比较好的方案,这样可以保证keep alive过期后,废弃的MySQL连接可以及时被回收。