NoSQLじゃないRedisをもっと知ってほしい


みんな大好きなRedisいいですよね。私もお世話になっています。

RedisといえばNoSQLのキーバリューストアとして使ったり、キャッシュ置き場として使うのが一般的かと思います。とはいってもそんなパフォーマンスを求められる使い方が必要になるには相当数のアクセスがあるサイトを開発しているエンジニアだけが必要なもの?

いや、Redisはもっとできる子なんです。今回の記事では小規模な開発でも、キーバリューストアやキャッシュ用途以外の使い道があるよということをまとめてみました。

1. メッセージキューとして使う

複数のプロセス間でメッセージキューを使って値の受け渡しをするようなことがあるかと思います。

RDBを用いた場合は

  1. 値の挿入
  2. 値の取得
  3. 読み取った値の削除

という処理になるかと思いますが、Redisの型の一つリスト型を用いることで簡単にメッセージキューとを実装することができます。

まずメッセージの投入側ではリストの先頭からメッセージを追加していきます。

LPUSH MyQueue "MessageA"  => MyQueueは["MessageA"]
LPUSH MyQueue "MessageB"  => MyQueueは["MessageB","MessageA"]

SpringDataRedisを使用すると上記Redisコマンドはこのようになります。

@Autowired
private RedisTemplate redisTemplate;

public void lpush() {
  redisTemplate.opsForList().leftPush("MyQueue", "MessageA");
  redisTemplate.opsForList().leftPush("MyQueue", "MessageB");
}

SpringBootでRedisを使用するための設定については Java: Spring Boot で Redis を使う! - akihyrox が参考になります。

メッセージの取り出し側ではリストの末尾からメッセージを追加していきます。

RPOP MyQueue ==> MessageAが返される
RPOP MyQueue ==> MessageBが返される

SpringDataRedisを使用すると上記Redisコマンドはこのようになります。

@Autowired
 private RedisTemplate redisTemplate;

public void lpush() {
  String v;
  v = redisTemplate.opsForList().rightPop("MyQueue");
  v = redisTemplate.opsForList().rightPop("MyQueue");
}

このメッセージのやり取りはRedisを介することで、異なるプロセスの間で可能なので実行したいメソッドやその時のパラメーターなどをメッセージとしてやり取りすることでメッセージキューとして使用することができます。

実際の運用ではRPOPLPUSHを用いて、実行中のメッセージを別のリスト(実行中リスト)に移してから実行終了時に実行中リストから削除するという実装が良いと思います。

参考
redisドキュメント日本語訳 データ型-リスト型

2. ロックとして使う

Webのリクエストによってデータを登録するような処理の場合、重複登録しないように何らかのロックをかける必要があります。RDB上にロックテーブルを作成してロックフラグをチェックさせるようなこともありますが、ロックの解除に失敗した場合ロックテーブル上のフラグが残ったままになってしまう危険性があります。

RedisではSETNXというコマンドを使用することで簡単にロック機構を実装することが可能です。
Redisで文字列を格納するときに使用するコマンドにSETというコマンドを用いますが、SETNX

  • キーが存在していない場合には値をセットしてその返り値として 1 を返します。
  • キーが既にあった場合は、値をセットしないでその返り値として 0 を返します。
SETNX MyLockKey "Locked" ==> 返り値は 1
SETNX MyLockKey "Locked2" ==> 返り値は 0 MyLockKeyの値は "Locked"のまま

この返り値を見ることでロックされているかどうかを取得することができます。

また、Redisでは登録したキーに対して有効期限(Expire)を指定することができます。
有効期限を過ぎたキーはRedisがキーを削除してくれますので万が一キーの削除に失敗した場合でもオペレーションなしでロックを外すことができます。

上記のSETNXを用いた例では、下記のようにEXPIRE指定します。この例ではキーMyLockKeyに対して60秒の有効期限を設けています。

SETNX MyLockKey "Locked"
EXPIRE MyLockKey 60

SpringDataRedisを使用するとロック処理はこのようになります。

@Autowired
 private RedisTemplate redisTemplate;

public void process() {


  while (true) {
    // ロックがかかっていないで、値が登録できた場合はisLock = true
    // すでに値が登録されていた場合はisLock = false
    Boolean isLock = redisTemplate.opsForValue().setIfAbsent("MyLockKey", "Locked");
    // 60秒のExpireを設定
    redisTemplate.expire("MyLockKey", 60, TimeUnit.Seconds);  
    if (isLock) {
      // ロック取得した後の処理
      // ここで上のメッセージキューを読みだして処理を行うとかできます。
      .....
      // 正常終了した後のロック解除
      redisTemplate.delete("MyLockKey");
    } else {
      // ロック中
    }
  }
}

参考
redisドキュメント日本語訳 データ型-文字列型
Redisのsetnxを使ってマルチサーバ環境での Web API ロック機構を実現する - Developers.IO

3. メッセージングのPub/Subに使う

HTTPのリクエスト以外のトリガーによって何らかの処理を実行したいようなケースではPublish/Subscribeを用いたメッセージングの仕組みを用いると任意のタイミングで処理の実行が可能になります。

Redisにはこのための仕組みが組み込まれていますので簡単にメッセージングの実装が可能です。
メッセージの送信には PUBLISHを用いて 購読にはSUBSCRIBEを用います。

SUBSCRIBEコマンドを実行すると引数となるチャンネルに対してPUBLISHが行われるまで待ち受けをします。

メッセージ購読側
SUBSCRIBE MyMessage

この状態で別のプロセスからこのチャンネルに対してPULISHを行います。

メッセージ送信側
PUBLISH MyMessage "Send Message"

すると、SUBSCRIBE側でメッセージを受信し次のメッセージが来るまで再び待ち続けます。

SpringDataRedisを用いたPub/Sub実装は少し長くなるので、こちらのサイトを参照してみてください。
PubSub Messaging with Spring Data Redis -baeldung

参考
redisドキュメント日本語訳 パブリッシュ/サブスクライブ

余談ですが

http://www.baeldung.com には、SpringBootを用いた実装例が簡潔に多く掲載されており非常に参考になります。

spring関連のインデクス
http://www.baeldung.com/category/spring/