Redisを用いた分散ロックの実装と応用

分散ロックの基本概念

分散ロックは、複数プロセス間での可視性と相互排他を実現するメカニズムです。主な目的は、複数インスタンスが共有リソースへのアクセスを調整し、プログラムの直列実行を保証することにあります。

分散ロックの必須条件

  • 可視性: すべてのプロセスが同じ状態を認識可能
  • 相互排他: 同時に一つのプロセスのみがロックを保持
  • 高可用性: システム障害時も機能継続
  • 高性能: ロック操作のオーバーヘッド最小化
  • 安全性: 不正なロック解放の防止

実装方式比較

方式 特徴
MySQL 組み込みロック機構あり、性能面の課題
Redis SETNXコマンドによる効率的な実装
ZooKeeper 分散協調サービス向けの高度なロック管理

Redis分散ロックの実装戦略

基本操作

public interface DistributedLock {
  boolean acquireLock(long timeout);
  void releaseLock();
}

ロック取得の実装例

private final String LOCK_PREFIX = "lock:";

public boolean acquireLock(long timeoutSeconds) {
  String lockId = generateLockIdentifier();
  return Boolean.TRUE.equals(
    redisTemplate.opsForValue().setIfAbsent(
      LOCK_PREFIX + resourceName, 
      lockId, 
      timeoutSeconds, 
      TimeUnit.SECONDS
    )
  );
}

private String generateLockIdentifier() {
  return UUID.randomUUID().toString().replace("-", "") + 
         ":" + 
         Thread.currentThread().getId();
}

ロック解放の課題と解決策

誤解放問題に対処するため、所有者確認プロセスを導入:

public void releaseLock() {
  String currentId = generateLockIdentifier();
  String storedId = redisTemplate.opsForValue().get(LOCK_PREFIX + resourceName);
  
  if (currentId.equals(storedId)) {
    redisTemplate.delete(LOCK_PREFIX + resourceName);
  }
}

原子性問題の解決

Luaスクリプトによる原子操作の実現:

-- unlock.lua
if redis.call('GET', KEYS[1]) == ARGV[1] then
  return redis.call('DEL', KEYS[1])
end
return 0

JavaからのLuaスクリプト実行

private static final RedisScript<Long> UNLOCK_SCRIPT;

static {
  UNLOCK_SCRIPT = new DefaultRedisScript<>();
  ((DefaultRedisScript<Long>) UNLOCK_SCRIPT).setLocation(
    new ClassPathResource("unlock.lua")
  );
  ((DefaultRedisScript<Long>) UNLOCK_SCRIPT).setResultType(Long.class);
}

public void safeReleaseLock() {
  redisTemplate.execute(
    UNLOCK_SCRIPT,
    Collections.singletonList(LOCK_PREFIX + resourceName),
    generateLockIdentifier()
  );
}

ビジネスロジックへの統合例

public TransactionResult processVoucher(Long voucherId) {
  // バリデーションロジック
  DistributedLock lock = new RedisDistributedLock("voucher:" + voucherId, redisTemplate);
  
  if (!lock.acquireLock(1200)) {
    return new TransactionResult("重複処理禁止");
  }
  
  try {
    // トランザクション処理
    return executeTransaction(voucherId);
  } finally {
    lock.safeReleaseLock();
  }
}

完全性を担保する技術要素

  • SETNXによる相互排他制御
  • 有効期限設定によるデッドロック予防
  • 所有者識別による安全な解放
  • Luaスクリプトによる原子操作

タグ: redis 分散ロック lua 原子性処理 排他制御

6月28日 19:39 投稿