特集:高パフォーマンスなKey-Valueストア「Redis」活用術
C#のRedisライブラリ「BookSleeve」の利用法
高パフォーマンスなKey-Valueストア「Redis」とは? 「BookSleeve」というライブラリによりC#でRedisを使う方法を紹介し、コードで示しながらその特徴を解説する。
Redis(=REmote DIctionary Serverの略で、読みは「レディス」が近いようだ)は、イタリアの開発者Salvatore Sanfilippo(a.k.a. antirez)氏が中心になって開発しているオープンソースのKey-Valueストア(以降、KVS)で、VMware社がスポンサーしている。オンメモリで動作するデータ・ストアであり、非常に高いパフォーマンスを誇っている。KVSということでMemcachedに近い性質を持つが、違いとして、
- 単純なKey-Valueのデータ型のほかに、リストやハッシュなど多彩なデータ構造を持つ
- 定期的にデータを書き出すことで永続化できる
- Pub/Sub(=発行/購読)モデルをサポートしている
といった点が挙げられる。TwitterやGitHubやStack Overflowなどが利用しており、国内でも多数の利用事例が報告されている、現在メジャーなKVSの1つといえるだろう。筆者の所属する株式会社グラニでも、クエリ・キャッシュ、セッション、リストを使った履歴保持など、さまざまな使い方をしている。
RedisはANSI Cで書かれており、Linux、各種BSD(OpenBSD/NetBSD/FreeBSD)、Mac OS XなどのPOSIXシステムの環境で動作する。Windowsは公式にサポートされていないが、Microsoft Open TechnologiesによりWindowsに移植されており、実行できる。プロダクションで使うにはLinuxが勧められているが、ちょっと試す分には、このWindows版のバイナリは役に立つだろう。実行ファイルのバイナリはソース・コードの「msvs/bin/release」下にある。
Redisは、ASP.NET SignalRのスケールアウト・プロバイダーとしても標準で用意されており(内部でRedisのPub/Sub機能を利用している)(詳細後述)、.NET開発者にとって、最も注目すべきKVSだろう。
BookSleeve
Redisのライブラリは多くの開発言語向けに存在している。C#の代表的なライブラリには、「ServiceStack.Redis」と「BookSleeve」がある。どちらも公式にお勧め印を受けているが、この記事ではBookSleeveを取り上げる。
BookSleeveは、「全てが非同期で、パイプラインで動作する」という特異な造りが特徴だ。Stack Overflowに勤務するMarc Gravell氏(protobuf-netの作者としても有名)が作成しているライブラリだ。Stack OverflowはRedisをキャッシュ層に使っており、そこでBookSleeveが用いられているようだ。
全てがパイプラインということだが、まず、Redisは機能としてパイプライニングをサポートしている。例えばクライアントがRedisのINCRコマンドを4回呼ぶと、
- Client: INCR X
- Server: 1
- Client: INCR X
- Server: 2
- Client: INCR X
- Server: 3
- Client: INCR X
- Server: 4
といったように、サーバはその都度、結果を返す。これは4回、Client-Server間でレスポンスを待つ応答時間が発生することを意味する。これを、Redisのサポートするパイプラインを使えば、
- Client: INCR X
- Client: INCR X
- Client: INCR X
- Client: INCR X
- Server: 1
- Server: 2
- Server: 3
- Server: 4
といった形になる。これはClient-Server間での通信が一度だけなので、往復遅延時間(Round Trip Time)のコストがかからず高速化される。単純なGETやSETだけではなく、あらゆる操作がパイプラインで行えるのはRedisの大きな魅力だ。そして、これをBookSleeveで書くと以下のようになる(C#)。
var connection = new RedisConnection("127.0.0.1");
await connection.Open();
var x1 = connection.Strings.Increment(db: 0, key: "X");
var x2 = connection.Strings.Increment(db: 0, key: "X");
var x3 = connection.Strings.Increment(db: 0, key: "X");
var x4 = connection.Strings.Increment(db: 0, key: "X");
await Task.WhenAll(x1, x2, x3, x4); // 全ての完了を待つ
// 結果表示
Console.WriteLine("{0}, {1}, {2}, {3}", x1.Result, x2.Result, x3.Result, x4.Result);
|
BookSleeveは全ての操作が非同期なため、戻り値はTask型(System.Threading.Tasks名前空間)となる。よって、C# 5.0から搭載されたasync/await非同期メソッドを用いることで、その性質を生かしつつ、(上記のコード例のように)簡易に書くことが可能だ。このようにTask型が用いられることで、パイプラインによる実行結果の取得や待機が自然に記述できるのも魅力的な点の1つだ。
ところで、ここで面白いのは、明示的にパイプラインでの実行を指示していないところだ。BookSleeveは、内部でブロッキング・キューを用いてコマンドを蓄積している。同時に、常にキューの中身が空でないかを監視し、コマンドを送信するワーカーが働いている。ワーカーが動くときにキューに複数のコマンドが蓄積されていれば、それらを全て一括で、パイプラインで送信する。その挙動により、同タイミングに発行されたコマンドは、自動的にパイプライン化されているのである。また、ネットワーク・アクセスは非同期I/Oによるソケット通信で行われているので、パイプラインの送信の合間の待機時間は最小限に抑えられている。
まさに、全てが非同期で、パイプラインで動作しているわけだ。
コネクション管理
以上の特徴を生かすために、RedisConnectionオブジェクト(=Redisサーバーへの接続)は単独で、開きっぱなしで、共有されていなければならない。接続をマネジメントするものとして、例えば以下のようなコードを用意しよう。
public static class RedisConnectionManager
{
static RedisConnection connection;
static object connectionLock = new object();
public static RedisConnection GetConnection()
{
if ((connection == null)
|| ((connection.State != RedisConnectionBase.ConnectionState.Open) && (connection.State != RedisConnectionBase.ConnectionState.Opening)))
{
lock (connectionLock)
{
if ((connection == null)
|| ((connection.State != RedisConnectionBase.ConnectionState.Open) && (connection.State != RedisConnectionBase.ConnectionState.Opening)))
{
connection = new RedisConnection("127.0.0.1"); // 接続設定は変更する
connection.Wait(connection.Open());
}
}
}
return connection;
}
}
|
上記のRedisConnectionManagerクラスは、次のようにして使う。
|
var redis = RedisConnectionManager.GetConnection();
await redis.Strings.Set(db: 0, key: "hoge",value: "てすと");
var value = await redis.Strings.Get(db: 0, key: "hoge");
|
これにより、RedisConnectionは全リクエストから共有される。ASP.NETで用いれば、全ての独立しているリクエスト、関連性のない全く異なるコマンドが、パイプラインでまとまって送信されていくことにより、大幅に往復遅延時間を軽減できる。
SignalR
ASP.NET SignalRはWebSocketを含めた、リアルタイムかつ双方向の通信を行う.NETのライブラリだ。そのSignalRで配信をスケールアウトさせる手段の1つとして、Redisによる実装が公式に公開されている。Redisによる通信には、前述のBookSleeveが用いられている。SignalRで利用する際には、特にRedisやBookSleeveについて意識することはなく、以下の1行をアプリケーション起動時に記述するだけでいい。
|
// ホスト、ポート、パスワード、識別キーは仮のもの
GlobalHost.DependencyResolver.UseRedis("localhost", 6379, "", "signalr.key");
|
このスケールアウト・プロバイダー「Microsoft.AspNet.SignalR.Redis」はNuGet経由でインストール可能だ。
補助ライブラリの作成
BookSleeveが提供するAPIは、非常に原始的で、ほとんどがbyte[]型の値を返す。このため多くの場合は、シリアライザを通してオブジェクトに変換するようにした方が便利だ。実アプリケーションで利用する場合は、BookSleeveの上に、ある程度、自前でライブラリを構築した方がよいだろう。
実際に筆者はこの目的で「CloudStructures」というライブラリを作成し公開している。このライブラリは、接続管理、クライアントサイドでの分散、.configファイルからの設定読み込み、シリアライズ対応したAPIなどを提供する。次のコードは、CloudStructuresライブラリに含まれるRedisListクラスを使うことで、オブジェクトを挿入したり取得したりできることを示している(AddLastメソッドの内部でシリアライズ、Rangeメソッドの内部でデシリアライズしている)。
|
var settings = new RedisSettings("127.0.0.1");
var list = new RedisList<person>(settings, "Person-Key-0");
await list.AddLast(new Person { Name = "やまだ", Age = 20 });
await list.AddLast(new Person { Name = "こいけ", Age = 35 });
var persons = await list.Range(0, 2);
|
■
Redisは現在も精力的にアップデートが続いており、去年の10月にはLuaスクリプティングのサポートされた2.6がリリースされたほか、細かく機能向上されたバージョンがリリースされている。BookSleeveもそれに合わせるように、活発にバージョン・アップされているので、動向に要注目だ。
更新履歴
- 2013/05/08
- 「RedisConnectionオブジェクトを管理するためのRedisConnectionManagerクラスのコード」のコードにおける条件判定が誤っていたので、修正しました。お詫びして訂正させていただきます。