LoginSignup
9
7

More than 5 years have passed since last update.

phpのMemcached拡張で持続的接続を行うとESTABLISHEDが大量に発生する問題の原因と対処法

Last updated at Posted at 2016-12-27

PHPでMemcachedを利用する際に使うPECL::Memcached
そんなPECL::MemcachedでESTABLISHEDが大量に発生しているという問題に遭遇しました。
ちなみにPHP7な環境で、php-memcached-devを使った場合でも同じ問題が発生していました。

何が起きたのか?

持続的接続しないと、アクセス数増えたら辛いわけで持続的接続をしようと思い、気軽にこんな感じの実装をしていました。

$memcached = new \Memcached(1);
$memcached->addServer('memcached', 11211);

$recommendTours = $memcached->get('recommendTours');

単純にMemcachedに接続してデータを取ってくる形ですね。

持続的接続を行う場合に軽くググってみたら
@shin1x1さんのPHP Memcached拡張で持続的接続を行うとか、PHPマニュアルを見て、「コンスタントにID渡せばいいのかぁー」なんて思って上記のようなコードを書いてしまっていました。

気づいたときにはMemcachedへの持続的なコネクション大量に貼られていたわけですね・・・。

原因

原因は$memcached->addServer('memcached', 11211);の部分にありました。

持続的接続の場合は、一度接続処理を行ったらPHPスクリプトが終了されても接続が持続されます。
おそらくfpmやapacheなりを再起動するまでは接続され続けます。

つまり上で書いたコードでは、実行される度に持続的接続が毎回行われてしまいます・・・。

持続的接続を行っている状態だと、$memcached->addServer('memcached', 11211);の記述がなくても、サーバ情報は取得出来ます。

$memcached = new \Memcached(1);
var_dump($memcached->getServerList());

もちろん初回の接続の場合は、サーバ情報を保持していません。
そのためMemcached::isPristine()で判定してあげる必要があります。

対処法

ということで、持続的接続の場合は初回以外は最初からサーバ情報を持っています。
以下のようにコードを実装しましょう。(当たり前といえば当たり前ですね。)

<?php

$memcached = new \Memcached(1);
if ($memcached->isPristine()) {
    $memcached->addServer('memcached', 11211, 1);
}

検証環境にDockerを使った

最初は全然検討がつかずに、単純にMemcached::quit()メソッドを実行すればいいやーなんて思っていたんですが、普通にコネクションがなくなってしまっていて、これじゃないと気づいたわけです。(マニュアルを読めばわかる話ですが。)

そこで、ちゃんと原因を追求するためにDockerを使い検証環境を用意しました。(単純にまだDockerをちゃんと触れていなかったので、触りたかっただけですが)

3つの検証環境

特定のバージョンで起きるのかなぁーとか思ってしまったので、今回は念のため3つの異なる環境を用意しました。

  • php5.6+php-fpm
  • php7.0+php-fpm
  • php7+apache

それぞれの環境にHTTPリクエストを送る際には以下のルールでアクセス出来ます。

php5.6+php-fpm

http://localhost/56/
でアクセスすればPHP5.6環境でappディレクトリのスクリプトが実行出来ます。

php7.0+php-fpm

http://localhost/
でアクセスすればPHP7.0環境でappディレクトリのスクリプトが実行出来ます。

php7+apache

http://localhost:8080/
でアクセスすればPHP7.0環境でappディレクトリのスクリプトが実行出来ます。

実際にESTABLISHEDが溜まっていくものを再現してみる

ダメなスクリプトを用意しました。
こいつをHTTPでアクセスする度にESTABLISHEDが溜まっていきます。

適当にcloneしてきてdocker-compouse使って動かします。
そして、http://localhost/bad.phpにアクセスします。
何回かリロードしたあとにDocker execでコンテナの中に接続してnetstatで確認してみます。

$ docker-compose ps
           Name                         Command               State              Ports
-------------------------------------------------------------------------------------------------
memcachedtest_memcached_1    docker-entrypoint.sh memcached   Up      11211/tcp
memcachedtest_nginx_1        nginx -g daemon off;             Up      443/tcp, 0.0.0.0:80->80/tcp
memcachedtest_php-apache_1   docker-php-entrypoint apac ...   Up      0.0.0.0:8080->80/tcp
memcachedtest_php56-fpm_1    docker-php-entrypoint php-fpm    Up      9000/tcp
memcachedtest_php7-fpm_1     docker-php-entrypoint php-fpm    Up      9000/tcp


$ docker exec -it memcachedtest_php7-fpm_1 /bin/bash
root@7975336be0a8:/var/www/html$ netstat -tan | grep ':11211' | awk '{print $6}' | sort | uniq -c
      6 ESTABLISHED

こんな感じで大量にアクセスがあると大変なことになりますね・・・。

ちなみに、ESTABLISHEDが溜まっていかないコードはindex.phpの方に記載してあります。

最後に

PHP+Memcachedでこの手の話題があまりなかったのですが、皆さんどう対応しているのでしょうか?
そもそも持続的接続ってあまり使わないんですかね?

Symfony2での対処法もBlogの方に書いておいたのよかったら参考にしてください。
MemcachedSessionHandlerで持続的接続を行うとESTABLISHEDが大量に発生する問題

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7