PSI Labs RSS feed

PHP のセッション管理に Redis + phpredis 拡張を使う

こんにちは、賞与が近づくと家電の調子が悪くなりはじめる tomita です。

さて、PHPのセッション管理には標準のファイルベースや Memcached 拡張を使ったオンメモリベースがありますが、永続化が可能なインメモリデータベース Redis の拡張 phpredis というのがあったので使ってみることにします。

以下の環境で試しました。

  • CentOS 6.3
  • Apache 2.4.3
  • PHP 5.4.8
  • Redis 2.6.7

phpredis インストール

まず git clone で最新ソースを取得、phpize + configure + make でコンパイルします。

$ git clone https://github.com/nicolasff/phpredis.git
$ cd phpredis
$ phpize
$ ./configure
$ make
$ make install

php.ini に redis.so を追記します。

$ vim /usr/local/lib/php.ini
extension=redis.so

以下コマンドで「redis」が表示されればインストール完了です。

$ php -m | grep redis
redis

セッションの設定

php.ini に以下の設定を書けば最低限の設定は終了です。お手軽ですね...
※ 認証しない場合は「?auth=...」以降は必要ありません。

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?auth=password"

共有サーバ等で php.ini を編集できない場合は、以下のように session_start() 前に ini_set() してもOKです。

<?php
ini_set( 'session.save_handler', 'redis' );
ini_set( 'session.save_path', 'tcp://127.0.0.1:6379?auth=password' );
session_start();

動作確認

リロードするたびにカウントアップする単純なスクリプトで試してみます。

<?php
ini_set( 'session.save_handler', 'redis' );
ini_set( 'session.save_path', 'tcp://127.0.0.1:6379?auth=password' );

session_start();
if ( ! isset( $_SESSION['cnt'] ) ) {
  $_SESSION['cnt'] = 0;
}
$_SESSION['cnt']++;
echo $_SESSION['cnt'];

実行前に、redis-cli の monitor コマンドで、どんな値が登録されてくるか監視します。

$ redis-cli
redis 127.0.0.1:6379> monitor
OK

3回リロードした結果、ブラウザには「3」が、コンソールには以下が出力されました。正常に動いているようです。

1355381367.907699 [0 127.0.0.1:33351] "AUTH" "password"
1355381367.908157 [0 127.0.0.1:33351] "GET" "PHPREDIS_SESSION:tgedf2qs2utus9nu7m2i92nj71"
1355381367.908782 [0 127.0.0.1:33351] "SETEX" "PHPREDIS_SESSION:tgedf2qs2utus9nu7m2i92nj71" "1440" "cnt|i:1;"
1355381369.875748 [0 127.0.0.1:33352] "AUTH" "password"
1355381369.875983 [0 127.0.0.1:33352] "GET" "PHPREDIS_SESSION:tgedf2qs2utus9nu7m2i92nj71"
1355381369.876580 [0 127.0.0.1:33352] "SETEX" "PHPREDIS_SESSION:tgedf2qs2utus9nu7m2i92nj71" "1440" "cnt|i:2;"
1355381370.947269 [0 127.0.0.1:33353] "AUTH" "password"
1355381370.947472 [0 127.0.0.1:33353] "GET" "PHPREDIS_SESSION:tgedf2qs2utus9nu7m2i92nj71"
1355381370.947732 [0 127.0.0.1:33353] "SETEX" "PHPREDIS_SESSION:tgedf2qs2utus9nu7m2i92nj71" "1440" "cnt|i:3;"

php.ini や ini_set() が効かない...

通常は上記でうまくいくんはずですが、環境によっては php.ini や ini_set() が効かず「Failed to write session data (redis). Please verify that the current setting of session.save_path is correct... 」と Warningが出てしまう場合があります。
※ 某 PaaS で遭遇しました...

その場合は、以下のようにセッションハンドラを独自に定義することで動作させることが可能です。

<?php
/**
 * Session_Redis_Handler.php
 */
class Session_Redis_Handler
{
  public function __construct( $host, $port, $password, $timeout, $session_prefix, $session_timeout ) {
    $this->_redis = new Redis();
    $this->_host = $host;
    $this->_port = $port;
    $this->_password = $password;
    $this->_timeout = $timeout;
    $this->_session_prefix = $session_prefix;
    $this->_session_timeout = $session_timeout;
    return true;
  }

  public function open( $savePath, $sessionName ) {
    try {
      $this->_redis->connect( $this->_host, $this->_port, $this->_timeout );
      $this->_redis->setOption( Redis::OPT_PREFIX, $this->_session_prefix );
      $this->_redis->auth( $this->_password );
    } catch ( RedisException $e ) {
      // var_dump( $e );
    }
    return true;
  }

  public function close() {
    return true;
  }

  public function read( $id ){
    return $this->_redis->get( $id );
  }

  public function write( $id, $data ){
    $this->_redis->setex( $id, $this->_session_timeout, $data );
    return true;
  }

  public function destroy( $id ){
    $this->_redis->delete( $id );
    return true;
  }

  public function gc( $maxlifetime ) {
    return true;
  }
}

先ほどのスクリプトを書き換えます。

<?php
require_once 'Session_Redis_Handler.php';
$handler = new Session_Redis_Handler(
  '127.0.0.1',   // ホスト名
  6379,          // ポート番号
  'password',    // 認証が必要ならば記述
  2.0,           // Redisのタイムアウト秒
  'SESS_REDIS:', // キーの接頭辞
  60 * 20        // セッション有効期間秒(20分)
);
session_set_save_handler(
  array( $handler, 'open' ),
  array( $handler, 'close' ),
  array( $handler, 'read' ),
  array( $handler, 'write' ),
  array( $handler, 'destroy' ),
  array( $handler, 'gc' )
);
register_shutdown_function( 'session_write_close' );

session_start();
if ( ! isset( $_SESSION['cnt'] ) ) {
  $_SESSION['cnt'] = 0;
}
$_SESSION['cnt']++;
echo $_SESSION['cnt'];

3回リロードした結果、ブラウザには「3」が、コンソールには以下が出力されました。正常に動いているようです。

1355382788.217556 [0 127.0.0.1:33359] "AUTH" "password"
1355382788.218166 [0 127.0.0.1:33359] "GET" "SESS_REDIS:tgedf2qs2utus9nu7m2i92nj71"
1355382788.218556 [0 127.0.0.1:33359] "SETEX" "SESS_REDIS:tgedf2qs2utus9nu7m2i92nj71" "1200" "cnt|i:1;"
1355382791.619673 [0 127.0.0.1:33360] "AUTH" "password"
1355382791.619909 [0 127.0.0.1:33360] "GET" "SESS_REDIS:tgedf2qs2utus9nu7m2i92nj71"
1355382791.620161 [0 127.0.0.1:33360] "SETEX" "SESS_REDIS:tgedf2qs2utus9nu7m2i92nj71" "1200" "cnt|i:2;"
1355382792.894807 [0 127.0.0.1:33361] "AUTH" "password"
1355382792.895045 [0 127.0.0.1:33361] "GET" "SESS_REDIS:tgedf2qs2utus9nu7m2i92nj71"
1355382792.895295 [0 127.0.0.1:33361] "SETEX" "SESS_REDIS:tgedf2qs2utus9nu7m2i92nj71" "1200" "cnt|i:3;"

上記のように、独自にセッションハンドラを定義するケースはあまりないと思いますが、公式マニュアル に一度目を通しておくと後々役にたつかもしれません。

それでは~。