.NET에서 RedLock.NET을 사용한 분산 락 구현
분산 락은 여러 서비스 인스턴스가 있는 시스템에서 임계 구역에 대한 독점 액세스를 제공합니다. Kubernetes의 마이크로서비스에서 팟이나 서로 다른 서비스가 리소스를 놓고 경쟁하는 상황에서 이는 매우 중요합니다. 주요 목표는 데이터베이스에서 핵심 데이터를 업데이트하거나 외부 API를 호출하는 등의 작업을 원자적으로 실행하는 것입니다.
일반적인 시나리오:
- 데이터베이스에서 비즈니스 핵심 값을 변경.
- 규제 시스템에 요청 전송 (예: Chestny Znak의 유사 시스템).
- 동일 마이크로서비스의 팟 간 또는 오케스트레이션/코레오그래피의 서비스 간 동기화.
SemaphoreSlim, AsyncLock 또는 lock 같은 표준 .NET 기본 요소는 인스턴스의 물리적 분리로 인해 적합하지 않습니다. Mutex는 Windows에서 작동하지만 비동기 코드와 Docker 컨테이너에서 문제가 발생합니다.
RedLock.NET 개요
RedLock.NET은 .NET에서 RedLock 알고리즘을 구현하는 라이브러리입니다. 최신 안정 버전은 2.3.2(2022)입니다. 이 패키지는 보안 검사를 통과했으며 알려진 취약점이 없습니다. 저자는 GitHub에서 이슈를 적극적으로 유지보수합니다.
RedLock 알고리즘은 Redis 클러스터를 사용해 TTL이 있는 분산 락을 구현하며, 네트워크 장애 시에도 안전성을 보장합니다. 라이브러리는 락 생성, 연장, 해제 로직을 캡슐화합니다.
대안: 데이터베이스 기반 락
PostgreSQL의 락 테이블
고전적인 접근 방식은 락 전용 테이블을 사용하는 것입니다:
CREATE TABLE locks (
resource VARCHAR(100) PRIMARY KEY,
locked_by VARCHAR(100),
locked_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ
);
충돌 처리를 포함한 원자적 INSERT:
INSERT INTO locks (resource, locked_by, expires_at)
VALUES (@resource, @locked_by, NOW() + (@timespan || ' seconds')::interval)
ON CONFLICT (resource) DO UPDATE
SET locked_by = EXCLUDED.locked_by,
expires_at = EXCLUDED.expires_at
WHERE locks.expires_at < NOW()
RETURNING locked_by;
로직:
- 락이 없으면 TTL과 함께 새 락 생성.
- 락이 만료되었으면 업데이트.
- 활성 상태이면 빈 결과 반환.
요구 사항:
- 작업당 고유
resource. - 고유
locked_by(GUID 또는 인스턴스 ID + 스레드 ID).
격리 수준
SERIALIZABLE은 추가 테이블 없이 원자성을 보장하지만 처리량을 줄입니다. 엔터프라이즈 환경에서 DBA들은 부하 때문에 이를 거의 승인하지 않습니다. READ COMMITTED와 함께 UPSERT 접근 방식이 선호됩니다.
접근 방식 비교
| 접근 방식 | 장점 | 단점 |
|---------------|-----------------------|-------------------------------|
| .NET 기본 요소 | 간단함, 낮은 지연 시간 | 단일 인스턴스만 |
| Mutex | OS 수준 동기화 | Docker/Linux 미지원, 비동기 문제 |
| 데이터베이스 락 | 데이터베이스 통합 | 데이터베이스 오버헤드, 확장성 문제 |
| RedLock.NET | 높은 가용성, TTL | Redis 의존성 |
데이터베이스 락은 빈도가 낮은 간단한 경우에 잘 작동합니다. RedLock.NET은 강력한 보장을 가진 고부하 시나리오에 이상적입니다.
RedLock.NET 실전 구현
설치: dotnet add package RedLock.net. Redis 엔드포인트로 RedLockFactory 생성:
var multiplexer = new ConnectionMultiplexer(...);
var factory = RedLockFactory.Create(...);
using var @lock = await factory.CreateLockAsync(resource, expiry);
if (@lock.IsAcquired) {
// critical section
}
주요 매개변수:
resource: 문자열 식별자 (Redis 키).expiry: TTL을 위한TimeSpan.- 쿼럼: 합의のための Redis 노드 과반수.
장애 처리: 라이브러리는 IExtensibleLock을 통해 장기 실행 작업에 대한 락을 자동 연장합니다.
확장 및 모범 사례
- 고가용성을 위해 Redis Sentinel 또는 Cluster 사용.
- 노드 간 클럭 드리프트 < 10ms.
- 유효 시간 > 2x 클럭 드리프트 + 작업 시간.
- 락 획득 실패율 모니터링.
프로덕션에서는 임계 구역의 외부 호출에 서킷 브레이커를 함께 사용합니다.
핵심 사항
- RedLock.NET은 단일 장애 지점 없이 쿼럼 기반 안전성을 제공합니다.
- 데이터베이스 락은 더 간단하지만 데이터베이스 과부하에 취약하고 확장되지 않습니다.
- 좀비 락에 대비해 항상 TTL 설정.
- 네트워크 파티션 시뮬레이션으로 부하 테스트.
- .NET Core 이후 버전의 컨테이너에서 Mutex 피함.
— Editorial Team
아직 댓글이 없습니다.