분산 락을 구현하는 대표적인 3가지 방법은 아래와 같다.
💡GET_LOCK()
같은 함수로 분산 락을 구현할 수 있습니다. 트랜잭션이 자원을 사용할 때 락을 걸고, 락 해제 시 자원이 해제되는 방식입니다. 하지만 MySQL은 데이터베이스 락이므로 인메모리 방식의 Redis에 비해 성능이 느리고 대규모 트래픽 처리에 적합하지 않을 수 있습니다.3가지 방법 중 Redis를 소개할건데 그 이유는 무엇보다도 빠른 성능과 확장성 때문이다. Redis는 인메모리 데이터 저장소로 더 빠른 속도를 제공하며 비교적 간단하게 락을 구현할 수 있기 때문에 대규모 트래픽을 처리하는 시스템에서 더 효율적인 선택입니다.
Redis에서 분산 락을 구현하는 방법은 주로 SETNX (SET if Not eXists) 명령어와 EXPIRE 명령어를 조합하는 방식이다.
1. 락 설정:
SETNX
명령어를 사용하여 특정 키에 값을 설정합니다. 이 명령어는 해당 키가 존재하지 않을 때만 값을 설정하므로, 여러 프로세스 중 하나만 성공적으로 락을 획득할 수 있습니다.2. 락 시간 설정: 락이 영원히 유지되지 않도록
EXPIRE
명령어를 사용해 TTL(Time To Live, 유효 시간)을 설정합니다. 이는 락이 과도하게 길게 유지되어 자원이 블로킹되지 않도록 방지합니다.3. 락 해제: 락을 점유한 프로세스가 작업을 완료한 후
DEL
명령어로 해당 키를 삭제해 락을 해제합니다.
Java 환경에서 Redis와 통신할 수 있는 대표적인 클라이언트는 Lettuce와 Redisson입니다. 두 클라이언트를 통해 분산 락을 구현할 수 있습니다.
Lettuce는 비동기 Redis 클라이언트로, 빠른 성능을 자랑하며 스핀락(spin-lock) 방식으로 락을 구현할 수 있습니다. 스핀락은 락을 얻기 위해 계속해서 반복적으로 시도하는 방식으로, 락을 얻기 전까지 CPU를 지속적으로 사용하게 됩니다.
public boolean acquireLock(String lockKey, long expireTime) {
while (true) { // 스핀락: 락이 설정될 때까지 계속 시도
if (commands.setnx(lockKey, "1")) {
// TTL 설정: 락이 만료되면 자동으로 해제됨
commands.expire(lockKey, expireTime);
return true; // 락 획득 성공
}
try {
// 락을 획득하지 못한 경우 잠시 대기 후 재시도
Thread.sleep(100); // 100ms 대기 후 재시도
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void releaseLock(String lockKey) {
commands.del(lockKey); // 락 해제
}
public static void main(String[] args) {
LettuceSpinLock spinLock = new LettuceSpinLock();
// 락 획득 시도
if (spinLock.acquireLock("lock_key", 10)) {
try {
// 락을 획득한 후 안전하게 작업 수행
System.out.println("작업 수행 중...");
// 작업이 완료되면 락 해제
} finally {
spinLock.releaseLock("lock_key");
System.out.println("락 해제 완료");
}
}
}
Redisson은 Redis를 활용한 고급 자바 클라이언트로 Redlock 알고리즘을 공식적으로 지원합니다. Redisson은 Redis의 Pub/Sub 메커니즘을 사용해 락을 관리하며 더 효율적이고 자원 소모가 적은 방식으로 락을 제어할 수 있습니다. 또한 분산 환경에서의 락 관리에 최적화되어 있으며 Java에서 쉽게 사용할 수 있는 다양한 분산 데이터 구조를 제공합니다.
Pub/Sub 메커니즘은 발행(Publish)과 구독(Subscribe) 방식으로 메시지를 주고받는 통신 방법입니다.
- Publisher는 메시지를 발행하고,
- Subscriber는 특정 주제를 구독하여 발행된 메시지를 받습니다.
이 메커니즘은 비동기적으로 동작하며, 발행자는 구독자 수나 상태를 신경 쓰지 않고 메시지를 보내고, 구독자는 자신이 구독한 주제에 대해 새 메시지가 오면 그때 받습니다.
Redis에서는 이 방식으로 클라이언트 간의 이벤트(예: 락 해제)를 전달할 때 사용됩니다.
public static void main(String[] args) throws InterruptedException {
// Redisson 클라이언트 설정
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// RLock 객체를 통해 락 제어
RLock lock = redisson.getLock("lock_key");
try {
// 락을 최대 10초 동안 대기하고, 30초 동안 점유
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
System.out.println("락 획득 성공. 작업 수행 중...");
// 안전하게 작업 수행
}
} finally {
lock.unlock(); // 락 해제
System.out.println("락 해제 완료");
}
// Redisson 클라이언트 종료
redisson.shutdown();
}
특징 | Lettuce | Redisson |
---|---|---|
락 방식 | 스핀락 방식 (반복적으로 락을 시도) | Pub/Sub 방식 (락 해제 이벤트 구독) |
성능 | 빠르지만 CPU 사용량이 높음 | 자원 효율적 관리, 안정적 |
구현 난이도 | 간단하지만 락 해제 관리가 직접 필요 | Redlock을 자동으로 관리, 더 쉬운 구현 |
사용 목적 | 단순한 Redis 락 구현에 적합 | 복잡한 분산 락 관리와 분산 시스템에서 적합 |
Java 환경에서 Redis 클라이언트를 선택할 때, Lettuce는 간단한 락 구현에 적합하지만 CPU 사용량이 높을 수 있습니다. 반면 Redisson은 더 효율적인 락 관리 및 복잡한 분산 시스템에서 유리합니다.
오 레디스 분산락! 유익하네요