Perl Hackers Hub

第43回 PerlでのRedis活用法(3)

この記事を読むのに必要な時間:およそ 4.5 分

(1)こちら⁠2)こちらから。

Redisの活用例

Redisの基本的な機能を見たところで,アプリケーションでの活用例を紹介します。CPANにはRedisを使ったモジュールも数多くアップロードされており,それらを利用することでRedisを簡単に活用できます。そのようなモジュールも一緒に見ていきましょう。

セッションストアやキャッシュ

Redisはデータを一時的に保存するのに適しているので,Webアプリケーションのセッションストアやキャッシュとして利用するとよいでしょう。

ただし,Redisが扱えるのは単純な文字列だけですので,複雑な構造を持ったデータを直接保存することはできません。⁠Redisのデータ型」で紹介したようにハッシュ型を扱うことはできますが,保存できる値はやはり文字列だけです。

ネストしたハッシュや配列のような複雑なデータをRedisに保存したい場合は,適切な方法でシリアライズする必要があります。そんなときにはCache::Redisモジュールを使うのが便利です。次のようにハッシュでもRedisに保存できます。

my $redis = Redis::Fast->new;
my $cache = Cache::Redis->new(
    redis => $redis
);
$cache->set('key' => {hoge => 1});
print $cache->get('key')->{hoge};

また,Cache::Redis はPerl で広く使われているCache::Cacheインタフェースと互換性があるという利点もあります。そのためほかのCPANモジュールとの連携も容易です。

リアルタイムランキング

ソート済みセット型は順位の計算を高速に行えるので,リアルタイムランキングを作るのに便利です。

ただし,スコアが同じ要素でも同順位とはならず,辞書順で早い要素が高い順位とみなされることに注意しましょう。本稿執筆時点(2017年1月)では,Redis自体に同順位を扱うコマンドはありません。

ランキングの目的によっては,同スコアの要素は同順位となったほうが望ましい場合も多いでしょう。そのような場合はPerl側で,⁠スコアを取得したあとで,それより高いスコアの要素数を調べる」といった工夫が必要です。Redis::LeaderBoardモジュールを使うと,同順位を考慮したランキングを簡単に扱えます。

my $redis = Redis::Fast->new;
my $lb = Redis::LeaderBoard->new(
    redis => $redis,
    key => 'leader_board:1',
    order => 'asc', # asc/desc
);
$lb->set_score(one => 100);
$lb->set_score(two => 50);
my ($rank, $score) =
    $lb->get_rank_with_score('one');

場合によっては,⁠同スコアのときは早いもの順で順位を決める」のように,スコアが同じだった場合の第2基準を設けたいこともあるでしょう。しかしRedis::LeaderBoardで指定できるスコアは1つだけですので,このようなルールには対応できません。そこで,複数のスコアを指定できるよう筆者が拡張を行ったRedis::LeaderBoardMultiというモジュールもあります。Redis::LeaderBoardと同様のインタフェースで使えるので,利用を検討してみてください。

排他制御

cronを使ってジョブを実行する場合などに,多重実行を制御したいことがあると思います。このような場合はsetlockコマンドがよく使われますが,ホストをまたいでの排他制御は行えません。これをRedisを利用して実現するのが,Redis::Setlockです。

次のようなコマンドで,programを排他的に実行できます。

$ redis-setlock KEY program

ライブラリとして利用することもできます。

my $redis = Redis::Fast->new;
my $g = Redis::Setlock->lock_guard($redis,'key');
if ($g) {
    # ロック獲得成功
}
else {
    # ロック獲得失敗
}

Redisのテスト

実際のサービスでRedisを活用するのであれば,正しく動くかを確認するために自動テストを書くのが望ましいです。テストであってもプロダクション環境に近いほうがよいので,実際にRedisサーバに接続しましょう。

開発用にRedisサーバを準備してもよいのですが,余計なデータがRedisサーバに残っていると,データの整合性が取れなくなってしまいます。それを避けるためにも,テストのたびにクリーンなRedisサーバを起動しましょう。次のようにTest::RedisServerモジュールを利用すると,テスト用のRedisサーバを簡単に起動できます。

use Redis::Fast;
use Test::RedisServer;
use Test::More;

# テスト用のRedisサーバを立ち上げる
my $redis_server = Test::RedisServer->new;

# テスト用のRedisサーバに接続する
my $redis = Redis::Fast->new(
    $redis_server->connect_info
);

# Redisを使ったテスト
is $redis->ping, 'PONG', 'ping pong ok';

done_testing;

Redisのキーを便利に使うユーティリティ

Redisは便利なKVSですが,Redisを利用するすべてのプログラムから自由に読み書きができるため,見方を変えればシステム全体で使える強力なグローバル変数であるとも言えます。安易にキーの名前を決めると,ほかのキーと衝突する可能性もあるので,適切に管理したいところです。本節ではそれを解決するモジュールを紹介します。

Redis::Namespace─⁠─キー名への接頭辞付与を自動化する

Redisではキー名に接頭辞を付けて,ネームスペースを表現することが推奨されています。たとえば,ユーザーに関するデータを保存するキーは「user:」で始める,セッション情報を保存するキーは「session:」で始めるといった具合です。

接頭辞を付ける作業を毎回手動で行っていると,タイプミスをしてしまったり,接頭辞を付け忘れたりしてしまう可能性があります。Redis::Namespaceモジュールはこういったミスを防ぐために,キー名への接頭辞付与を自動的に行います。

Redis::NamespaceはRedis::Fastとインタフェースの互換性があるので,次のコードのように,Redis::Fastを直接使う場合とまったく同じ書き方で利用できます。

my $redis = Redis::Fast->new;
my $ns = Redis::Namespace->new(
    redis => $redis,
    namespace => 'ns',
);

# $redis->set('ns:key', 'value');
$ns->set('key' => 'value');

# $redis->get('ns:key');
print $ns->get('key');

Redis::Namespaceのインタフェース互換性を応用すると,1つのRedisサーバを複数のアプリケーションから共有するという,おもしろい使い方もできます。たとえば,アプリケーションApp1には「app1:」というネームスペースを,アプリケーションApp2には「app2:」というネームスペースを付けるようにすれば,App1とApp2で使われるキー名が被ることはありません。こうすることで,Redisサーバを共有していることをまったく意識せずに,それぞれのアプリケーションを開発できます。もちろん,プロダクション環境ではそれぞれのアプリケーションに個別のRedisサーバを割り当てるべきですが,開発環境など処理速度や信頼性が多少低くても問題ない用途では有用でしょう。

Redis::Key─⁠─Redisのキーのラッパモジュール

「user:ユーザーID」のような形式で,キー名の一部にユーザーIDなどを埋め込みたい場合があります。そのような場合に便利なのがRedis::Keyモジュールです。

Redis::KeyはRedisのキーのラッパモジュールです。プレースホルダを使って「user:{id}」のようにキー名のテンプレートを作ることができます。もちろん,Perl組込みのsprintfを用いたり,正規表現を利用してもよいのですが,Redis::Keyは引数に名前を付けられるのでキー名をわかりやすく管理できます。また,⁠キーの名前」「Redisの接続先」を一括して管理できるため,同じキーへの操作を簡潔に書けるのも利点です。

# キー名のルールを決める
my $redis = Redis->new;
my $user = Redis::Key->new(
    redis => $redis,
    key => 'user:{id}',
    need_bind => 1,
);

# キー名にIDを埋め込む
my $key = $user->bind(id => 123);

# $redis->set('user:123', 'value');
$key->set('value');

# $redis->get('user:123');
$key->get;

著者プロフィール

一野瀬翔吾(いちのせしょうご)

1988年生まれの新潟出身で元高専生。学生時代はロボコンや競技プログラミングの大会に参加。

面白法人KAYACに入社後はソーシャルゲームの開発と運営に携わる。現在はフラー株式会社に移り,Go言語を使ったAPI開発を担当する。

Twitter:@shogo82148
GitHub:https://github.com/shogo82148

コメント

コメントの記入