Spring Bootの@Scheduledと分散環境における注意点

基本的な実装と課題

Spring Bootの@Scheduledアノテーションを使用する場合、デフォルトではシングルスレッドで処理が実行されます。この仕様により、処理時間が長いタスクが存在する場合、以下の2つの問題が発生します:

  • タスクの実行遅延
  • スレッドの競合による処理停止
@Component
@EnableScheduling
public class ScheduledTask {
    @Scheduled(cron = "0/5 * * * * ?")
    public void executeTask() {
        try {
            Thread.sleep(10000);
            System.out.println("現在のスレッドID: " + Thread.currentThread().getId());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

問題の根本原因

SpringのTaskSchedulingAutoConfigurationが生成するスレッドプールのデフォルト設定では、コアスレッド数が1に固定されているため、並列処理が不可能です。この設計は、軽量なタスクを想定していますが、実際の業務では処理時間が長くなるケースが多いため、適切なスレッドプールの構成が必要です。

解決策

1. スレッドプールの設定

application.ymlでスレッドプールのサイズを変更可能です:

spring:
  task:
    scheduling:
      pool:
        size: 10
      thread-name-prefix: task-pool-

2. 非同期処理の導入

カスタムスレッドプールを作成し、非同期処理を実装します:

@Configuration
public class TaskPoolConfig {
    @Bean
    public ExecutorService taskExecutor() {
        return new ThreadPoolTaskExecutor(10, 20, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(200));
    }
}

3. 分散環境におけるロック処理

Redissonを使用して分散ロックを実装することで、複数ノード間でのタスク競合を防止できます:

@Component
@EnableScheduling
public class DistributedTask {
    private static final String TASK_LOCK = "distributed:task-lock";
    private final RedissonClient redisson;

    public DistributedTask(RedissonClient redisson) {
        this.redisson = redisson;
    }

    @Scheduled(cron = "0/5 * * * * ?")
    public void runDistributedTask() {
        RLock lock = redisson.getLock(TASK_LOCK);
        try {
            lock.lock(10, TimeUnit.SECONDS);
            // 実際の処理
        } finally {
            lock.unlock();
        }
    }
}

実装上の留意点

  • スレッドプールのサイズ設定は、システムリソースと処理量を考慮して決定する必要があります
  • 非同期処理では、例外処理とリソース解放の明示的な管理が重要です
  • 分散ロックの生存確認(リース時間)を適切に設定し、デッドロックを防止する必要があります

タグ: Spring Boot Scheduled Tasks 分散システム Redisson スレッドプール

5月28日 15:52 投稿