접속자 대기열 시스템 #7 - 대기열 스케줄러 개발
이번 단계에서는 대기열에서 사용자를 자동으로 허용하는 스케줄러를 구현합니다. 이 스케줄러는 주기적으로 대기열을 확인하고, 일정 수의 사용자를 허용 목록으로 이동시키는 역할을 합니다. 이를 통해 대기열 시스템을 자동화하고 효율성을 높일 수 있습니다.
1. UserQueueService 클래스
UserQueueService
클래스는 대기열과 관련된 비즈니스 로직을 담당하며, 스케줄러를 통해 대기열 관리 작업을 수행합니다.
스케줄러 메서드: scheduleAllowUser
@Slf4j
@Service
@RequiredArgsConstructor
public class UserQueueService {
private final ReactiveRedisTemplate<String, String> reactiveRedisTemplate;
private final String USER_QUEUE_WAIT_KEY = "users:queue:%s:wait";
private final String USER_QUEUE_WAIT_KEY_FOR_SCAN = "users:queue:*:wait";
private final String USER_QUEUE_PROCEED_KEY = "users:queue:%s:proceed";
@Value("${scheduler.enabled}")
private Boolean scheduling = false;
@Scheduled(initialDelay = 5000, fixedDelay = 10000)
public void scheduleAllowUser() {
if (!scheduling) {
log.info("passed scheduling...");
return;
}
log.info("called scheduling...");
var maxAllowUserCount = 3L;
reactiveRedisTemplate.scan(ScanOptions.scanOptions()
.match(USER_QUEUE_WAIT_KEY_FOR_SCAN)
.count(100)
.build())
.map(key -> key.split(":")[2])
.flatMap(queue -> allowUser(queue, maxAllowUserCount)
.map(allowed -> Tuples.of(queue, allowed)))
.doOnNext(tuple -> log.info("Tried %d and allowed %d member of %s queue"
.formatted(maxAllowUserCount, tuple.getT2(), tuple.getT1())))
.subscribe();
}
}
1. 기능 설명:
@Scheduled
어노테이션을 사용하여 스케줄러를 설정합니다.initialDelay = 5000
은 서버 시작 후 5초 후에 첫 실행을 의미하며,fixedDelay = 10000
은 이후 10초마다 실행됩니다.scheduleAllowUser
메서드는 Redis를 통해 대기열을 검색하고, 각 대기열에서 지정된 수의 사용자를 허용합니다.
2. 스케줄러 활성화 조건:
scheduling
변수가true
일 때만 스케줄러가 동작합니다. 이는application.yml
파일에서 설정됩니다.
3. 대기열 스캔과 사용자 허용:
reactiveRedisTemplate.scan(...)
을 통해 Redis에서users:queue:*:wait
형식의 모든 키를 검색하여 각 대기열을 찾습니다..flatMap(queue -> allowUser(...))
: 각 대기열에서maxAllowUserCount
수만큼 사용자를 허용합니다..doOnNext
: 허용된 사용자 수와 대기열 이름을 로그에 출력합니다.
스케줄러의 코드 분석
reactiveRedisTemplate.scan(ScanOptions.scanOptions()
.match(USER_QUEUE_WAIT_KEY_FOR_SCAN)
.count(100)
.build())
.map(key -> key.split(":")[2])
.flatMap(queue -> allowUser(queue, maxAllowUserCount)
.map(allowed -> Tuples.of(queue, allowed)))
.doOnNext(tuple -> log.info("Tried %d and allowed %d member of %s queue"
.formatted(maxAllowUserCount, tuple.getT2(), tuple.getT1())))
.subscribe();
- 대기열 스캔:
reactiveRedisTemplate.scan(...)
은 Redis의SCAN
명령어를 사용해 비동기적으로 키를 검색합니다. 여기서ScanOptions
는 검색 조건을 지정합니다..match(USER_QUEUE_WAIT_KEY_FOR_SCAN)
은"users:queue:*:wait"
형식의 키를 찾도록 설정하고,.count(100)
은 한 번에 최대 100개의 키를 반환합니다.
- 키 가공:
.map(key -> key.split(":")[2])
는 검색된 키를 대기열 이름으로 가공합니다."users:queue:<queue_name>:wait"
형식의 키에서<queue_name>
을 추출하는 작업입니다.
- 사용자 허용:
.flatMap(queue -> allowUser(queue, maxAllowUserCount))
를 통해 각 대기열에서 최대maxAllowUserCount
수만큼 사용자를 허용합니다..map(allowed -> Tuples.of(queue, allowed))
는 허용된 사용자 수와 대기열 이름을 튜플 형식으로 변환합니다.
- 로그 출력:
.doOnNext(tuple -> log.info(...))
를 통해 각 대기열에서 허용된 사용자 수와 대기열 이름을 로그로 남깁니다.
- 스트림 실행:
.subscribe()
메서드가 호출되면 스트림이 실행되고, 모든 작업이 비동기적으로 처리됩니다.
2. WaitingApiApplication 클래스
WaitingApiApplication
클래스는 애플리케이션의 진입점이며, 스케줄러 기능을 활성화합니다.
package com.example.flow;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@SpringBootApplication
public class WaitingApiApplication {
public static void main(String[] args) {
SpringApplication.run(WaitingApiApplication.class, args);
}
}
1. @EnableScheduling 어노테이션:
- 이 어노테이션은 애플리케이션에서 스케줄링 기능을 활성화합니다. 이를 통해
@Scheduled
로 설정한 메서드가 주기적으로 실행될 수 있습니다.
3. application.yml 설정
스케줄러의 활성화 여부를 설정하는 application.yml
파일입니다.
scheduler:
enabled: true
---
spring:
config:
activate:
on-profile: test
scheduler:
enabled: false
1. 프로덕션 환경 설정:
- 기본적으로
scheduler.enabled
값이true
로 설정되어, 스케줄러가 활성화됩니다.
2. 테스트 환경 설정:
spring.config.activate.on-profile: test
조건이 활성화되면,scheduler.enabled
값이false
로 설정되어 스케줄러가 비활성화됩니다.
QnA
Q1. @Scheduled
어노테이션을 사용할 때 주의해야 할 점은 무엇인가요?
@Scheduled
메서드는 기본적으로 하나의 스레드에서 실행되므로, 오랜 시간 동안 실행되는 작업이 있을 경우 동시성 문제가 발생할 수 있습니다. 필요하다면,@Async
어노테이션을 함께 사용하여 비동기적으로 실행할 수 있습니다.
Q2. 스케줄러의 실행 간격을 조정하려면 어떻게 해야 하나요?
@Scheduled
어노테이션의initialDelay
와fixedDelay
값을 변경하여 조정할 수 있습니다.fixedDelay
는 이전 작업이 완료된 후부터 다음 작업이 실행되기까지의 간격을 나타냅니다.
Q3. 스케줄러가 주기적으로 허용할 사용자 수를 동적으로 조정할 수 있나요?
- 네, 스케줄러 메서드에서
maxAllowUserCount
값을 외부 설정 파일이나 데이터베이스에서 동적으로 읽어와 설정할 수 있습니다.
Q4. 여러 인스턴스가 실행 중일 때 스케줄러의 동작은 어떻게 관리하나요?
- 여러 인스턴스에서 동일한 스케줄러가 실행되면, 중복 작업이 발생할 수 있습니다. 이를 방지하기 위해 분산 락을 사용하여 한 번에 하나의 인스턴스만 스케줄러 작업을 수행하도록 설정할 수 있습니다.
Q5. 스케줄러가 허용할 사용자의 수를 초과한 경우에는 어떻게 되나요?
allowUser
메서드는 실제 대기열에 있는 사용자 수보다 허용할 수 있는 사용자 수가 적다면, 허용 가능한 최대 사용자 수만큼만 처리됩니다. 예를 들어, 대기열에 2명만 있고 최대 허용 수가 5명일 때, 실제로는 2명만 허용됩니다.
Q6. 스케줄러를 특정 시간대에만 활성화할 수 있나요?
@Scheduled
어노테이션 대신Cron
표현식을 사용하여 특정 시간대에 스케줄러를 실행할 수 있습니다. 예를 들어,"0 0 9 * * *"
표현식은 매일 오전 9시에 스케줄러를 실행합니다.
Q7. 스케줄러가 비활성화된 경우에도 수동으로 실행할 수 있나요?
- 스케줄러가 비활성화된 경우, 수동으로
scheduleAllowUser
메서드를 호출하여 작업을 수행할 수 있습니다. 이를 위해 별도의 API 엔드포인트를 - 만들거나, 애플리케이션 내부에서 직접 호출할 수 있습니다.
Q8. scan
메서드로 검색된 대기열이 많을 경우 성능 문제는 없나요?
scan
메서드는 Redis의SCAN
명령어를 사용하며, 이는 대량의 키를 효율적으로 검색하는 방식입니다. 한 번에 검색할 키의 수를 조절하여 성능을 최적화할 수 있습니다.
Q9. 테스트 환경에서 스케줄러를 비활성화하는 이유는 무엇인가요?
- 테스트 환경에서는 특정 시나리오를 재현하거나, 데이터를 조작하여 테스트하기 위해 스케줄러를 비활성화하는 것이 일반적입니다. 이를 통해 테스트 중 발생할 수 있는 불필요한 작업을 방지할 수 있습니다.
Q10. 스케줄러가 실행 중일 때 Redis 서버가 중단되면 어떻게 되나요?
- Redis 서버가 중단되면,
ReactiveRedisTemplate
을 통해 수행되는 작업이 실패하게 됩니다. 이를 대비해 에러를 적절히 처리하고, 스케줄러가 중단되지 않도록 하는 것이 중요합니다.
위와 같이, 스케줄러를 사용한 대기열 관리 시스템을 개발하면서 고려해야 할 사항들과 동작 방식을 이해할 수 있습니다. 이를 통해 대기열 시스템의 자동화와 효율성을 높일 수 있습니다.
'프레임워크 > 자바 스프링' 카테고리의 다른 글
접속자 대기열 시스템 #9- 테스트 (0) | 2024.10.10 |
---|---|
접속자 대기열 시스템 #8- 대기열 이탈 (0) | 2024.10.10 |
접속자 대기열 시스템 #6- 접속 대기 웹페이지 개발 (1) | 2024.10.10 |
접속자 대기열 시스템 #5- Redis를 이용한 대기열 관리 및 웹페이지 진입 API 구현 (1) | 2024.10.10 |
접속자 대기열 시스템 #3- 셋업 (2) | 2024.10.10 |