Spring + Java Scheduling

Jindolph·2024년 9월 2일

스케줄링 처리 구현 가이드맵


기본 구성 설정

  1. 프로젝트 초기화

    • Spring Initializr 또는 Maven/Gradle을 사용하여 Spring Boot 프로젝트 생성.
    • spring-boot-starter, spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-scheduling 등의 의존성 추가.
  2. Scheduling 활성화

    • @EnableScheduling 어노테이션을 추가하여 스케줄링 기능 활성화.

      @SpringBootApplication
      @EnableScheduling
      public class MyApplication {
          public static void main(String[] args) {
              SpringApplication.run(MyApplication.class, args);
          }
      }

스케줄 작업 구현

  1. Scheduled 메서드 정의

    • @Scheduled 어노테이션을 사용해 스케줄링 작업 정의.

    • cron, fixedRate, fixedDelay 등을 사용해 주기 설정.

      @Service
      public class ScheduledTasks {
      
          @Scheduled(cron = "0 0 * * * ?") // 매 정각마다 실행
          public void executeTask() {
              System.out.println("Task executed at " + LocalDateTime.now());
          }
          
          @Scheduled(fixedRate = 5000) // 이전 작업 시작 시점부터 5초마다 실행
          public void executeFixedRateTask() {
              System.out.println("Fixed rate task executed at " + LocalDateTime.now());
          }
      
          @Scheduled(fixedDelay = 5000) // 이전 작업 종료 시점부터 5초 후에 실행
          public void executeFixedDelayTask() {
              System.out.println("Fixed delay task executed at " + LocalDateTime.now());
          }
      }

효율적인 스케줄 관리

  1. Thread Pool 설정

    • 여러 작업이 동시에 실행될 수 있도록 ThreadPoolTaskScheduler 설정.

      @Configuration
      public class SchedulerConfig {
      
          @Bean
          public TaskScheduler taskScheduler() {
              ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
              scheduler.setPoolSize(10);
              scheduler.setThreadNamePrefix("scheduled-task-pool-");
              scheduler.initialize();
              return scheduler;
          }
      }
  2. 작업의 동시 실행 방지

    • 동기화 블록을 사용하여 작업의 중복 실행 방지.

      @Service
      public class ExclusiveTask {
      
          private final Object lock = new Object();
      
          @Scheduled(cron = "0 0/1 * * * ?")
          public void executeExclusiveTask() {
              synchronized (lock) {
                  System.out.println("Exclusive task executed at " + LocalDateTime.now());
                  // 작업 로직 수행
              }
          }
      }

에러 처리 및 모니터링

  1. 에러 핸들링

    • try-catch 블록 또는 AOP를 활용하여 스케줄 작업 중 발생하는 예외를 처리.

      @Service
      public class ErrorHandledTask {
      
          @Scheduled(fixedRate = 60000)
          public void executeTaskWithErrorHandling() {
              try {
                  // 작업 수행
                  System.out.println("Error handled task executed");
              } catch (Exception e) {
                  // 에러 로깅 및 처리
                  System.err.println("Task execution failed: " + e.getMessage());
              }
          }
      }
  2. 작업 모니터링

    • Spring Boot Actuator, Prometheus, Grafana 등을 사용해 스케줄 작업 모니터링.

동적 스케줄 관리

  • 스케줄 설정을 애플리케이션 실행 중에 동적으로 변경하는 방법.
@Configuration
public class DynamicSchedulerConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addCronTask(() -> executeDynamicTask(), "0 0/5 * * * ?");
    }

    public void executeDynamicTask() {
        System.out.println("Dynamic task executed at " + LocalDateTime.now());
    }
}

주의사항 및 베스트 프랙티스

  • 작업 주기와 작업의 경량화를 고려하여 서버 성능에 영향을 주지 않도록 설계.
  • 비동기 작업의 결과 추적과 에러 처리를 위한 로깅 및 모니터링 전략 수립.
  • 작업 실패 시 재시도 로직 도입 또는 실패한 작업을 큐에 저장하여 재실행 가능하도록 설계.

시간대 관련 문제 해결 방법

문제: 분산 서버 환경에서 cron 표현식을 일관되게 처리하고 시간대 문제를 방지하는 방법

  1. UTC 시간 기준 사용

    • 모든 서버가 UTC 시간대에서 동작하도록 설정.

    • cron 표현식을 UTC 시간 기준으로 설정.

      @Scheduled(cron = "0 0 * * * ?", zone = "UTC")
      public void executeTask() {
          // 작업 수행 로직
      }
  2. Centralized Scheduler 사용

    • 중앙 스케줄러 서버에서 모든 스케줄을 관리.
    • 분산 서버는 메시징 큐를 통해 작업을 처리.
  3. 스케줄링 관리 시스템 도입

    • Quartz Scheduler 또는 Spring Cloud Data Flow를 도입하여 중앙에서 시간대 문제를 관리.

Quartz Scheduler 또는 Spring Cloud Data Flow를 이용한 스케줄링 관리

Quartz Scheduler

  1. Quartz Scheduler 주요 개념

    • Job, Trigger, JobStore, Scheduler.
  2. Quartz 설정

    • Quartz 의존성 추가 및 스케줄러 설정.

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-quartz</artifactId>
      </dependency>
      @Configuration
      public class QuartzConfig {
      
          @Bean
          public JobDetail jobDetail() {
              return JobBuilder.newJob(MyJob.class)
                               .withIdentity("myJob")
                               .storeDurably()
                               .build();
          }
      
          @Bean
          public Trigger trigger(JobDetail jobDetail) {
              return TriggerBuilder.newTrigger()
                                   .forJob(jobDetail)
                                   .withIdentity("myJobTrigger")
                                   .withSchedule(CronScheduleBuilder.cronSchedule("0 0/5 * * * ?")
                                                                     .inTimeZone(TimeZone.getTimeZone("UTC")))
                                   .build();
          }
      
          @Bean
          public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
              SchedulerFactoryBean factory = new SchedulerFactoryBean();
              factory.setDataSource(dataSource);
              factory.setJobDetails(jobDetail());
              factory.setTriggers(trigger(jobDetail()));
              factory.setOverwriteExistingJobs(true);
              factory.setApplicationContextSchedulerContextKey("applicationContext");
              return factory;
          }
      }
  3. Quartz 클러스터링 설정

    
    spring.quartz.job-store-type=jdbc
    spring.quartz.jdbc.initialize-schema=always
    spring.quartz.properties.org.quartz.jobStore.isClustered=true
    spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=20000

Spring Cloud Data Flow

  1. Spring Cloud Data Flow 주요 개념

    • Task, Scheduler, Stream.
  2. Spring Cloud Data Flow 설정

    • Data Flow Server 설치 및 스케줄링 설정.

      services:
        dataflow-server:
          image: springcloud/spring-cloud-dataflow-server:2.8.0
          environment:
            - SPRING_CLOUD_SCHEDULER_KUBERNETES_ENABLED=true
            - SPRING_CLOUD_KUBERNETES_SECRETS_PATH=/etc/secrets
          ports:
            - "9393:9393"

작업 실패 처리 및 재처리 로직

요구사항

  1. 각 scheduling job은 cron expression을 이용하여 정해진 시간에 주기적으로 실행.
  2. 스케줄링을 통한 job 실행 실패 시, 기록이 필요.
  3. 기록된 실패를 바탕으로 재처리 필요.

실패 기록 및 재처리 설계

  1. 작업 실패 기록 방법

    • 데이터베이스 테이블을 이용한 실패 기록.

    • Job Execution LogJob Retry Queue 테이블 설계.

      CREATE TABLE job_execution_log (
          id BIGINT AUTO_INCREMENT PRIMARY KEY,
          job_name VARCHAR(255),
          execution_time TIMESTAMP,
          status VARCHAR(50),
          error_message TEXT,
          retry_count INT DEFAULT 0
      );
      CREATE TABLE job_retry_queue (
          id BIGINT PRIMARY KEY,
          job_name VARCHAR(255),
          scheduled_time TIMESTAMP,
          retry_count INT DEFAULT 0,
          last_retry_time TIMESTAMP,
          next_retry_time TIMESTAMP,
          status VARCHAR(50)
      );
  2. 재처리 로직

    • 재시도 전략: 재시도 횟수를 제한하여 무한 재처리를 방지.

    • Job Retry Queue에 기록된 작업을 일정 주기마다 확인하여 재실행.

      @Service
      public class JobRetryService {
      
          @Scheduled(fixedRate = 60000) // 1분마다 실행
          public void retryFailedJobs() {
              List<JobRetryQueue> pendingJobs = jobRetryQueueRepository.findPendingJobs(LocalDateTime.now());
      
              for (JobRetryQueue job : pendingJobs) {
                  try {
                      performTask(job);
                      updateJobRetryStatus(job, "SUCCESS");
                      removeJobFromRetryQueue(job);
                  } catch (Exception e) {
                      handleRetryFailure(job, e.getMessage());
                  }
              }
          }
      
          private void performTask(JobRetryQueue job) throws Exception {
              // 작업 로직 수행
          }
      
          private void updateJobRetryStatus(JobRetryQueue job, String status) {
              job.setStatus(status);
              jobRetryQueueRepository.save(job);
          }
      
          private void removeJobFromRetryQueue(JobRetryQueue job) {
              jobRetryQueueRepository.delete(job);
          }
      
          private void handleRetryFailure(JobRetryQueue job, String errorMessage) {
              if (job.getRetryCount() >= maxRetryLimit) {
                  updateJobRetryStatus(job, "FAILED");
              } else {
                  job.setRetryCount(job.getRetryCount() + 1);
                  job.setLastRetryTime(LocalDateTime.now());
                  job.setNextRetryTime(calculateNextRetryTime(job));
                  jobRetryQueueRepository.save(job);
              }
          }
      
          private LocalDateTime calculateNextRetryTime(JobRetryQueue job) {
              // 재시도 시간 계산 (예: 지수 백오프 방식)
              return LocalDateTime.now().plusMinutes(5 * job.getRetryCount());
          }
      }
  3. 무한 반복 방지

    • 재시도 횟수 제한: 재시도는 최대 N번까지만 시도.
    • 휴지 기간(Delay Period): 재시도 간의 간격을 두어 시스템에 부담을 주지 않음.
    • 중복 실행 방지: 작업을 다시 실행하기 전에 현재 실행 중인 작업이 있는지 확인.

작업 관리 및 모니터링

  1. 관리 인터페이스 제공

    • 실패한 작업을 조회하고 수동으로 재시도를 트리거할 수 있는 인터페이스 제공.
    • Spring Boot Admin, Grafana, Prometheus 등을 사용해 작업 상태 모니터링.
  2. 알림 설정

    • 재시도 횟수 초과 시 관리자에게 알림 전송.
      (Slack 등.)

profile
Hello World!

0개의 댓글