Redlock은 Redis의 창시자인 Salvatore Sanfilippo가 제안한 분산락 알고리즘입니다.
단일 Redis 인스턴스 환경에서도 장애시 안전하게 동작하도록 설계되어있습니다.
그럼 왜 Redlock이 필요한걸까요? 일반적인 Redis 분산락(단일 인스턴스)은 단일 장애점(Single Point of Failure) 문제가 있습니다.

Redlock의 핵심은 과반수(majority) 합의 이며, 분산 시스템의 핵심을 Redis에 적용하게 됩니다.
즉 아래와 같은 상황에서 과반수 합의가 적용되는 것이죠.
✅ 5개 중 3개 이상이 동의하면 → 락 획득 성공
❌ 3개 미만만 동의하면 → 락 획득 실패
왜 과반수인가?
→ 두 클라이언트가 동시에 락을 획득하려 해도,
과반수를 차지할 수 있는 것은 단 하나뿐이기 때문입니다.
단일 Redis를 사용하게되면 그 Redis가 장애를 일으켰을 때 분산락 시슽메 전체가 마비됩니다.
이러한 상황을 미연에 방지하기 위하여 Redlock의 과반수 합의 방식이 사용되게 됩니다.
Redlock은 여러 개의 독립적인 Redis 인스턴스를 사용해 일부가 장애를 일으켜도 락 시스템이 계속 동작하도록 보장합니다.
여기서 독립적이란 복제가 아닌 완전 별개의 Redis를 의미합니다. Master-Slave 구조가 아닌 각각 독립된 Redis가 함께 운영되는 것을 말하죠.

Redlock에는 3가지 핵심 원리가 존재합니다.
1. 다중 인스턴스 : Redis를 단일이 아닌 다중으로 관리합니다.
2. 과반수 규칙 : 다중 Redis 중 과반 이상의 락을 획득해야 분산락으로 인정됩니다.
3. 시간 검증 : 락 획득에 걸린 시간을 고려해 실제 유효 시간을 계산합니다.
이제 각 원리에 대해 자세히 살펴보도록 하겠습니다.
왜 5개인가?
- 홀수 개를 권장 (3, 5, 7개)
- 5개일 때: 2개까지 장애 허용 (5 - 3 = 2)
- 3개일 때: 1개까지 장애 허용 (3 - 2 = 1)
- 7개일 때: 3개까지 장애 허용 (7 - 4 = 3)
일반적으로 5개가 가장 많이 사용됩니다.
→ 적절한 장애 허용 + 적절한 운영 비용
쉬운 비유: 5명의 판사 중 3명 이상이 "유죄"라고 해야 유죄 판결이 나는 것과 같습니다.
과반수 공식: N/2 + 1
- 5개 인스턴스: 5/2 + 1 = 2.5 + 1 = 3개 이상 필요
- 3개 인스턴스: 3/2 + 1 = 1.5 + 1 = 2개 이상 필요
왜 과반수가 안전한가?
→ 두 클라이언트가 동시에 락을 요청해도, 5개 중 과반수(3개)를 동시에 획득할 수 있는 것은 오직 하나뿐입니다.
예시:
- 클라이언트 A: Redis 1, 2, 3에서 성공 (3개) ✅ 락 획득!
- 클라이언트 B: Redis 4, 5에서만 성공 (2개) ❌ 락 획득 실패
락을 획득하는 데 걸린 시간을 고려해야 실제로 락을 사용할 수 있는 시간을 정확히 알 수 있습니다.
예시 시나리오:
1. 락 TTL을 30초로 설정
2. 5개 Redis에 순차적으로 요청
3. 네트워크 지연 등으로 총 5초 소요
→ 실제 사용 가능 시간 = 30초 - 5초 = 25초
만약 시간 검증을 하지 않으면?
→ 클라이언트는 "30초 동안 락을 가지고 있다"고 착각
→ 실제로는 25초 후에 락이 만료되어 다른 클라이언트가 침입 가능!
| 특성 | 단일 Redis | Redlock (5개 인스턴스) |
|---|---|---|
| 장애 허용 | ❌ Redis 1대 장애 = 전체 마비 | ✅ 2대까지 장애 허용 |
| 네트워크 파티션 | ❌ 취약 (연결 끊기면 락 불가) | ✅ 과반수 연결 가능하면 동작 |
| 구현 복잡도 | 간단 (기본 Redisson 사용) | 복잡 (여러 인스턴스 관리) |
| 운영 비용 | 낮음 (1대 운영) | 높음 (5대 독립 운영) |
| 데이터 일관성 | 단일 소스이므로 일관적 | 시간 동기화 필요 |
| 적합한 상황 | 일반적인 서비스 | 금융, 결제 등 고가용성 필수 |
Redlock 알고리즘은 그럼 어떤 과정을 통해 동작할까요? 도식화하여 동작 과정을 단계별로 상세히 살펴보겠습니다.

단계별로 정리를 한 번 해보겠습니다.
1. 현재 시간을 t1에 저장합니다.
2. 모든 Redis에게 SET NX EX를 전송하여 동시에 락 요청을 합니다.
3. 각 Redis로부터의 응답을 확인합니다.
4. 과반수 이상의 Redis로부터 락을 획득했고 TTL도 0이상이면 성공한 것으로 판단합니다.
5. 이후 비즈니스 로직을 수행합니다.
6. 모든 비즈니스가 완료된 이후 모든 Redis에 DEL 명령어를 전송합니다. 이는 락 획득에 실패했을 때도 동일하게 발생합니다.
순차적으로 한 번 위 내용을 분석해보겠습니다.
// 왜 시작 시간을 기록할까요?
// 그 이유는 락 획득에 걸린 시간을 계산하기 위해서입니다.
// 락 획득이 오래 걸리면, 실제 사용 가능 시간이 줄어듭니다.
long startTime = System.currentTimeMillis();
// SET lock "uuid" NX EX 30 명령의 의미:
// - SET lock "uuid": lock이라는 키에 uuid 값을 저장
// - NX: 키가 존재하지 않을 때만 설정 (Not eXists)
// - EX 30: 30초 후 자동 만료
// 왜 UUID를 사용할까요?
// 락을 설정한 클라이언트만 해제할 수 있도록 하기 위해서입니다.
// 다른 클라이언트가 실수로 내 락을 해제하는 것을 방지합니다.
String lockValue = UUID.randomUUID().toString();
중요: 각 Redis에 요청하는 시간제한(timeout)을 짧게 설정해야 합니다.
- 권장: TTL의 1/10 정도 (TTL이 30초면, 타임아웃은 3초)
- 이유: 느린 Redis 하나 때문에 전체 락 획득이 지연되면 안 됩니다.
각 Redis의 응답을 확인합니다:
Redis 1: ✅ OK (락 획득 성공)
Redis 2: ✅ OK (락 획득 성공)
Redis 3: ❌ nil (이미 다른 클라이언트가 락 보유)
Redis 4: ✅ OK (락 획득 성공)
Redis 5: ✅ OK (락 획득 성공)
→ 총 4개 성공
락 획득 성공 조건 2가지를 모두 만족해야 합니다:
조건 1: 과반수 이상에서 락 획득
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
성공 개수(4) >= 과반수(3) → ✅ 만족!
조건 2: 유효 시간이 양수
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
유효 시간 = TTL - 소요시간 - 시계오차보정
= 30초 - 0.5초 - 0.3초
= 29.2초 > 0 → ✅ 만족!
두 조건 모두 만족 → 🎉 락 획득 성공!
만약 실패하면?
시나리오 A: 과반수 미달
- 성공 2개 < 과반수 3개 → ❌ 실패
- 즉시 모든 Redis에 DEL 명령으로 정리
시나리오 B: 시간 초과
- 락 획득에 25초 소요
- 유효 시간 = 30 - 25 - 0.3 = 4.7초
- 비즈니스 로직에 5초 이상 필요하면?
- → ❌ 실패로 처리하고 재시도
// 락을 성공적으로 획득했다면, 계산된 유효 시간 내에
// 비즈니스 로직을 완료해야 합니다.
// validTime = 29.2초
// 비즈니스 로직은 반드시 29.2초 내에 완료되어야 합니다!
try {
// 비즈니스 로직 실행
processPayment(userId, amount);
} finally {
// 락 해제는 항상 실행
unlock();
}
핵심 규칙: 성공이든 실패든, 모든 Redis에 락 해제를 시도합니다.
// 왜 모든 Redis에 해제 요청을 보내나요?
// 1. 성공한 경우:
// - 실제로 락을 설정한 Redis들에서 해제 필요
// 2. 실패한 경우에도 해제하는 이유:
// - 일부 Redis에는 락이 설정되었을 수 있음
// - 해제하지 않으면 TTL까지 불필요하게 락이 유지됨
// - 다음 클라이언트의 락 획득을 방해함
// 3. 해제 시 UUID 확인:
// - 자신이 설정한 락만 해제하도록 UUID 비교
// - Lua 스크립트로 원자적으로 확인 + 삭제
String luaScript = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
""";
위 내용들을 한 번 도식화하여 보기 쉽게 나열해보겠습니다.

Redisson은 Redlock 알고리즘을 RedissonRedLock 클래스로 제공하여 보다 쉽게 사용할 수 있는 방법을 지원하고 있습니다.
이를 통해 직접 알고리즘 구현 없이 간단하게 사용할 수 있습니다.
글머 왜 Redisson을 함께 사용해야 할까요?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Redlock 직접 구현 시 해야 할 것들:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 여러 Redis에 동시 요청 (멀티스레딩)
2. 각 요청에 타임아웃 설정
3. 성공 개수 카운팅
4. 유효 시간 계산
5. 시계 드리프트 보정
6. 실패 시 정리(cleanup) 로직
7. 재시도 로직
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Redisson 사용 시:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
→ 이 모든 것이 RedissonRedLock 내부에 구현되어 있음
→ tryLock() 한 번 호출로 끝!
위와 같은 이유들로 Redisson을 통해 Redlock을 구현하면 쉽게 사용할 수 있게 됩니다.
그럼 어떻게 사용하는지 코드를 보면서 확인해볼까요?
/**
* Redlock 설정 클래스
*
* 핵심 포인트:
* 1. 각 RedissonClient는 서로 다른 Redis 인스턴스에 연결
* 2. 각 Redis는 완전히 독립적 (복제 관계 없음)
* 3. 3개 또는 5개를 권장 (홀수 개)
*/
@Configuration
public class RedlockConfig {
/**
* Redis 인스턴스 1에 대한 클라이언트
* - 물리적으로 다른 서버/컨테이너에 배치 권장
* - 장애 도메인 분리가 중요
*/
@Bean
public RedissonClient redissonClient1() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://redis1:6379")
.setConnectionMinimumIdleSize(5) // 최소 유휴 커넥션
.setConnectionPoolSize(10) // 커넥션 풀 크기
.setTimeout(3000) // 타임아웃 3초
.setRetryAttempts(3); // 재시도 횟수
return Redisson.create(config);
}
@Bean
public RedissonClient redissonClient2() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://redis2:6379")
.setConnectionMinimumIdleSize(5)
.setConnectionPoolSize(10)
.setTimeout(3000)
.setRetryAttempts(3);
return Redisson.create(config);
}
@Bean
public RedissonClient redissonClient3() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://redis3:6379")
.setConnectionMinimumIdleSize(5)
.setConnectionPoolSize(10)
.setTimeout(3000)
.setRetryAttempts(3);
return Redisson.create(config);
}
}
@Service
@RequiredArgsConstructor
@Slf4j
public class RedlockService {
private final RedissonClient redissonClient1;
private final RedissonClient redissonClient2;
private final RedissonClient redissonClient3;
/**
* Redlock을 사용한 안전한 비즈니스 로직 실행
*
* @param userId 대상 사용자 ID
*/
public void processWithRedlock(Long userId) {
// 1. 락 키 정의 (모든 Redis에서 동일한 키 사용)
String lockKey = "user:" + userId + ":lock";
// 2. 각 Redis에 대한 RLock 객체 생성
RLock lock1 = redissonClient1.getLock(lockKey);
RLock lock2 = redissonClient2.getLock(lockKey);
RLock lock3 = redissonClient3.getLock(lockKey);
// 3. RedissonRedLock 생성 (Redlock 알고리즘 적용)
// 내부적으로 과반수 규칙, 유효시간 검증 등을 처리
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
// 4. 락 획득 시도
// - waitTime: 5초 동안 락 획득 대기
// - leaseTime: 30초 후 자동 해제
boolean acquired = redLock.tryLock(5, 30, TimeUnit.SECONDS);
if (acquired) {
log.info("Redlock 획득 성공: userId={}", userId);
// 5. 비즈니스 로직 실행
// 주의: 반드시 leaseTime(30초) 내에 완료해야 함!
doSomething();
} else {
// 과반수 획득 실패 또는 대기 시간 초과
log.warn("Redlock 획득 실패: userId={}", userId);
throw new LockAcquisitionException("락을 획득할 수 없습니다.");
}
} catch (InterruptedException e) {
// 대기 중 인터럽트 발생
Thread.currentThread().interrupt();
throw new RuntimeException("락 획득 중 인터럽트 발생", e);
} finally {
// 6. 락 해제 (성공/실패 무관하게 항상 실행)
// 내부적으로 모든 Redis에 해제 요청을 보냄
if (redLock.isHeldByCurrentThread()) {
redLock.unlock();
log.info("Redlock 해제 완료: userId={}", userId);
}
}
}
}
내부 동작 원리는 아래와 같습니다.

이번 게시글에는 Redlock 알고리즘을 활용한 분산락 구현 방법을 알아보았습니다.
읽어주셔서 감사드립니다 🫡