안녕하세요. 간만에 또 찾아왔습니다. 한 동안 스케줄러 때문에 삽질 아닌 삽질을 해서 스케줄러와 관련된 부분들을 나누고자 글을 올립니다.
제가 경험한 스케줄러의 형태는 2종류였습니다.
만약 매일 지정된 시간의 10분에 한 번 실행되는 스케줄러라 가정했을 때
- 서버 시작으로부터 10분에 실행되는 스케줄러.
- 지정된 시간의 10분에 실행되는 스케줄러.
였습니다.
저의 경우 매일 지정된 시간에 해야 했기 때문에 당연히 후자의 스케줄러를 추가적으로 만들어야 했습니다.
그 후엔 기존 코드에 작성되어 있는 형태대로 하루에 돌도록 만들었습니다.
그런데 매일 하루에 돌도록 설계되어 있는 스케줄러가 매일 지정된 시간에 돌지 않는 것이 였습니다.
몇 칠을 삽질하며 알게 된 것이 현재 설계되어 있는 스케줄러의 형태는 후자쪽이 아니라 전자에 해당하는 형태였던 것이죠.
그러니까 지정된 시간에 돌지 않았던 이유가 서버가 처음 실행 시간으로 부터 하루 였던 것이죠.
그래서 제가 원하는 형태로 만들기 위해서는 기존과 다른 형태의 스케줄러를 만들어야 했습니다.
현재는 ScheduledExecutorService를 사용하고 있는데 운이 좋게도 이 것을 사용한 방법이 있어 공유해봅니다.
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import static java.time.LocalDateTime.now;
import static java.util.concurrent.TimeUnit.SECONDS;
public class Scheduler {
public static final String SEOUL_ZONE = "Asia/Seoul";
public static final int ONE_DAY = 1;
public static final int ONE_DAY_AS_SECOND = 24 * 60 * 60;
public static final int SINGLE_POOL_SIZE = 1;
private final ScheduledExecutorService scheduler;
public Scheduler() {
this.scheduler = Executors.newScheduledThreadPool(SINGLE_POOL_SIZE);
}
public void execute(Runnable command, int hour, int minute, int second) {
ZonedDateTime now = ZonedDateTime.of(now(), ZoneId.of(SEOUL_ZONE));
ZonedDateTime nextExecutionTime = this.getNextExecutionTime(hour, minute, second, now);
scheduler.scheduleAtFixedRate(command, this.getInitialExecutionTime(now, nextExecutionTime), ONE_DAY_AS_SECOND, SECONDS);
}
private ZonedDateTime getNextExecutionTime(int hour, int minute, int second, ZonedDateTime now) {
ZonedDateTime nextExecutionTime;
nextExecutionTime = now
.withHour(hour)
.withMinute(minute)
.withSecond(second);
if (this.isOverDay(now, nextExecutionTime))
nextExecutionTime = nextExecutionTime.plusDays(ONE_DAY);
return nextExecutionTime;
}
private boolean isOverDay(ZonedDateTime zonedNow, ZonedDateTime nextExecutionTime) {
return zonedNow.compareTo(nextExecutionTime) > 0;
}
private long getInitialExecutionTime(ZonedDateTime now, ZonedDateTime nextExecutionTime) {
Duration duration = Duration.between(now, nextExecutionTime);
return duration.getSeconds();
}
}
먼저 Scheduler()라는 생성자에서 초기화를 해주게 되면 execute()에서 현재 시간과 실행시간을 구하게 됩니다.
그러면 그 다음에는 getInitialExecutionTime()을 통해서 두 시간의 차이(지금부터 실행 시간까지 남은 시간)를 구하게 되고,
scheduler.scheduleAtFixedRate()를 통해 지정하여 스케줄러에 넣은 함수를 일정시간이 지난 후에 지정한 시간 간격으로 수행하게 됩니다.
참고로 scheduler.scheduleAtFixedRate() 메소드는 2가지 형태로 구분됩니다.
- (void) scheduler.scheduleAtFixedRate(TimerTask task, long delay, long period)
- 일정 시간(delay)이 지난 후에 지정한 시간 간격으로(period) 지정한 작업(task)을 처리한다.
- (void) scheduler.scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
- 지정 날짜 or 시간(firstTime)으로 부터 시작하여, 정해진 시간 간격(period)마다 지정한 작업(task)을 반복 수행한다.
상황에 따라 바꿔쓰시면 됩니다.
저의 경우 UTC time 기준으로 실행시켜야 해서
ZonedDateTime now = ZonedDateTime.of(now(), ZoneId.of(SEOUL_ZONE));
이 부분만 아래와 같이 수정하였습니다.
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
실행 방법은
execute(() -> System.out.println("Hello World"), 3, 0, 0);
위와 같이 해주시면 되는데 실행 할 함수, 지정 시간, 지정 분, 지정 초로 넣으셔서 테스트 해주시면 됩니다.
참조
- https://stackoverflow.com/questions/20387881/how-to-run-certain-task-every-day-at-a-particular-time-using-scheduledexecutorse
- https://alwayspr.tistory.com/32
'프로그래밍(Web) > 업무관련' 카테고리의 다른 글
[바미] ON DUPLICATE KEY UPDATE문을 주의해서 사용하자. (0) | 2023.02.03 |
---|---|
[바미] Java Milliseconds to Seconds 에피소드. (0) | 2022.12.16 |
[바미] Java ConcurrentModificationException 에러 (0) | 2022.11.18 |
[바미] TypeORM 타임존 이슈 (0) | 2022.11.03 |
[바미] Ajax data 주의 사항. (0) | 2022.05.18 |