PHPセッションをPhpRedisに保存する

# 概要

PHPのRedis通信拡張(extension)であるPhpRedisでは、通常のRedisへの入出力に加えて、PHPセッションを保存する機能が提供されています。
PHPセッションは、デフォルトでは/tmpや/var/tmpなどといった一時ディレクトリにファイルとして保存されます。高負荷が予期されるサーバにおいて、ファイルI/Oが気になるような環境では、memcachedやRedisといったオンメモリKVSデータベースの利用を考えることになりますが、PhpRedisはその選択肢の1つになるのではないでしょうか。

# インストール

PhpRedisのインストール自体はLinux系ならyumやapt-get、macOSならHomeBrewやMacPorts、*BSD系ならports collectionといった各パッケージシステムから利用できるかと思います。
また、私が書いた[こちら](http://qiita.com/zurazurataicho/items/496bfe04bea77fc46777)も、参考になれば幸いです。

参考:[macOS(Mac OS X)にPhpRedisをインストール](http://qiita.com/zurazurataicho/items/496bfe04bea77fc46777)

# 設定

PhpRedisのGitHubにある[README.markdown](https://github.com/phpredis/phpredis/blob/3.0.0/README.markdown)に書いてあるとおり、PhpRedisではphp.iniにハンドラ設定をすることでPHPセッションを保存に対応すると[記されています](https://github.com/phpredis/phpredis/blob/3.0.0/README.markdown#php-session-handler)。

PHPセッションを保存するには、php.iniに次のような2行を設定します。

```shell-session:PHPセッションを保存するphp.iniの設定
# grep ^session /opt/local/etc/php56/php.ini
session.save_handler = redis
session.save_path = "tcp://localhost:6379"
(以下略)
```

1行目はハンドラとしてredisを使うことを宣言しています。デフォルトではここはfilesになっていますので、ここを書き換えます。
書き換えがためらわれる方は、ここを行コピーして、filesの行をコメントし、コピー行のfilesをredisに書き換えます。

2行目は保存パスです。filesの場合はここは/tmpなどのようにディレクトリパスが記載されていますが、redisではTCP経由で行いますので、tcp://localhost:6379を指定しています。
注意したいのはlocalhostの部分で、ここはredis-serverがコネクションを確立できる(TCP経由でデータを送受信できる)アドレスを指定します。ホスト名もしくはIPアドレスを指定することになります。
ホスト名(IPアドレス)の項目の右にある:の、さらに右にある数値がポート番号で、デフォルトではRedisは6379/TCPを使用します。別のポート番号を使っている人は、ここの値も同じものにしてください。

いくつかオプションが付けられますが、複数サーバでの運用とかタイムアウトとか、そうしたものを指定したい場合に加えることができます(少しだけ後述)。

# 動作確認

さて、これで本当にRedisに保存されるのでしょうか。
ということで、動作確認をしてみます。redis-serverはあらかじめ起動しておく必要があります。

```shell-session:redis-serverの起動の例
# redis-server &
```

次に、PHPセッションを使うテストコードsession.phpを用意します。

```php:session.php
<?php
session_start();

echo "save_handler=" . ini_get("session.save_handler") . "\n";
echo "save_path=" . ini_get("session.save_path") . "\n";
echo "session_id=" . session_id() . "\n";

$_SESSION['libname'] = "PhpRedis";
```

テストコードを実行する前に、必要な確認作業があります。
当然ですが、現時点でRedisに何も(少なくともPHPセッションIDなどが)入っていないことを確認しなければなりません。

```shell-session:Redisの中にキーが何もないことを確認
> redis-cli
127.0.0.1:6379> keys *
(empty list or set)
```

この場合は、あらゆるキーが入っていないことを確認できましたので、心置きなく動作確認ができます。

では、さきほどのsession.phpを実行してみましょう。

```shell-session:テストコードの実行
> php56 session.php
save_handler=redis
save_path=tcp://localhost:6379
session_id=h59pqds8s5sdj9j0anmiqh63s0
```

save_handlerやsave_pathが、さきほどphp.iniに設定したものと同じになっていれば、設定とテストコードはうまく合致していそうです。

そして、ここが肝心な部分ですが、表示されているsession_idと同じものが、Redisに保存されて入れば、成功ということになります。redis-cliコマンドで確認してみましょう。

```shell-session:redis-cliでRedisにPHPセッションが保存されていることを確認
> redis-cli
127.0.0.1:6379> keys *
1) "PHPREDIS_SESSION:h59pqds8s5sdj9j0anmiqh63s0"
```

キーは含まれています。どうやら通信も行われてRedisにキーが保存できているようです。

では、$_SESSION["libname"]に代入した値は保存されているでしょうか。
セッションIDをキーにしてredis-cliからgetしてみましょう。

```shell-session:セッション変数の保存状況の確認
127.0.0.1:6379> get PHPREDIS_SESSION:h59pqds8s5sdj9j0anmiqh63s0
"libname|s:8:\"PhpRedis\";"
```

こちらも保存されているようですね。ファイルにPHPセッションを保存しているのと同様に、PHPシリアライズで保存されてます。保存内容自体は、保存先(save_handler)が変わっても同じであることも確認できたと言えそうです(当然ちゃ当然ですけど)。

# オプション

最後に、指定できるオプションを簡単にご紹介しておきます。説明自体はPhpRedisのGitHubリポジトリにあるREADME.markdownの[PHP Session handler](https://github.com/phpredis/phpredis/blob/3.0.0/README.markdown#php-session-handler)の項目に書かれています。

| オプション | 型 | デフォルト | 意味 |
|:--|:--|:--|:--|
|weight|整数(integer)|1|ホスト(サーバ)の重み付けの値。PHPセッションを複数のRedisホストに分散して保存する場合の比重を設定することができる。あるRedisサーバAが別のRedisサーバBの2倍の重みを持っているとしたら、Aは2倍のセッションを保持することになる。|
|timeout|小数(float)|86400|Redisサーバへのコネクションタイムアウトまでの秒数。この時間以内にRedisサーバに接続できなかった場合、クライアントはセッションを利用できない。|
|persistent|0もしくは1|0|永続的コネクションで利用する場合は1を指定する(試験的オプション)。|
|prefix|文字列|PHPREDIS_SESSION:|キーの接頭辞として使用する文字。さきほどの動作確認でのキーを見ると、デフォルトの接頭辞が付いているのがわかる。|
|auth|文字列|(空欄)|コマンド早出前の認証キー。Redisでauthを使っている場合に使用する。|
|database|整数|-1|データベース番号を指定する。|

オプションの指定の仕方は、さきほどのREADME.markdownの[PHP Session handler](https://github.com/phpredis/phpredis/blob/3.0.0/README.markdown#php-session-handler)の項目に書かれています(以下抜粋)。

```shell-session:オプションの指定方法
session.save_path = "tcp://host1:6379?weight=1, tcp://host2:6379?weight=2&timeout=2.5, tcp://host3:6379?weight=2"
```