Redisを用いた分散ロックの実装手法

分散ロックの要件

分散システムで共有データを操作する際、複数プロセス間の調整が必要です。高可用性のある分散ロックは以下の特性を満たす必要があります:

  • 同一メソッドが同時に単一ノードの単一スレッドで実行される
  • ロック取得・解放の高パフォーマンス
  • デッドロック防止のための有効期限機構
  • 再入可能なロック取得
  • 非ブロッキングな取得失敗時の動作

Redisスタンドアロン実装

Redisの単一スレッド特性を活用した実装手法:

基本実装と進化

基本方式: SETNXコマンドでロック取得後、DELで解放。問題点:クライアント障害時のデッドロック。

有効期限追加: SETNX後にEXPIREを実行。問題点:非原子操作による有効期限設定失敗の可能性。

原子操作化: 拡張SETコマンドでロック取得と有効期限設定を原子化:

SET resource_key unique_value PX 5000 NX

問題点:他クライアントのロック誤解放。

最終解決策

Luaスクリプトによる原子操作で値検証と削除を実現:

String lua = 
  "if redis.call('get', KEYS[1]) == ARGV[1] then " +
  "  return redis.call('del', KEYS[1]) " +
  "else " +
  "  return 0 " +
  "end";
Object res = redis.eval(lua, List.of(lockName), List.of(clientToken));

この実装でスタンドアロン環境の要件を満たします。

Redisクラスタ環境実装

マスター障害時のフェイルオーバーで発生するロック競合を解決するため、RedLockアルゴリズムを採用:

RedLockアルゴリズム

  1. 現在のUnixタイムスタンプ取得
  2. N個のRedisノードで順次ロック取得(単一ノード方式と同様)
  3. 以下の条件を満たす場合にロック成功:
    • ロック有効時間 = (現在時刻 - 要求時刻) < ロックTTL
    • 過半数ノードで取得成功
  4. 実効有効時間 = ロックTTL - 取得処理時間
  5. 全ノードに解放コマンドを送信

Redisson実装

RedissonはRedLockを実装した公式推奨ライブラリ:

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.16.0</version>
</dependency>

設定例:

// クラスタ設定
Config cfg = new Config();
cfg.useClusterServers()
  .addNodeAddress("redis://node1", "redis://node2");

RedissonClient client = Redisson.create(cfg);
RLock lock = client.getLock("shared_lock");

try {
  if(lock.tryLock(5, 10, TimeUnit.SECONDS)) {
    // クリティカルセクション処理
  }
} finally {
  lock.unlock();
}

内部実装ではハッシュ構造で再入可能ロックを管理:

// ロック取得Luaスクリプト
String lockScript = 
  "if redis.call('exists', KEYS[1]) == 0 then " +
  "  redis.call('hset', KEYS[1], ARGV[2], 1); " +
  "  redis.call('pexpire', KEYS[1], ARGV[1]); " +
  "  return nil; " +
  "end; " +
  "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " +
  "  redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
  "  redis.call('pexpire', KEYS[1], ARGV[1]); " +
  "  return nil; " +
  "end; " +
  "return redis.call('pttl', KEYS[1]);";

タグ: redis 分散ロック Redisson RedLock 分散システム

7月26日 20:26 投稿