Shedlock
1. 배경
현재 트리플콤마의 배치 서버는 하나의 서버와 함께 Blue-Green 배포 방식을 사용하고 있습니다.
단일 서버이기 때문에 평소에는 문제가 없지만 배포하는 경우 일시적으로 서버가 두 개 생길 수 있는데 바로 다음과 같은 경우입니다.
이렇게 되는 경우 각 배치 서버에서 실행되고 있는 스케줄러가 중복으로 실행되는 가능성이 존재하게 되는데 제가 이번 스프린트에서 맡은 알림 서비스의 경우 동일한 유저에게 동일한 알림이 중복되어 발송될 수 있습니다.
따라서 이러한 문제점을 해결하기 위해 Shedlock을 도입했습니다.
2. Shedlock
Shedlock이란, 예약된 작업이 여러 인스턴스에서 중복 실행되지 않도록 하는 Lock입니다.
만약, 특정 작업을 한 서버에서 실행 중이면 다른 서버는 실행을 기다리지 않고 무시합니다. 이러한 Lock은 JDBC 데이터베이스나 Redis, Mongo 등 기타 외부 저장소를 사용할 수 있습니다.
3. Usage
-
Dependency
implementation 'net.javacrumbs.shedlock:shedlock-spring:${shedlock_version}'
-
Configuration
@EnableSchedulerLock( defaultLockAtLeastFor = "60s", // lock이 설정되어야 하는 최소 시간 (디폴트) defaultLockAtMostFor = "120s" // lock이 설정되어야 하는 최대 시간 (디폴트) )
-
LockProvider 생성 (Bean 등록)
LockProvider는 여러 구현체가 있습니다.
그 중, 가장 많이 사용하는 두 구현체는 다음과 같이 사용할 수 있습니다.
-
jdbcTemplateLockProvider
-
테이블 생성
# MySQL, MariaDB CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL, locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name)); # Postgres CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP NOT NULL, locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name)); # Oracle CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL, locked_at TIMESTAMP(3) NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name)); # MS SQL CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until datetime2 NOT NULL, locked_at datetime2 NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name)); # DB2 CREATE TABLE shedlock(name VARCHAR(64) NOT NULL PRIMARY KEY, lock_until TIMESTAMP NOT NULL, locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL);
-
Dependency
implementation("net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.8.0") ... (기타 DBMS 의존성)
-
Bean 설정
@Bean public LockProvider lockProvider(DataSource dataSource) { return JdbcTemplateLockProvider( JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(JdbcTemplate(dataSource)) .usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2 .build() ) }
-
-
RedisLockProvider
-
Dependency
implementation 'net.javacrumbs.shedlock:shedlock-provider-redis-spring:${shedlock_version}'
-
Redis 설정
@Bean public LettuceConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(); } @Bean public RedisTemplate<String, String> redisTemplate() { RedisTemplate<String, String> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory()); return template; }
-
Bean 설정
@Bean public LockProvider lockProvider(RedisTemplate<String, String> redisTemplate) { return new RedisLockProvider(redisTemplate); }
-
-
-
Scheduler Lock 설정
@Scheduled(cron = "0 0 * * * ?") @SchedulerLock(name = "scheduledTaskName", lockAtLeastFor = "15s", lockAtMostFor = "30s") public void scheduledTask() { // Your scheduled task logic here }
4. JdbcTemplateLockProvider vs RedisLockProvider
Usage 중간 부분에서 LockProvider의 구현체를 두 가지 언급했습니다.
바로 JdbcTemplateLockProvider
와 RedisLockProvider
인데 둘의 차이는 다음과 같습니다.
- JdbcTemplateLockProvider
- ShedLock을 위한 전용 테이블 필요
- SQL 명령어 (INSERT, UPDATE, SELECT)를 통해 Lock 관리
- 기존 관계형 DB 인프라를 그대로 이용 가능
- DB 성능에 의존, 높은 빈도의 Lock/Unlock의 경우 부하 발생 가능성
- RedisLockProvider
- Redis 설정이 필요
- Redis 명령어 (SETNX, EXPIRE, DEL)를 통해 Lock 관리
- 높은 성능과 빠른 응답 시간, 분산 환경에서 일관성 보장
- ShedLock만을 사용하기 위해 Redis를 구축하는 것은 비추
이처럼 두 Provider에서도 이러한 차이가 있고 다른 Provider도 마찬가지입니다.
따라서 여러 구현체들 중 본인의 프로젝트 환경에 맞게 사용하면 됩니다.
Reference
📕 개인 기록용 블로그입니다.
😊 오타나 잘못된 정보가 있을 경우 댓글이나 메일로 말씀해주시면 바로 수정하겠습니다! 😊
댓글남기기