RedisとPHPを高速連携!phpredisの基本を紹介
はじめに:Web開発におけるパフォーマンスの壁とRedisの力
現代のWebアプリケーションは、ユーザーエクスペリエンスの向上、リアルタイム性の追求、大量のデータ処理など、高度な要求に応える必要があります。PHPは長年にわたりWeb開発の主要な言語として利用されてきましたが、特にデータベースアクセスや計算量の多い処理がボトルネックとなり、パフォーマンスが低下することがあります。
このようなパフォーマンスの課題を解決する上で、インメモリデータストアであるRedisは非常に強力なツールとなります。Redisはその超高速な読み書き性能、多様なデータ構造のサポート、永続化オプションなどを通じて、Webアプリケーションの様々な側面でパフォーマンスを劇的に向上させることができます。
PHPアプリケーションからRedisを最大限に活用するためには、効率的な連携が不可欠です。PHPからRedisにアクセスするためのライブラリはいくつか存在しますが、中でもphpredisは、そのネイティブなC言語による実装から、他のPHP実装ライブラリに比べて卓越したパフォーマンスを発揮します。
この記事では、なぜphpredisが高速なのか、そしてその基本的な使い方から高度な機能、さらには実践的な応用例までを、詳細なコード例とともに解説します。Redisの基本的な概念から触れ、phpredisの導入方法、主要なデータ構造の操作、トランザクションやパイプラインといった高度な機能、そしてキャッシュ、セッション管理、キューといった具体的な応用例まで、幅広く掘り下げていきます。この記事を通じて、PHP開発者がphpredisを利用してアプリケーションのパフォーマンスを向上させるための知識と実践的なスキルを習得できることを目指します。
Redisとは? その特徴とPHP開発における利点
phpredisについて深く掘り下げる前に、まずは連携対象であるRedisそのものについて理解しておきましょう。Redisは “Remote Dictionary Server” の略で、Key-Valueストアとして知られる高性能なオープンソースのインメモリデータ構造サーバーです。
Redisの主な特徴:
- 高速性 (Speed): Redisの最大の強みは、データをメモリ上に保持することによる驚異的な読み書き速度です。ディスクI/Oがボトルネックとなる従来のデータベースに比べ、桁違いに高速なアクセスが可能です。
- 豊富なデータ構造 (Rich Data Structures): 単純なStringだけでなく、Lists, Sets, Sorted Sets, Hashes, Bitmaps, HyperLogLogs, Geospatial indexesといった多様なデータ構造をネイティブにサポートしています。これにより、様々な種類のデータを効率的に扱うことができます。
- アトミック操作 (Atomic Operations): 各データ構造に対する操作はアトミックに実行されます。複数のクライアントからの同時アクセスがあっても、操作の整合性が保たれます。
- 永続化 (Persistence): データはメモリ上にありますが、RDB (Redis Database) スナップショットやAOF (Append Only File) を利用することで、データをディスクに永続化し、サーバー再起動時にもデータを復旧できます。
- マスタースレーブレプリケーション (Master-Slave Replication): データの冗長化や読み取り負荷分散のためにレプリケーションを容易に設定できます。
- Pub/Sub (Publish/Subscribe): メッセージングシステムとして機能し、非同期処理やリアルタイム性の高い機能を実現できます。
- トランザクション (Transactions): 複数のコマンドをまとめて実行し、アトミック性を確保できます。
- スクリプト (Scripting): Luaスクリプトを使用して、複雑なロジックをサーバーサイドでアトミックに実行できます。
PHP開発におけるRedisの具体的な利点:
Redisのこれらの特徴は、PHPアプリケーションの様々な課題を解決し、パフォーマンスを向上させるために非常に有効です。
- キャッシュ (Caching): 最も一般的な用途です。データベースクエリの結果、計算結果、HTMLフラグメントなどをRedisにキャッシュすることで、繰り返しの処理を省略し、レスポンスタイムを大幅に短縮できます。メモリ上のデータへのアクセスは、DBへのアクセスより圧倒的に高速です。
- セッション管理 (Session Management): デフォルトのファイルベースのセッション保存は、特に複数のWebサーバーを運用している場合に問題となります。Redisをセッションストアとして利用することで、セッションデータを一元管理し、スケーラビリティとパフォーマンスを向上させることができます。
- ジョブキュー (Job Queues): 時間のかかる処理(メール送信、画像処理など)をバックグラウンドで実行するために、RedisのListsデータ構造をキューとして利用できます。ワーカープロセスがキューからタスクを取り出して実行することで、Webサーバーの負荷を軽減し、ユーザーへのレスポンスを即座に返すことができます。
- ランキング/リーダーボード (Leaderboards): Sorted Setsを利用することで、ユーザーのスコアや人気度に基づいたランキングを効率的に実装できます。スコアの更新や、特定の範囲のユーザーを取得する操作が高速です。
- リアルタイム機能 (Real-time Features): Pub/Sub機能を利用して、チャットや通知システムなど、リアルタイム性が求められる機能を実装できます。
- カウンタリング/レートリミッター (Counters/Rate Limiters): Stringの
INCRコマンドを利用して、アクセス数やクリック数などを高速にカウントできます。また、レートリミットの実装にも応用できます。 - データ構造の活用: Hashesを使ってオブジェクトを格納したり、Setsを使ってタグやユニークな要素の集合を扱ったりと、アプリケーションの要件に応じて最適なデータ構造を選択できます。
PHPとRedisの連携の課題、そしてphpredisの優位性
PHPからRedisを利用するには、Redisサーバーとの通信が必要です。PHPの標準ライブラリにはRedisに直接アクセスするための機能は含まれていません。そのため、何らかのクライアントライブラリを利用する必要があります。
PHPで利用できるRedisクライアントライブラリとしては、主に以下の二つが挙げられます。
- Predis: PHPで書かれたRedisクライアントライブラリです。Composerなどを使って容易に導入でき、PHPのオブジェクトとして扱うため親しみやすいという利点があります。PHPの標準関数と同様のAPIを提供しているため、比較的容易にRedis操作を記述できます。
- phpredis: C言語で書かれたPHPの拡張モジュールとして提供されるRedisクライアントです。PECL経由でインストールするのが一般的です。
どちらのライブラリもRedisの機能にアクセスできますが、パフォーマンスの面で決定的な違いがあります。
PredisはPHPで書かれているため、Redisサーバーとの通信やデータのシリアライズ/デシリアライズなどの処理はすべてPHPスクリプト上で実行されます。これは導入の容易さというメリットをもたらしますが、PHPの実行エンジンを介するため、どうしてもオーバーヘッドが発生します。
一方、phpredisはC言語で実装されたPHP拡張です。Redisサーバーとの通信、コマンドの送信、レスポンスの受け取り、そしてPHPのデータ型への変換などの処理は、PHPの実行エンジンよりも低レベルで、より効率的に行われます。C言語で書かれたコードは、PHPスクリプトの実行に比べて高速であるため、Redisへのアクセスが頻繁に行われるようなアプリケーションでは、phpredisを利用する方が圧倒的に高いパフォーマンスを発揮します。
特に、ネットワーク通信のラウンドトリップを削減するパイプライン機能や、複数のコマンドをアトミックに実行するトランザクションなどを多用する場合、C拡張であるphpredisのパフォーマンス上の優位性が顕著になります。phpredisはRedisの各種コマンドをPHPのメソッドとして提供しており、Predisと非常によく似たAPIを持っているため、乗り換えや学習コストも比較的低いです。
したがって、パフォーマンスを最優先する場合、特にRedisへのアクセス頻度が高いアプリケーションにおいては、phpredisを選択するのが最良の選択肢となります。
phpredisの導入
phpredisを利用するには、まずPHP環境にphpredis拡張モジュールをインストールする必要があります。インストール方法はオペレーティングシステムや環境によって異なりますが、主にPECLを使う方法とソースコードからコンパイルする方法があります。
必要な環境
- Redisサーバーがインストールされ、稼働していること。
- PHPがインストールされていること(多くの機能を利用するにはPHP 7以上が推奨されます)。
- PHPの開発環境(php-develやphp-devパッケージなど、ヘッダーファイルやビルドツールが必要)が整っていること。
PECLを使ったインストール (推奨)
PECL (PHP Extension Community Library) は、PHP拡張モジュールのインストールを容易にするためのリポジトリおよびツールです。phpredisはPECLで公開されています。
ターミナルを開き、以下のコマンドを実行します。
bash
pecl install redis
このコマンドを実行すると、phpredisの最新バージョンがダウンロードされ、コンパイル、インストールが行われます。途中で設定に関する質問が表示されることがありますが、基本的にはEnterキーを押してデフォルト設定で進めて問題ありません。
コンパイルとインストールが完了したら、PHPがphpredis拡張をロードするように設定ファイルを編集する必要があります。
php.ini ファイル(通常は/etc/php/<バージョン>/cli/php.ini や /etc/php/<バージョン>/fpm/php.ini など、利用するPHP SAPIによって場所が異なります)を開き、以下の行を追加します。
ini
extension=redis.so
または、システムによっては拡張モジュールごとに設定ファイルが分かれている場合があります (/etc/php/<バージョン>/mods-available/redis.ini など)。その場合は、そのファイルに上記の行を記述します。設定ファイルを有効にするには、以下のコマンドを実行することがあります。
bash
phpenmod redis
設定ファイルの変更を有効にするには、PHPを動作させているSAPI(WebサーバーやPHP-FPMなど)を再起動する必要があります。
“`bash
Apacheの場合
sudo systemctl restart apache2
Nginx + PHP-FPMの場合
sudo systemctl restart php<バージョン>-fpm.service # 例: sudo systemctl restart php7.4-fpm.service
“`
ソースコードからコンパイルしてインストール
PECLが利用できない環境や、特定のバージョンをインストールしたい場合などは、ソースコードからコンパイルする方法を選択できます。
-
phpredisのGitHubリポジトリからソースコードをダウンロードします。
bash
git clone https://github.com/phpredis/phpredis.git
cd phpredis特定のバージョンをインストールしたい場合は、タグを指定してチェックアウトします。
bash
git checkout <tag名> # 例: git checkout 5.3.5 -
phpizeコマンドを実行して、PHP拡張のビルドに必要なスクリプトを生成します。bash
phpize -
configureを実行してビルド設定を行います。bash
./configure -
makeを実行してコンパイルします。bash
make -
make installを実行してインストールします。bash
sudo make install
インストール場所は、php.ini の extension_dir 設定で確認できます。コンパイルが完了すると、通常はそのディレクトリに redis.so ファイルがコピーされます。
あとはPECLの場合と同様に、php.ini に extension=redis.so を追加し、Webサーバー/PHP-FPMを再起動すれば完了です。
インストールの確認
phpredisが正しくインストールされ、PHPにロードされているかを確認するには、phpinfo() 関数を利用するのが最も簡単です。
“`php
“`
このスクリプトを実行し、出力された情報の中に「redis」のセクションがあれば、インストールは成功しています。
また、簡単なPHPスクリプトでphpredisのクラスが利用できるかを確認することもできます。
“`php
getMessage() . “\n”;
}
?>
“`
このスクリプトを実行し、”phpredis is installed and enabled.” と “Redis class instantiated successfully.” の両方が表示されれば、準備完了です。
phpredisの基本的な使い方:接続と切断
phpredisをPHPスクリプトで利用するためには、まずRedisサーバーに接続する必要があります。phpredisでは主に二つの接続方法が提供されています。
connect(host, port, timeout): Redisサーバーに接続します。これはリクエストごとに接続・切断を行う標準的な方法です。pconnect(host, port, timeout): Redisサーバーへの持続的接続 (Persistent Connection) を確立します。これは、PHP-FPMなどのSAPIを利用している場合に非常に有用です。リクエスト終了後も接続が開いたままプールされ、次のリクエストで同じ設定の接続が必要になった場合に再利用されます。これにより、TCP接続確立のオーバーヘッドを削減し、パフォーマンスを向上させることができます。
特別な理由がない限り、PHP-FPM環境では pconnect の利用が推奨されます。CLIスクリプトやApache + mod_php のような環境では、connect を使用するのが一般的です。
接続例
“`php
connect(‘127.0.0.1’, 6379)) {
echo “Connected to Redis using connect()\n”;
// ここでRedis操作を行う
$redis->set(‘mykey’, ‘Hello Redis’);
echo “mykey value: ” . $redis->get(‘mykey’) . “\n”;
} else {
echo “Failed to connect to Redis using connect()\n”;
}
} catch (RedisException $e) {
echo “Redis connection error: ” . $e->getMessage() . “\n”;
}
echo “— Separator —\n”;
// 持続的接続 (pconnect)
// 第三引数に persisten_id を指定することも可能。省略時はホスト:ポート:タイムアウトがIDになる。
// 同一の ID を持つ接続があればそれが再利用される。
try {
// 例: 永続IDを指定しない場合
if ($redis->pconnect(‘127.0.0.1’, 6379)) {
echo “Connected to Redis using pconnect()\n”;
// ここでRedis操作を行う
$redis->set(‘myotherkey’, ‘Persistent Connection’);
echo “myotherkey value: ” . $redis->get(‘myotherkey’) . “\n”;
} else {
echo “Failed to connect to Redis using pconnect()\n”;
}
// 例: 永続IDを指定する場合
// if ($redis->pconnect(‘127.0.0.1’, 6379, 0, ‘my_persistent_connection’)) {
// echo “Connected to Redis using pconnect() with ID\n”;
// } else {
// echo “Failed to connect to Redis using pconnect() with ID\n”;
// }
} catch (RedisException $e) {
echo “Redis persistent connection error: ” . $e->getMessage() . “\n”;
}
// 接続は自動的に閉じられるか、明示的に close() を呼び出す
// connect() で接続した場合、通常スクリプト終了時に自動的に閉じられる
// pconnect() で接続した場合、プールされるため close() は呼び出さないことが多い
// $redis->close();
?>
“`
接続オプション:
connect() および pconnect() メソッドには、ホスト、ポート、タイムアウト以外にもいくつかのオプションを指定できます。
timeout: 接続タイムアウト (秒)。デフォルトは0(無制限)。非ブロッキングI/Oを使用している場合、読み取り/書き込みのタイムアウトとしても機能します。read_timeout: 読み取り操作のタイムアウト (秒)。PHP 7以降で利用可能。context: PHPのストリームコンテキストオプションを指定できます(例:SSL/TLS接続)。persistent_id(pconnectのみ): 持続的接続の識別子。同一の識別子を持つ接続が再利用されます。
認証 (AUTH)
Redisサーバーがパスワード認証を要求する場合、接続後に auth() メソッドで認証を行う必要があります。
“`php
connect(‘127.0.0.1’, 6379)) {
// 認証が必要な場合
if ($redis->auth(‘your_redis_password’)) {
echo “Authenticated successfully.\n”;
// 認証成功後、Redis操作を行う
$redis->set(‘secured_key’, ‘Secured Value’);
echo “secured_key value: ” . $redis->get(‘secured_key’) . “\n”;
} else {
echo “Authentication failed.\n”;
}
} else {
echo “Failed to connect to Redis.\n”;
}
} catch (RedisException $e) {
echo “Redis error: ” . $e->getMessage() . “\n”;
}
?>
“`
DBの選択 (SELECT)
Redisはデフォルトで16個の論理データベース(DB 0からDB 15)を持っています。特に指定しない限り、DB 0に接続されます。select() メソッドを使って別のDBを選択できます。
“`php
connect(‘127.0.0.1’, 6379);
echo “Connected to DB 0 by default.\n”;
$redis->set(‘db0_key’, ‘Value in DB 0’);
echo “db0_key value in DB 0: ” . $redis->get(‘db0_key’) . “\n”;
// DB 1 を選択
if ($redis->select(1)) {
echo “Switched to DB 1.\n”;
$redis->set(‘db1_key’, ‘Value in DB 1’);
echo “db1_key value in DB 1: ” . $redis->get(‘db1_key’) . “\n”;
// DB 0 に戻る
$redis->select(0);
echo “Switched back to DB 0.\n”;
echo “db0_key value in DB 0: ” . $redis->get(‘db0_key’) . “\n”;
echo “db1_key value in DB 0: ” . $redis->get(‘db1_key’) . “\n”; // null が返るはず
} else {
echo “Failed to select DB 1.\n”;
}
} catch (RedisException $e) {
echo “Redis error: ” . $e->getMessage() . “\n”;
}
?>
“`
接続の切断 (CLOSE)
connect() で確立した接続は、スクリプトの実行終了時に自動的に閉じられます。しかし、明示的に接続を閉じたい場合は close() メソッドを呼び出します。
pconnect() で確立した持続的接続は、明示的に close() を呼び出しても、実際には閉じられずにプールに戻されます。完全に接続を閉じたい場合は、PHPプロセス自体を終了させる必要があります。通常、PHP-FPM環境で pconnect() を利用している場合は、close() を呼び出す必要はありません。
“`php
connect(‘127.0.0.1’, 6379);
echo “Connected.\n”;
// Redis操作…
$redis->set(‘temp_key’, ‘some value’);
// 接続を明示的に閉じる (connectの場合)
$redis->close();
echo “Connection closed.\n”;
// 再度接続したい場合は connect() を再度呼び出す必要がある
// $redis->connect(‘127.0.0.1’, 6379);
// echo “Reconnected.\n”;
} catch (RedisException $e) {
echo “Redis error: ” . $e->getMessage() . “\n”;
}
?>
“`
エラーハンドリング
Redisコマンドの実行中にエラーが発生した場合、phpredisは RedisException をスローします。重要なRedis操作(特に接続や書き込み操作)を行う際は、try...catch ブロックでエラーを捕捉し、適切に処理することが推奨されます。
“`php
connect(‘127.0.0.1’, 9999)) {
echo “Connected.\n”;
// Redis操作
} else {
echo “Failed to connect.\n”;
// ここでエラーをログに記録するなど
}
} catch (RedisException $e) {
// 接続失敗などのエラーを捕捉
echo “Caught a RedisException: ” . $e->getMessage() . “\n”;
// エラーメッセージをログに記録したり、ユーザーに適切なメッセージを表示したり
} catch (Exception $e) {
// その他の例外を捕捉
echo “Caught a general Exception: ” . $e->getMessage() . “\n”;
}
?>
“`
phpredisで利用できる主要なデータ構造と操作
Redisの強力さは、その多様なデータ構造にあります。phpredisはこれらのデータ構造に対する操作を、直感的でPHPらしいメソッドとして提供しています。ここでは、主要なデータ構造とその基本的な操作方法を紹介します。
1. Strings
最も基本的なデータ構造です。キーと単一の値を関連付けます。値は任意のバイナリセーフなデータ(文字列、バイナリデータ、整数、浮動小数点数など)を格納できます。
- 値を設定する:
set(key, value) - 値を取得する:
get(key) - キーが存在しなければ値を設定する:
setnx(key, value) - 有効期限 (TTL) 付きで値を設定する:
setex(key, seconds, value),psetex(key, milliseconds, value) - 複数のキーと値を一度に設定する:
mset(array) - 複数のキーの値を一度に取得する:
mget(array) - 値を整数としてインクリメントする:
incr(key),incrBy(key, increment) - 値を浮動小数点数としてインクリメントする:
incrByFloat(key, increment) - 値を整数としてデクリメントする:
decr(key),decrBy(key, decrement) - 値に文字列を追記する:
append(key, value) - 値の長さを取得する:
strlen(key)
“`php
connect(‘127.0.0.1’, 6379); // 例としてconnectを使用
// SETとGET
$redis->set(‘greeting’, ‘Hello, Redis!’);
echo “Greeting: ” . $redis->get(‘greeting’) . “\n”; // Output: Greeting: Hello, Redis!
// SETNX (存在しない場合のみ設定)
$redis->setnx(‘greeting’, ‘Hello again!’); // キーは既に存在するため設定されない
$redis->setnx(‘new_greeting’, ‘Hello from new!’); // キーは存在しないため設定される
echo “Greeting after SETNX: ” . $redis->get(‘greeting’) . “\n”; // Output: Greeting after SETNX: Hello, Redis!
echo “New greeting: ” . $redis->get(‘new_greeting’) . “\n”; // Output: New greeting: Hello from new!
// SETEX (有効期限付き設定)
$redis->setex(‘temp_key’, 10, ‘This will expire in 10 seconds’);
echo “Temp key: ” . $redis->get(‘temp_key’) . “\n”;
// 10秒後には null が返る
// MSETとMGET
$redis->mset([‘key1’ => ‘value1’, ‘key2’ => ‘value2’, ‘key3’ => ‘value3’]);
$values = $redis->mget([‘key1’, ‘key2’, ‘non_existent_key’]);
print_r($values);
/* Output:
Array
(
[0] => value1
[1] => value2
[2] => false // 存在しないキーは false になる
)
*/
// INCR/DECR
$redis->set(‘counter’, 10);
echo “Counter: ” . $redis->get(‘counter’) . “\n”; // Output: Counter: 10
$redis->incr(‘counter’);
echo “Counter after INCR: ” . $redis->get(‘counter’) . “\n”; // Output: Counter after INCR: 11
$redis->incrBy(‘counter’, 5);
echo “Counter after INCRBY 5: ” . $redis->get(‘counter’) . “\n”; // Output: Counter after INCRBY 5: 16
$redis->decr(‘counter’);
echo “Counter after DECR: ” . $redis->get(‘counter’) . “\n”; // Output: Counter after DECR: 15
$redis->decrBy(‘counter’, 3);
echo “Counter after DECRBY 3: ” . $redis->get(‘counter’) . “\n”; // Output: Counter after DECRBY 3: 12
// APPEND
$redis->set(‘phrase’, ‘Hello’);
$redis->append(‘phrase’, ‘ World’);
echo “Phrase after APPEND: ” . $redis->get(‘phrase’) . “\n”; // Output: Phrase after APPEND: Hello World
// STRLEN
echo “Length of ‘phrase’: ” . $redis->strlen(‘phrase’) . “\n”; // Output: Length of ‘phrase’: 11
$redis->close(); // 接続を閉じる (connectの場合)
?>
“`
2. Hashes
フィールドと値のペアの集まりを格納するデータ構造です。一つのキーに対して、複数のフィールドとその値をマッピングできます。オブジェクトや構造体を表現するのに適しています。
- フィールドに値を設定する:
hset(key, field, value) - フィールドの値を取得する:
hget(key, field) - 複数のフィールドに値を一度に設定する:
hmset(key, array) - 複数のフィールドの値を一度に取得する:
hmget(key, array) - 全てのフィールドと値を取得する:
hgetall(key) - 全てのフィールド名を取得する:
hkeys(key) - 全ての値を取得する:
hvals(key) - フィールドの値を整数としてインクリメントする:
hincrby(key, field, increment) - フィールドの値を浮動小数点数としてインクリメントする:
hincrbyfloat(key, field, increment) - フィールドが存在するか確認する:
hexists(key, field) - フィールドを削除する:
hdel(key, field1, field2, ...) - ハッシュに含まれるフィールドの数を取得する:
hlen(key)
“`php
connect(‘127.0.0.1’, 6379);
// HSET と HGET
$redis->hset(‘user:1’, ‘name’, ‘Alice’);
$redis->hset(‘user:1’, ‘age’, 30);
echo “User 1 Name: ” . $redis->hget(‘user:1’, ‘name’) . “\n”; // Output: User 1 Name: Alice
// HMSET と HMGET
$userData = [‘city’ => ‘Tokyo’, ‘country’ => ‘Japan’];
$redis->hmset(‘user:1’, $userData);
$retrievedData = $redis->hmget(‘user:1’, [‘name’, ‘city’, ‘email’]); // emailは存在しない
print_r($retrievedData);
/* Output:
Array
(
[name] => Alice
[city] => Tokyo
[email] => false // 存在しないフィールドは false になる
)
*/
// HGETALL
$allUserData = $redis->hgetall(‘user:1’);
print_r($allUserData);
/* Output:
Array
(
[name] => Alice
[age] => 30
[city] => Tokyo
[country] => Japan
)
*/
// HKEYS と HVALS
$fields = $redis->hkeys(‘user:1’);
print_r($fields);
/* Output:
Array
(
[0] => name
[1] => age
[2] => city
[3] => country
)
*/
$values = $redis->hvals(‘user:1’);
print_r($values);
/* Output:
Array
(
[0] => Alice
[1] => 30
[2] => Tokyo
[3] => Japan
)
*/
// HINCRBY
$redis->hset(‘product:abc’, ‘stock’, 100);
$redis->hincrby(‘product:abc’, ‘stock’, -10); // 在庫を減らす
echo “Product abc Stock: ” . $redis->hget(‘product:abc’, ‘stock’) . “\n”; // Output: Product abc Stock: 90
// HDEL
$redis->hdel(‘user:1’, ‘country’);
echo “User 1 Country after DEL: ” . $redis->hget(‘user:1’, ‘country’) . “\n”; // Output: User 1 Country after DEL:
// HLEN
echo “Number of fields in user:1: ” . $redis->hlen(‘user:1’) . “\n”; // Output: Number of fields in user:1: 3
$redis->close();
?>
“`
3. Lists
要素の順番付けられた集合です。リストの両端(左端と右端)から要素を追加したり取り出したりできます。キューやスタックとして利用するのに適しています。
- リストの左端に要素を追加する:
lpush(key, value1, value2, ...) - リストの右端に要素を追加する:
rpush(key, value1, value2, ...) - リストの左端から要素を取り出す:
lpop(key) - リストの右端から要素を取り出す:
rpop(key) - 指定した範囲の要素を取得する:
lrange(key, start, stop) - リストの長さを取得する:
llen(key) - 指定したインデックスの要素を取得する:
lindex(key, index) - 指定した値を持つ要素をリストから削除する:
lrem(key, value, count) - リストを指定した範囲でトリムする:
ltrim(key, start, stop) - リストが空になるまで、指定したキーから要素をブロッキングモードで取り出す:
blpop(keys, timeout),brpop(keys, timeout)
“`php
connect(‘127.0.0.1’, 6379);
// LPUSH と RPUSH
$redis->del(‘mylist’); // 既存のリストを削除
$redis->lpush(‘mylist’, ‘element1’); // [‘element1’]
$redis->rpush(‘mylist’, ‘element2’); // [‘element1’, ‘element2’]
$redis->lpush(‘mylist’, ‘element0’); // [‘element0’, ‘element1’, ‘element2’]
$redis->rpush(‘mylist’, ‘element3’); // [‘element0’, ‘element1’, ‘element2’, ‘element3’]
// LRANGE
$list = $redis->lrange(‘mylist’, 0, -1); // 全ての要素を取得
print_r($list);
/* Output:
Array
(
[0] => element0
[1] => element1
[2] => element2
[3] => element3
)
*/
// LPOP と RPOP
echo “Left pop: ” . $redis->lpop(‘mylist’) . “\n”; // Output: Left pop: element0, リスト: [‘element1’, ‘element2’, ‘element3’]
echo “Right pop: ” . $redis->rpop(‘mylist’) . “\n”; // Output: Right pop: element3, リスト: [‘element1’, ‘element2’]
// LLEN
echo “List length: ” . $redis->llen(‘mylist’) . “\n”; // Output: List length: 2
// LINDEX
echo “Element at index 1: ” . $redis->lindex(‘mylist’, 1) . “\n”; // Output: Element at index 1: element2
// LREM
$redis->rpush(‘mylist’, ‘element1’); // [‘element1’, ‘element2’, ‘element1’]
$redis->lrem(‘mylist’, ‘element1’, 1); // 左端から1つだけ element1 を削除
$list = $redis->lrange(‘mylist’, 0, -1); // 全ての要素を取得
print_r($list); // Output: Array( [0] => element2 [1] => element1 )
// LTRIM
$redis->ltrim(‘mylist’, 1, 1); // インデックス1から1までの要素を残す (つまりインデックス1のみ)
$list = $redis->lrange(‘mylist’, 0, -1); // 全ての要素を取得
print_r($list); // Output: Array( [0] => element1 )
$redis->close();
?>
“`
BLPOP / BRPOP は、要素がリストに追加されるまで接続をブロックするコマンドです。これはワーカープロセスがキューからタスクを取り出す際に非常に便利です。指定したタイムアウト時間、要素が追加されるのを待ちます。
“`php
connect(‘127.0.0.1’, 6379);
echo “Waiting for items in myqueue…\n”;
// myqueueから要素を10秒間ブロックして待ちます
$item = $redis->blpop(‘myqueue’, 10); // 返り値は [‘queue_name’, ‘item_value’] の配列、タイムアウト時は false
if ($item) {
echo “Received item from ” . $item[0] . “: ” . $item[1] . “\n”;
// ここで受け取ったアイテムを使った処理を行う
} else {
echo “Timeout after 10 seconds, no items received.\n”;
}
$redis->close();
?>
connect(‘127.0.0.1’, 6379);
echo “Adding item to myqueue…\n”;
$redis->rpush(‘myqueue’, ‘task_data_123’);
echo “Item added.\n”;
$redis->close();
?>
“`
4. Sets
重複しない要素の順序付けされていない集合です。要素の追加、削除、存在チェック、集合演算(和集合、積集合、差集合)などが高速に行えます。
- 要素を追加する:
sadd(key, member1, member2, ...) - 要素を削除する:
srem(key, member1, member2, ...) - 集合の全ての要素を取得する:
smembers(key) - 要素が集合に存在するか確認する:
sismember(key, member) - 集合の要素数を取得する:
scard(key) - 集合からランダムに要素を取り出す (元の集合からは削除される):
spop(key, count) - 集合からランダムに要素を取得する (元の集合は変更されない):
srandmember(key, count) - 複数の集合の和集合を取得する:
sunion(key1, key2, ...) - 複数の集合の和集合を新しいキーに保存する:
sunionstore(destination, key1, key2, ...) - 複数の集合の積集合を取得する:
sinter(key1, key2, ...) - 複数の集合の積集合を新しいキーに保存する:
sinterstore(destination, key1, key2, ...) - 複数の集合の差集合を取得する:
sdiff(key1, key2, ...) - 複数の集合の差集合を新しいキーに保存する:
sdiffstore(destination, key1, key2, ...)
“`php
connect(‘127.0.0.1’, 6379);
// SADD と SMEMBERS
$redis->del(‘myset’);
$redis->sadd(‘myset’, ‘memberA’);
$redis->sadd(‘myset’, ‘memberB’);
$redis->sadd(‘myset’, ‘memberA’); // 重複は無視される
$redis->sadd(‘myset’, ‘memberC’);
$members = $redis->smembers(‘myset’);
print_r($members); // 出力順序は保証されない 例: Array( [0] => memberA [1] => memberB [2] => memberC )
// SCARD
echo “Set size: ” . $redis->scard(‘myset’) . “\n”; // Output: Set size: 3
// SISMEMBER
echo “Is memberB in set? ” . ($redis->sismember(‘myset’, ‘memberB’) ? ‘Yes’ : ‘No’) . “\n”; // Output: Yes
echo “Is memberD in set? ” . ($redis->sismember(‘myset’, ‘memberD’) ? ‘Yes’ : ‘No’) . “\n”; // Output: No
// SREM
$redis->srem(‘myset’, ‘memberB’);
$members = $redis->smembers(‘myset’);
print_r($members); // 例: Array( [0] => memberA [1] => memberC )
// SPOP (ランダムに取得&削除)
echo “Popped random member: ” . $redis->spop(‘myset’) . “\n”;
echo “Set size after pop: ” . $redis->scard(‘myset’) . “\n”;
// 集合演算
$redis->del(‘set1’, ‘set2’);
$redis->sadd(‘set1’, ‘apple’, ‘banana’, ‘cherry’);
$redis->sadd(‘set2’, ‘banana’, ‘date’, ‘fig’);
$union = $redis->sunion(‘set1’, ‘set2’); // 和集合
echo “Union:\n”; print_r($union); // apple, banana, cherry, date, fig
$inter = $redis->sinter(‘set1’, ‘set2’); // 積集合
echo “Intersection:\n”; print_r($inter); // banana
$diff = $redis->sdiff(‘set1’, ‘set2’); // 差集合 (set1にあるがset2にないもの)
echo “Difference (set1 – set2):\n”; print_r($diff); // apple, cherry
// 演算結果を新しいキーに保存
$redis->sunionstore(‘union_set’, ‘set1’, ‘set2’);
echo “Union set size: ” . $redis->scard(‘union_set’) . “\n”; // Output: Union set size: 5
$redis->close();
?>
“`
5. Sorted Sets (ZSets)
重複しない要素の順序付けられた集合ですが、各要素にはスコアが関連付けられています。スコアを使って要素をソートすることができます。ランキングや優先度付きキューなどに利用されます。
- 要素をスコア付きで追加する:
zadd(key, score1, member1, score2, member2, ...) - 指定したスコア範囲または辞書式順序で要素を取得する:
zrangebyscore(key, min, max, options) - 指定した順位範囲 (インデックス) で要素を取得する:
zrange(key, start, stop, options)(optionsでスコア含めるか指定可能) - 逆順 (スコア降順) で要素を取得する:
zrevrange(key, start, stop, options),zrevrangebyscore(key, max, min, options) - 要素を削除する:
zrem(key, member1, member2, ...) - Sorted Setの要素数を取得する:
zcard(key) - 指定したスコア範囲内の要素数を取得する:
zcount(key, min, max) - 指定した要素のスコアを取得する:
zscore(key, member) - 指定した要素の順位を取得する (スコア昇順、0ベース):
zrank(key, member) - 指定した要素の逆順順位を取得する (スコア降順、0ベース):
zrevrank(key, member) - 指定した要素のスコアをインクリメントする:
zincrby(key, increment, member)
“`php
connect(‘127.0.0.1’, 6379);
// ZADD
$redis->del(‘leaders’);
$redis->zadd(‘leaders’, 100, ‘Alice’);
$redis->zadd(‘leaders’, 200, ‘Bob’);
$redis->zadd(‘leaders’, 150, ‘Charlie’);
$redis->zadd(‘leaders’, 50, ‘David’);
$redis->zadd(‘leaders’, 180, ‘Eve’);
// ZRANGE (スコア昇順)
echo “Leaders (ascending):\n”;
$leadersAsc = $redis->zrange(‘leaders’, 0, -1);
print_r($leadersAsc); // Output: Array( [0] => David [1] => Alice [2] => Charlie [3] => Eve [4] => Bob )
// ZRANGE with scores (スコア昇順)
echo “Leaders with scores (ascending):\n”;
$leadersAscWithScores = $redis->zrange(‘leaders’, 0, -1, true); // trueでスコアを含む
print_r($leadersAscWithScores);
/* Output:
Array
(
[David] => 50.0
[Alice] => 100.0
[Charlie] => 150.0
[Eve] => 180.0
[Bob] => 200.0
)
*/
// ZREVRANGE (スコア降順 – ランキング)
echo “Leaders (descending):\n”;
$leadersDesc = $redis->zrevrange(‘leaders’, 0, -1);
print_r($leadersDesc); // Output: Array( [0] => Bob [1] => Eve [2] => Charlie [3] => Alice [4] => David )
// ZRANK / ZREVRANK (順位)
echo “Bob’s rank (ascending, 0-based): ” . $redis->zrank(‘leaders’, ‘Bob’) . “\n”; // Output: 4
echo “Bob’s rank (descending, 0-based): ” . $redis->zrevrank(‘leaders’, ‘Bob’) . “\n”; // Output: 0
// ZSCORE
echo “Charlie’s score: ” . $redis->zscore(‘leaders’, ‘Charlie’) . “\n”; // Output: 150.0
// ZINCRBY (スコア更新)
$redis->zincrby(‘leaders’, 30, ‘Alice’); // Aliceのスコアを30増やす (100 + 30 = 130)
echo “Alice’s new score: ” . $redis->zscore(‘leaders’, ‘Alice’) . “\n”; // Output: 130.0
// ZCOUNT (スコア範囲内の要素数)
echo “Number of leaders with score between 100 and 180: ” . $redis->zcount(‘leaders’, 100, 180) . “\n”; // Output: 3 (Alice=130, Charlie=150, Eve=180)
// ZRANGEBYSCORE (スコア範囲で取得)
echo “Leaders with score between 100 and 180:\n”;
$filteredLeaders = $redis->zrangebyscore(‘leaders’, 100, 180);
print_r($filteredLeaders); // Output順序はスコア昇順: Array( [0] => Alice [1] => Charlie [2] => Eve )
// ZREM
$redis->zrem(‘leaders’, ‘David’); // Davidをランキングから削除
echo “Leaders after removing David:\n”;
$leaders = $redis->zrange(‘leaders’, 0, -1, true);
print_r($leaders);
/* Output:
Array
(
[Alice] => 130.0 // Aliceのスコアが更新されている
[Charlie] => 150.0
[Eve] => 180.0
[Bob] => 200.0
)
*/
$redis->close();
?>
“`
6. Keys全般の操作
特定のデータ型に依存しない、キー自体の操作に関するメソッドも多く提供されています。
- 指定したパターンにマッチするキーを検索する:
keys(pattern)– 注意!本番環境での広範なパターン検索は避けるべき! Redisサーバーをブロックする可能性があります。代替としてSCANコマンドを使用することを検討してください。 - キーが存在するか確認する:
exists(key) - キーを削除する:
del(key1, key2, ...) - バックグラウンドでキーを削除する:
unlink(key1, key2, ...)– 大量のキー削除時のブロックを防ぐ - キーに有効期限を設定する:
expire(key, seconds),pexpire(key, milliseconds) - キーにUNIXタイムスタンプで有効期限を設定する:
expireat(key, timestamp),pexpireat(key, milliseconds_timestamp) - キーの有効期限 (秒/ミリ秒) を取得する:
ttl(key),pttl(key) - キーの有効期限を解除する:
persist(key) - キーの名前を変更する:
rename(oldkey, newkey) - キーの名前を変更する (新しいキー名が存在しない場合のみ):
renamenx(oldkey, newkey) - キーのデータ型を取得する:
type(key)(string, list, set, zset, hash, none) - 現在のデータベースの全てのキーを削除する:
flushdb()– 運用注意! - サーバー上の全てのデータベースの全てのキーを削除する:
flushall()– 運用注意!
“`php
connect(‘127.0.0.1’, 6379);
// SET でキーを作成
$redis->set(‘mykey1’, ‘value1’);
$redis->set(‘mykey2’, ‘value2’);
$redis->set(‘anotherkey’, ‘value3’);
// EXISTS
echo “Does mykey1 exist? ” . ($redis->exists(‘mykey1’) ? ‘Yes’ : ‘No’) . “\n”; // Output: Yes
echo “Does non_existent_key exist? ” . ($redis->exists(‘non_existent_key’) ? ‘Yes’ : ‘No’) . “\n”; // Output: No
// TYPE
echo “Type of mykey1: ” . $redis->type(‘mykey1’) . “\n”; // Output: string
$redis->lpush(‘mylist’, ‘a’, ‘b’);
echo “Type of mylist: ” . $redis->type(‘mylist’) . “\n”; // Output: list
// KEYS (開発/デバッグ環境でのみ慎重に使用)
echo “Keys matching ‘mykey*’:\n”;
$keys = $redis->keys(‘mykey*’);
print_r($keys); // Output: Array( [0] => mykey1 [1] => mykey2 ) (順序は保証されない)
// EXPIRE と TTL
$redis->expire(‘mykey1’, 5); // mykey1 を5秒後に期限切れにする
echo “TTL for mykey1: ” . $redis->ttl(‘mykey1’) . ” seconds\n”; // 残り秒数、-2はキーが存在しない、-1は期限なし
sleep(6); // 6秒待つ
echo “Does mykey1 exist after 6 seconds? ” . ($redis->exists(‘mykey1’) ? ‘Yes’ : ‘No’) . “\n”; // Output: No
// PERSIST
$redis->set(‘volatile_key’, ‘temp’);
$redis->expire(‘volatile_key’, 10);
echo “TTL for volatile_key: ” . $redis->ttl(‘volatile_key’) . ” seconds\n”; // positive value
$redis->persist(‘volatile_key’);
echo “TTL for volatile_key after PERSIST: ” . $redis->ttl(‘volatile_key’) . ” seconds\n”; // Output: -1
// RENAME
$redis->rename(‘anotherkey’, ‘renamedkey’);
echo “Does anotherkey exist? ” . ($redis->exists(‘anotherkey’) ? ‘Yes’ : ‘No’) . “\n”; // Output: No
echo “Does renamedkey exist? ” . ($redis->exists(‘renamedkey’) ? ‘Yes’ : ‘No’) . “\n”; // Output: Yes
// DEL
$redis->del(‘mykey2’, ‘renamedkey’);
echo “Does mykey2 exist after DEL? ” . ($redis->exists(‘mykey2’) ? ‘Yes’ : ‘No’) . “\n”; // Output: No
echo “Does renamedkey exist after DEL? ” . ($redis->exists(‘renamedkey’) ? ‘Yes’ : ‘No’) . “\n”; // Output: No
// UNLINK (非同期削除)
$redis->set(‘large_key’, str_repeat(‘a’, 1024 * 1024)); // 1MBのデータ
echo “Deleting large_key using UNLINK…\n”;
$redis->unlink(‘large_key’); // バックグラウンドで削除
echo “UNLINK command sent.\n”; // PHPスクリプトはすぐに続行される
// Caution: FLUSHDB / FLUSHALL
// $redis->flushdb(); // 現在のDBの全てのキーを削除
// $redis->flushall(); // 全てのDBの全てのキーを削除
$redis->close();
?>
“`
より高度なphpredisの使い方
phpredisは、基本的なデータ構造操作だけでなく、Redisが提供するより高度な機能もサポートしています。これらの機能を活用することで、アプリケーションのパフォーマンスや信頼性をさらに向上させることができます。
1. トランザクション (MULTI/EXEC)
Redisトランザクションは、複数のコマンドをまとめて実行し、その実行をアトミックに(全て成功するか、全て失敗するか)行うための機能です。MULTI コマンドでトランザクションを開始し、その後のコマンドはキューに入れられます。EXEC コマンドでキューに入れられた全てのコマンドを実行します。DISCARD コマンドでキューを破棄し、トランザクションを中止できます。
phpredisでは、multi() メソッドでトランザクションを開始し、その後のメソッド呼び出しは自動的にキューイングされます。exec() メソッドで実行、discard() メソッドで破棄します。
“`php
connect(‘127.0.0.1’, 6379);
// トランザクションを開始
$redis->multi();
// キューに入れるコマンド
$redis->set(‘counter_tx’, 10);
$redis->incr(‘counter_tx’);
$redis->incrBy(‘counter_tx’, 5);
$redis->set(‘other_key_tx’, ‘hello’);
// exec()を呼び出すまでコマンドは実行されない
// exec()はキュー内の各コマンドの実行結果を格納した配列を返す
$results = $redis->exec();
// 結果を確認
print_r($results);
/* Output例:
Array
(
[0] => true // SET ‘counter_tx’ の結果 (成功なら true)
[1] => 11 // INCR ‘counter_tx’ の結果
[2] => 16 // INCRBY ‘counter_tx’, 5 の結果
[3] => true // SET ‘other_key_tx’ の結果
)
*/
echo “Final value of counter_tx: ” . $redis->get(‘counter_tx’) . “\n”; // Output: 16
echo “Value of other_key_tx: ” . $redis->get(‘other_key_tx’) . “\n”; // Output: hello
echo “— DISCARD Example —\n”;
// DISCARD の例
$redis->multi();
$redis->set(‘temp_key_tx’, ‘temp_value’);
$redis->incr(‘non_existent_counter’); // この時点ではエラーにならない
$redis->discard(); // トランザクションを破棄
echo “Value of temp_key_tx after DISCARD: ” . $redis->get(‘temp_key_tx’) . “\n”; // Output: false (セットされていない)
echo “Value of non_existent_counter after DISCARD: ” . $redis->get(‘non_existent_counter’) . “\n”; // Output: false
$redis->close();
?>
“`
トランザクションは、複数の関連する操作を不可分な一連の操作として実行したい場合に役立ちます。例えば、「在庫を減らす」と「注文履歴に追加する」といった操作を同時に行いたい場合などです。
2. パイプライン (Pipeline)
パイプラインは、複数のコマンドをバッファリングして一度にRedisサーバーに送信し、全てのコマンドのレスポンスをまとめて受け取るための機能です。これにより、コマンドごとにネットワークラウンドトリップが発生するのを防ぎ、特に多くのコマンドを連続して実行する場合に全体の実行時間を大幅に短縮できます。
phpredisでは、pipeline() メソッドでパイプラインモードを開始し、その後のメソッド呼び出しはキューイングされます。パイプラインモードを終了する際に、キューに入れられた全てのコマンドが送信され、結果が返されます。
“`php
connect(‘127.0.0.1’, 6379);
// 通常のコマンド実行(パイプラインなし)
echo “— Normal Execution —\n”;
$start = microtime(true);
for ($i = 0; $i < 100; $i++) {
$redis->set(“key:$i”, “value:$i”);
}
echo “Normal execution took ” . (microtime(true) – $start) * 1000 . ” ms\n”;
// パイプラインを使った実行
echo “— Pipeline Execution —\n”;
$start = microtime(true);
// パイプラインモードを開始
$pipeline = $redis->pipeline();
// コマンドをキューに入れる
for ($i = 0; $i < 100; $i++) {
$pipeline->set(“key:$i”, “value:$i”);
}
// パイプラインを実行し、結果を取得
$results = $pipeline->exec(); // パイプラインの場合は exec() で実行・結果取得
echo “Pipeline execution took ” . (microtime(true) – $start) * 1000 . ” ms\n”;
// 結果はキューに入れた順に配列で返される
// print_r($results); // 100個の true が含まれる配列
$redis->close();
?>
“`
上記の例のように、100回の SET コマンドを通常の方法とパイプラインで実行した場合、パイプラインを使った方が圧倒的に高速であることがわかります。これは、ネットワークの遅延がコマンド実行時間に与える影響が大きいためです。多くの読み書きをまとめて行う必要がある場合に、パイプラインは非常に有効な最適化手法です。
3. Pub/Sub (Publish/Subscribe)
Pub/Sub機能は、メッセージングシステムとして機能します。クライアントは特定のチャンネルを購読 (Subscribe) し、別のクライアントはそのチャンネルにメッセージを発行 (Publish) します。購読している全てのクライアントにメッセージが配信されます。
phpredisでは、publish() でメッセージを発行し、subscribe() や psubscribe() (パターンマッチング購読) でチャンネルを購読します。購読はブロッキング操作であり、メッセージを受け取るまで処理が停止します。そのため、通常は独立したワーカープロセスなどで実行されます。
“`php
connect(‘127.0.0.1’, 6379);
echo “Publishing message to chat:news channel…\n”;
$redis->publish(‘chat:news’, ‘Hello subscribers! Breaking news!’);
echo “Message published.\n”;
$redis->close();
?>
connect(‘127.0.0.1’, 6379);
echo “Subscribing to chat:news channel…\n”;
// 購読処理を開始
// コールバック関数はメッセージが届くたびに実行される
$redis->subscribe([‘chat:news’], function($redis, $channel, $message) {
echo “Received message from channel [{$channel}]: {$message}\n”;
// 特定のメッセージで購読を停止する場合
if ($message === ‘quit’) {
echo “Unsubscribing…\n”;
$redis->unsubscribe([‘chat:news’]); // 購読を停止
}
});
echo “Subscription ended.\n”; // unsubscribe() が呼ばれるまでここには到達しない
// パターンマッチング購読 psubscribe も同様に利用可能
// $redis->psubscribe([‘chat:*’], function($redis, $pattern, $channel, $message) {
// echo “Received message from channel [{$channel}] (matched pattern [{$pattern}]): {$message}\n”;
// });
$redis->close();
?>
“`
Pub/Subは、リアルタイム通知、キャッシュの無効化、アプリケーションコンポーネント間の非同期通信など、様々な用途に利用できます。
4. Luaスクリプト (EVAL)
RedisはLuaスクリプトをサーバーサイドで実行する機能を持ちます。これにより、複数のRedisコマンドを単一のアトミックな操作として実行したり、複雑なロジックをサーバー側で行うことでネットワークオーバーヘッドを削減したりできます。phpredisは eval() および evalsha() メソッドでこの機能をサポートします。
eval() はスクリプトの内容を直接送信して実行します。evalsha() は、事前に script('load', script) でロードしておいたスクリプトのSHA1ハッシュを指定して実行します。ハッシュを使う方が、スクリプトの内容を毎回送信するオーバーヘッドがないため効率的です。
“`php
connect(‘127.0.0.1’, 6379);
// Luaスクリプトの例: キーが存在しない場合のみSETし、成功したらOK、存在したらNGを返す
$script = <<
echo “Result 1 (key not exists): ” . $result1 . “\n”; // Output: OK
echo “Value of my_script_key: ” . $redis->get(‘my_script_key’) . “\n”; // Output: script_value_1
$result2 = $redis->eval($script, [‘my_script_key’], 1, ‘script_value_2’); // キーは既に存在する
echo “Result 2 (key exists): ” . $result2 . “\n”; // Output: NG
echo “Value of my_script_key: ” . $redis->get(‘my_script_key’) . “\n”; // Output: script_value_1 (値は変更されていない)
// EVALSHA の例
echo “— EVALSHA Example —\n”;
// スクリプトをロードしてSHA1ハッシュを取得
$sha = $redis->script(‘load’, $script);
echo “Script SHA1: ” . $sha . “\n”;
// EVALSHA で実行
// evalsha(sha1, keys_array, num_keys)
$result3 = $redis->evalsha($sha, [‘my_script_key_sha’], 1, ‘script_value_sha_1’);
echo “Result 3 (key not exists): ” . $result3 . “\n”; // Output: OK
echo “Value of my_script_key_sha: ” . $redis->get(‘my_script_key_sha’) . “\n”; // Output: script_value_sha_1
$result4 = $redis->evalsha($sha, [‘my_script_key_sha’], 1, ‘script_value_sha_2’); // キーは既に存在する
echo “Result 4 (key exists): ” . $result4 . “\n”; // Output: NG
echo “Value of my_script_key_sha: ” . $redis->get(‘my_script_key_sha’) . “\n”; // Output: script_value_sha_1 (値は変更されていない)
$redis->close();
?>
“`
Luaスクリプトは、複数のコマンドをサーバーサイドでアトミックに実行できるため、複雑な操作や競合状態を避けたい場合に非常に強力です。例えば、「在庫数を減らし、かつその結果がゼロ未満にならないことを保証する」といったロジックなどを、サーバー側で安全に実行できます。
phpredisを利用した実践的な応用例
phpredisの強力な機能は、様々なWebアプリケーションの要件を満たすために活用できます。ここでは、代表的な応用例をいくつか紹介し、phpredisを使った実装のポイントを解説します。
1. キャッシュ
Redisをキャッシュとして利用するのは最も一般的で効果的な方法の一つです。データベースクエリの結果や、レンダリングされたHTMLの一部などをキャッシュすることで、後続のリクエストで同じデータを取得する際に、Redisから直接読み込むことができ、DBアクセスや計算処理をスキップできます。
例:DBクエリ結果のキャッシュ
“`php
pconnect(‘127.0.0.1’, 6379); // 持続的接続
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); // PHPのserialize/unserializeを使う設定
// または Redis::SERIALIZER_IGBINARY (igbinary拡張が必要でより高速・コンパクト)
// または Redis::SERIALIZER_JSON (PHP 7.4+ 推奨)
} catch (RedisException $e) {
die(“Redis connection failed: ” . $e->getMessage());
}
$userId = 123;
$cacheKey = “user_data:” . $userId;
$cacheTTL = 3600; // キャッシュ有効期限 (秒)
// 1. キャッシュからデータを取得
$userData = $redis->get($cacheKey);
if ($userData !== false) { // キャッシュヒット
echo “Data retrieved from cache.\n”;
// キャッシュされたデータは unserialize されている(SERIALIZER_PHP/IGBINARYの場合)
// print_r($userData);
} else { // キャッシュミス
echo “Cache miss. Retrieving data from database…\n”;
// 2. データベースからデータを取得 (模擬)
// 実際にはここでPDOなどを使ってDBクエリを実行
$userData = [
‘id’ => $userId,
‘name’ => ‘Alice’,
‘email’ => ‘[email protected]’,
‘created_at’ => time()
];
// 3. 取得したデータをキャッシュに保存 (有効期限付き)
// SERIALIZER_PHP/IGBINARY を使っている場合は自動的に serialize される
$redis->setex($cacheKey, $cacheTTL, $userData);
echo “Data retrieved from database and cached.\n”;
// print_r($userData);
}
// 取得したデータを利用する
echo “User Name: ” . $userData[‘name’] . “\n”;
// スクリプト終了時にpconnectはプールされるためcloseは不要
?>
“`
キャッシング戦略:
- Cache-aside: アプリケーションコードが最初にキャッシュを確認し、存在しなければDBから読み込んでキャッシュに書き込む。上の例はこの戦略です。最も一般的で柔軟ですが、キャッシュとDBの整合性維持に注意が必要です。
- Write-through: データを書き込む際に、まずキャッシュに書き込み、次にDBにも書き込む。書き込みの整合性が高いですが、書き込み性能はDBに依存します。
- Write-back: データを書き込む際に、まずキャッシュに書き込み、一定時間後や特定のイベントでまとめてDBに書き込む。書き込み性能は高いですが、キャッシュが失われるとデータも失われるリスクがあります。
phpredisでは、set(), get(), setex(), mget(), mset() といったString操作が主に使われます。複雑なオブジェクトをキャッシュする場合は、SERIALIZER_PHP や SERIALIZER_IGBINARY オプションを使うと便利です。JSON形式で保存する場合は SERIALIZER_JSON を使用します。
2. セッション管理
PHPのデフォルトのセッションハンドラはファイルベースですが、Webサーバーが複数台構成になっている場合や、セッションデータの永続性、パフォーマンスなどが問題になることがあります。Redisをセッションストアとして利用することで、これらの課題を解決できます。
PHPでは session_set_save_handler() 関数を使って、セッションの読み込み、書き込み、削除などの処理をカスタムハンドラに委譲できます。phpredisは、このカスタムハンドラとして利用できるクラスを提供しています。
“`php
“`
session.save_handler を redis に設定し、session.save_path にRedisサーバーの接続情報を指定するだけで、PHPのセッションデータが自動的にRedisに保存されるようになります。phpredisはこの設定を解釈し、内部的にセッションハンドラのインターフェースを実装しています。これにより、ファイルシステムI/Oのボトルネックが解消され、分散環境でのセッション共有が容易になります。
3. ジョブキュー
時間のかかるタスク(例:メール送信、画像処理、ファイル変換、バッチ処理)をユーザーリクエストとは独立して実行するために、ジョブキューシステムを構築できます。RedisのListsデータ構造は、シンプルなキューの実装に最適です。プロデューサーがタスクをリストの右端に追加し (RPUSH)、ワーカーがリストの左端からタスクを取り出して実行します (LPOP または BLPOP)。
BLPOP を使うことで、キューにタスクがない場合にワーカープロセスがブロックされ、無駄なポーリングを避けることができます。
“`php
pconnect(‘127.0.0.1’, 6379);
$taskData = [
‘type’ => ‘send_email’,
‘to’ => ‘[email protected]’,
‘subject’ => ‘Welcome!’,
‘body’ => ‘Thanks for signing up!’
];
echo “Adding task to queue…\n”;
// タスクデータをJSON形式でリストに追加
$redis->rpush(‘email_queue’, json_encode($taskData));
echo “Task added.\n”;
// スクリプト終了
?>
pconnect(‘127.0.0.1’, 6379); // ワーカーも持続的接続が有効
echo “Worker started, waiting for tasks…\n”;
// 無限ループでキューからタスクを待ち続ける
while (true) {
// email_queue からタスクを最大60秒間ブロックして待つ
$item = $redis->blpop(‘email_queue’, 60); // タイムアウト時は false
if ($item) {
// $item は [‘queue_name’, ‘task_data_json’] の形式
$queueName = $item[0];
$taskJson = $item[1];
echo “Received task from {$queueName}: {$taskJson}\n”;
// タスクデータをデコード
$taskData = json_decode($taskJson, true);
// ここで実際のタスク処理を行う
if ($taskData && isset($taskData[‘type’])) {
switch ($taskData[‘type’]) {
case ‘send_email’:
echo “Sending email to ” . $taskData[‘to’] . “…\n”;
// メール送信処理のコード…
echo “Email sent.\n”;
break;
// 他のタスクタイプに対応…
default:
echo “Unknown task type: ” . $taskData[‘type’] . “\n”;
}
} else {
echo “Invalid task data.\n”;
}
} else {
echo “Timeout (60s). No tasks received.\n”;
// タイムアウトが続いたらワーカーを終了するなどのロジックを追加しても良い
// break;
}
// 短時間スリープを挟むことでCPU負荷を軽減する場合もあるが、BLPOPを使っている場合は不要
// usleep(10000); // 10ms sleep
}
echo “Worker stopped.\n”;
// ワーカーが終了する場合のみ close()
// $redis->close();
?>
“`
Redisをベースにしたジョブキューライブラリ(例:Resque, Laravel Queue (Redisドライバ))は、より高度な機能(優先度、遅延実行、失敗したタスクの再試行、監視など)を提供しており、これらもphpredisを内部的に利用しています。
4. ランキングシステム
ゲームのハイスコアランキングや、人気記事のランキングなど、スコアに基づいて要素をソートし、一定範囲を取得する必要があるシステムには、RedisのSorted Setsが非常に適しています。
“`php
pconnect(‘127.0.0.1’, 6379);
$leaderboardKey = ‘game:scores’;
// ユーザーのスコアを更新 (存在しない場合は追加)
$redis->zadd($leaderboardKey, 1050, ‘user:Alice’);
$redis->zadd($leaderboardKey, 2100, ‘user:Bob’);
$redis->zadd($leaderboardKey, 1500, ‘user:Charlie’);
$redis->zadd($leaderboardKey, 800, ‘user:David’);
$redis->zadd($leaderboardKey, 2100, ‘user:Eve’); // Bobと同じスコア
// Bobのスコアを更新
$redis->zadd($leaderboardKey, 2200, ‘user:Bob’);
// 上位5名をスコア降順で取得
echo “Top 5 Leaders:\n”;
// zrevrange(key, start, stop, withscores=false)
// 0-based index, 0から4までで上位5名
$topLeaders = $redis->zrevrange($leaderboardKey, 0, 4, true);
print_r($topLeaders);
/* Output (例):
Array
(
[user:Bob] => 2200.0
[user:Eve] => 2100.0
[user:Charlie] => 1500.0
[user:Alice] => 1050.0
[user:David] => 800.0
)
*/
// 特定ユーザーの順位を取得 (スコア降順、1ベースの順位が必要なら+1)
$bobRank = $redis->zrevrank($leaderboardKey, ‘user:Bob’);
echo “Bob’s rank: ” . ($bobRank !== false ? $bobRank + 1 : ‘N/A’) . “\n”; // Output: Bob’s rank: 1
// 特定ユーザーのスコアを取得
$charlieScore = $redis->zscore($leaderboardKey, ‘user:Charlie’);
echo “Charlie’s score: ” . ($charlieScore !== false ? $charlieScore : ‘N/A’) . “\n”; // Output: Charlie’s score: 1500
// スコア範囲でユーザーを取得 (例: スコア1000以上2000未満)
echo “Leaders with scores between 1000 and 2000:\n”;
// zrangebyscore(key, min, max, options)
// options: [‘withscores’ => true, ‘limit’ => [offset, count]]
$filteredLeaders = $redis->zrangebyscore($leaderboardKey, 1000, 2000, [‘withscores’ => true]);
print_r($filteredLeaders);
/* Output (例):
Array
(
[user:Alice] => 1050.0
[user:Charlie] => 1500.0
)
*/
// 特定のユーザーをランキングから削除
// $redis->zrem($leaderboardKey, ‘user:David’);
// Sorted Setの要素数
echo “Total leaders: ” . $redis->zcard($leaderboardKey) . “\n”; // Output: Total leaders: 5
// スクリプト終了
?>
“`
Sorted Setsは、スコアの更新、順位やスコア範囲での要素取得が非常に高速に行えるため、リアルタイムに近いランキングシステムを構築するのに最適です。
パフォーマンス最適化と注意点
phpredisは非常に高速ですが、その性能を最大限に引き出し、安定した運用を行うためには、いくつかの点を考慮する必要があります。
- 持続的接続 (
pconnect) の活用: PHP-FPM環境など、PHPプロセスが複数のリクエストを処理する場合、pconnectを使用することで、リクエストごとにTCP接続を確立・切断するオーバーヘッドを削減できます。これにより、特にRedisへのアクセス頻度が高いアプリケーションのパフォーマンスが大幅に向上します。ただし、持続的接続には、接続が閉じられないことによるリソース消費や、特定のDB選択や認証状態が維持されることによる意図しない挙動のリスクもあります。適切な接続プール管理や、リクエストの最初に使用するDBを選択し直すなどの対策が必要になる場合があります。 - パイプラインの効果的な利用: 複数のRedisコマンドを連続して実行する場合、個別にコマンドを発行するのではなく、パイプライン機能を使ってまとめて送信することで、ネットワークラウンドトリップの遅延を大幅に削減できます。これにより、バッチ処理やリスト操作など、多くのコマンドを実行する場面でパフォーマンスが向上します。
KEYSコマンドの利用は避ける:KEYSコマンドはRedisサーバー上の全てのキーをスキャンし、大量のキーがある場合にサーバーを長時間ブロックする可能性があります。これは運用中のシステムでは致命的な問題につながることがあります。キーの検索が必要な場合は、代わりにSCANコマンドを使用することを検討してください。SCANはイテレーターとして機能し、Redisサーバーをブロックせずに少しずつキーを取得できます。phpredisはscan()メソッドをサポートしています。- 大きなバリューの取り扱い: Redisはメモリ上にデータを保持するため、巨大なデータをバリューとして保存すると、メモリを大量に消費し、パフォーマンスに影響を与える可能性があります。また、大きなデータを読み書きする際のネットワーク転送時間や、PHP側でのシリアライズ/デシリアライズの処理時間も増加します。大きなデータを扱う場合は、データを分割してHashや複数のStringとして保存したり、Redisに格納するのではなく、外部ストレージに保存してRedisにはその参照(パスなど)のみを格納するなどの設計を検討してください。
- シリアライザーの選択: phpredisは
Redis::SERIALIZER_NONE(raw),Redis::SERIALIZER_PHP,Redis::SERIALIZER_IGBINARY,Redis::SERIALIZER_JSONのシリアライザーをサポートしています。PHPのオブジェクトや配列を扱う場合、SERIALIZER_PHPやSERIALIZER_IGBINARYを使うと自動的にシリアライズ/デシリアライズが行われます。IGBINARYはC拡張であり、PHP標準のserializeよりも高速かつ生成されるデータサイズが小さい傾向があります。PHP 7.4以降であればSERIALIZER_JSONも利用可能です。用途や環境に応じて最適なシリアライザーを選択してください。
php
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY); // igbinary拡張が必要 - Redisサーバーのチューニング: phpredisのパフォーマンスは、もちろん連携しているRedisサーバーの性能にも大きく依存します。Redisサーバー自体のメモリ、CPU、ネットワーク、永続化設定、Evictionポリシー(メモリ上限に達した際のデータ削除ポリシー)などを適切にチューニングすることが重要です。特にキャッシュ用途では適切なEvictionポリシーを設定し、メモリ不足にならないように注意が必要です。
- 接続プール: 大規模なアプリケーションでは、Redisへの接続数が多くなり、接続管理が複雑になることがあります。phpredisの
pconnectは単純な持続的接続を提供しますが、より高度な接続プールが必要な場合は、フレームワークの機能を利用したり、カスタム実装を検討したりすることもあります。
phpredisとPredisの比較 (補足)
前述の通り、phpredisとPredisはどちらもPHPでRedisを利用するための主要なライブラリですが、設計思想が異なります。簡単にそれぞれの特徴をまとめ、どちらを選択すべきかについて補足します。
| 特徴 | phpredis (C Extension) | Predis (PHP Library) |
|---|---|---|
| 実装言語 | C言語 (PHP拡張) | PHP |
| インストール | PECL または ソースコンパイル | Composer でインストール (依存関係) |
| パフォーマンス | 一般的に高速 (C実装、低オーバーヘッド) | phpredisより遅い可能性がある (PHP実装) |
| 導入容易性 | サーバーへの拡張インストールが必要 | Composerで簡単に追加できる |
| PHP依存性 | PHPバージョンとの互換性に注意が必要 | PHPバージョン依存性は低い |
| 機能サポート | 最新のRedisコマンドに対応 | 最新のRedisコマンドに対応 |
| 開発/デバッグ | 拡張モジュールのためデバッグが難しい | PHPコードなのでデバッグしやすい |
| 接続方法 | connect, pconnect (持続的接続) |
通常接続 (持続的接続は限定的) |
どちらを選択すべきか?
- パフォーマンス最優先、本番環境: phpredis が推奨されます。特にRedisへのアクセス頻度が高い場合や、パイプラインを多用する場合は、phpredisの速度的な優位性が活かせます。サーバー環境への拡張インストールが可能であれば、まずはphpredisを検討するのが良いでしょう。
- 導入の容易さ、開発/テスト環境、拡張インストールが困難な場合: Predis が手軽です。Composerでプロジェクトに追加するだけで利用できるため、開発環境の構築やCI/CDなどでの利用が容易です。Predisも十分に高速であり、多くのユースケースで問題なく利用できます。
多くのケースではphpredisのパフォーマンス上のメリットが大きいため、本番環境ではphpredisが採用されることが多い傾向にあります。しかし、Predisも活発に開発されており、新しいRedisの機能への対応は迅速です。プロジェクトの要件やチームのスキルセットに合わせて最適な方を選択することが重要です。APIが似ているため、後から切り替えることも比較的容易です。
まとめ
この記事では、PHPとRedisを高速に連携させるための最も効率的な方法として、phpredis拡張モジュールに焦点を当て、その基本的な使い方から高度な機能、そして実践的な応用例までを詳細に解説しました。
Redisはそのインメモリデータ構造サーバーとしての特性から、極めて高速なデータアクセスを提供します。PHPアプリケーションにおいて、キャッシュ、セッション管理、ジョブキュー、ランキングなど、様々な場面でRedisを活用することで、パフォーマンスボトルネックを解消し、よりスケーラブルで応答性の高いアプリケーションを構築することが可能です。
phpredisは、C言語で実装されたネイティブなPHP拡張であるため、PHPスクリプトで実装されたライブラリ(Predisなど)と比較して、ネットワーク通信やデータ処理におけるオーバーヘッドが少なく、Redisとの連携において最高のパフォーマンスを発揮します。
記事を通じて、以下の点を理解いただけたかと思います。
- RedisがPHP開発にもたらすメリットと、パフォーマンス上の優位性。
- phpredisが他のライブラリより高速である理由。
- phpredisのインストール方法と基本的な接続・切断の手順。
- Strings, Hashes, Lists, Sets, Sorted SetsといったRedisの主要なデータ構造と、それらをphpredisで操作する方法。
- トランザクション、パイプライン、Pub/Sub、LuaスクリプトといったRedisの高度な機能をphpredisで利用する方法。
- キャッシュ、セッション管理、ジョブキュー、ランキングといった具体的な応用例の実装ポイント。
- phpredisを効果的に利用するためのパフォーマンス最適化と注意点。
phpredisは、PHPアプリケーションのパフォーマンスを向上させるための強力な武器となります。この記事を参考に、ぜひご自身のプロジェクトにRedisとphpredisを導入し、その高速性と柔軟性を体験してみてください。
さらに深く学びたい場合は、phpredisの公式ドキュメントやRedisの公式ドキュメントを参照することをお勧めします。Redisは常に進化しており、新しいデータ型やコマンドが追加されています。phpredisもそれに追随して開発が進められています。これらの最新情報を常にチェックし、Redisとphpredisの機能を最大限に活用していきましょう。