콘서트 대기열 서비스를 개발하면서 토큰을 만료 스케줄링을 작업하면서 고민했던 내용이다.
같은 시간에 분산 환경에서 애플리케이션 서버들의 스케줄링 중복 실행을 방지하기 위해 shed lock 을 적용했다.
처음 적용해본 개념에 관해 정리하기 위해 작성한 글이다.
GitHub - pbg0205/concert
Contribute to pbg0205/concert development by creating an account on GitHub.
github.com
[1] ShedLock?
ShedLock은 분산 시스템에서 스케줄러 작업(Scheduled Tasks)의 중복 실행 방지를 위해 사용되는 라이브러리이다. 여러 인스턴스에서 실행되는 애플리케이션이 동일한 스케줄러 작업을 동시에 실행하지 않도록 락(Lock)을 사용하여 제어한다.
- 분산 환경 지원 : ShedLock 은 여러 서버 인스턴스에서 하나의 스케줄러 작업에 관한 실행을 보장한다.
- 다양한 스토리지 제공 : 데이터베이스(MySQL, PostgreSQL 등) Redis, DynamoDB 등의 다양한 저장소를 락 저장소로 사용하고 LockProvider 를 구현하여 맞춤형 저장소를 설정할 수 있어 높은 확장성을 제공한다.
- 자동 만료 락 제공 : 지정된 시간이 지나면 락이 자동을 해제되므로 작업 중단 문제를 방지할 수 있다.
[2] ShedLock 를 적용해보자.
ShedLock GitHub 에서 제공하는 튜토리얼에 관해 설명이 잘되어 있다. 이를 기반으로 ShedLock 을 적용해보자.
GitHub - lukas-krecan/ShedLock: Distributed lock for your scheduled tasks
Distributed lock for your scheduled tasks. Contribute to lukas-krecan/ShedLock development by creating an account on GitHub.
github.com
(1) build.gradle
- ShedLock 은 Spring Boot 환경에서 쉬운 설정과 사용이 가능하다. Spring 에서 ShedLock 을 사용하고자 하는 경우, shedlock-spring 을 호환하는 스프링 버전에 맞게 적용하도록하자.
- 현재 프로젝트에서는 RDB 에 Lock data 를 관리할 예정이므로 shedlock-provider-jdbc-template 를 사용했다.
dependencies {
// Shed lock
implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:6.2.0'
implementation 'net.javacrumbs.shedlock:shedlock-spring:6.2.0'
}
(2) shedlock schema 추가하기
shedlock data 를 관리하기 위해서 별도의 테이블 선언이 필요하다. 테이블에 관한 정보는 shedlock GitHub 에서 제공하고 있으니 참고하도록 하자. (RDB 에서는 Named Lock 으로 동작하는 것 같다.)
# 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));
(3) LockProvider 추가하기
- ShedLock 을 활성화하기 위해서는 @EnableSchedulerLock 을 선언해야 한다.
- @EnableSchedulerLock 은 defaultLockAtMostFor 옵션을 설정해야 한다.
- 락의 최대 점유 시간을 설정하는 옵션이니 서비스 환경에 맞게 설정하면 될 것 같다.
- Lock data 를 관리하기 위해서는 LockProvicer 를 설정해야 한다.
- 현재는 RDB 를 기반으로 사용할 예정이므로 JdbcTemplateLockProvicer 를 적용한다.
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
public class ScheduleConfig {
@Bean
public LockProvider lockProvider(final DataSource dataSource) {
return new JdbcTemplateLockProvider(dataSource, "shedlock"); // datasource + table 이름
}
}
(3) ShedLock 적용하기
- 실행할 스케줄링에 @SchedulerLock 선언한다.
- name : 락의 key name 을 설정하는 옵션이다.
- lockAtLeastFor : 락을 점유할 최소 시간을 설정하는 옵션이다.
- lockAtMostFor : 락을 점유할 최대 시간을 설정하는 옵션이다. 최대 시간을 초과하는 경우 자동으로 락을 해제한다.
(시간에 관련된 설정은 duration+unit / duration in ms / ISO-8601 을 지원하므로 자세한 내용을 해당 링크를 참고하자.)
@Scheduler
@RequiredArgsConstructor
public class TokenScheduler {
private final TokenSchedulerUseCase tokenSchedulerUseCase;
@SchedulerLog
@Scheduled(fixedRateString = "${queue.processing.rate}", timeUnit = TimeUnit.SECONDS)
@SchedulerLock(name = "tokenActiveScheduler", lockAtLeastFor = "PT5S", lockAtMostFor = "PT8S")
public Integer tokenActiveScheduler() {
return tokenSchedulerUseCase.updateToProcessing(LocalDateTime.now());
}
}
(4) ShedLock 적용 확인하기
local 환경에서 정상적으로 스케줄링의 단일 실행을 테스트하기 위해 program argument(--server.port) 을 사용해 두 개의
application 을 실행해 단일 스케줄링을 확인해볼 수 있었다.
java -jar build/libs/concert-0.0.1-SNAPSHOT.jar --server.port=8080
java -jar build/libs/concert-0.0.1-SNAPSHOT.jar --server.port=8081
참고
lock data 는 key name 당 하나의 로우를 관리한다.
만약 해당 키 이름의 shedlock 이 해제되고 잠금이 활성화되면 해당 로우(row)를 업데이트하는 방식으로 동작한다.
'spring > summary' 카테고리의 다른 글
Spring REST Docs + Swagger UI 를 통한 API 문서화 (1) | 2024.12.07 |
---|---|
비관적 락 vs 낙관적 락(In JPA) (0) | 2024.11.28 |
MessageSourceAutoConfiguration 코드 검토하기 (0) | 2024.11.28 |
[kurly tech blog] Redisson, Spring AOP 기반 분산락 적용 방법 summary (0) | 2024.11.26 |
Spring Batch 기반 회원 삭제 배치 삽질 기록 (0) | 2024.09.03 |