Hướng dẫn tạo lịch (Task, Scheduler) với @Schedule trong Spring

Hướng dẫn tạo lịch (Task, Scheduler) với @Schedule trong Spring.

(Xem thêm: Code ví dụ Spring Boot tạo lịch với annotation @Scheduled)

Trong bài này mình sẽ giới thiệu chức năng tạo lịch (Schedule) với Spring.

Đôi khi viết chương trình chúng ta sẽ gặp những tình hướng thực hiện các chức năng chạy theo 1 lịch cố định: ví dụ cứ 5 phút gửi request 1 lần, cứ vào 23h đêm hàng ngày thì thực hiện chạy chức năng backup data, cứ 7h sáng chủ nhật hàng tuần thì tính toán và gửi báo cáo…

Chúng ta có nhiều giải pháp như:

  • Làm bằng tay (cách này không ổn, nhỡ quên thì chết toi, và không thể thực hiện khi thời gian giữa các lần quá ngắn)
  • Tạo Thread chạy ko ngừng nghỉ, và kiểm tra thời gian nếu khớp thì chạy method.
  • Sử dụng TimerTask của Java, các thư viện như Quartz…

Cách thứ 3 là ổn nhất, và trong spring nó tích hợp sẵn cho chúng ta cách đó. Cùng tìm hiểu xem cách cấu hình như nào nhé.

Cấu hình Scheduling Tasks trong Spring

Bật chức năng schedule trong Spring:

Với trường hợp cấu hình bằng annotation hay Spring Boot ta sử dụng annotation @EnableScheduling

@Configuration
@EnableScheduling
public class SpringConfig {
    ...
}
@SpringBootApplication
@EnableScheduling
public class Application {
    ...
}

Với trường hợp cấu hình bằng xml ta sử dụng thẻ sau:

<task:annotation-driven>

Tạo task/job chạy theo lịch với annotation @Schedule

Tạo schedule task với fixedDelay

@Scheduled(fixedDelay = 1000)
public void scheduleFixedDelayTask() throws InterruptedException {
  System.out.println("Task1 - " + new Date());
}
  • Cứ sau khoảng thời gian fixedDelay thì nó lại chạy một lần, ví dụ ở trên thì cứ sau 1000ms (1 giây) thì nó lại chạy method scheduleFixedDelayTask một lần.
  • Với fixedDelay thì chỉ khi nào task trước đó thực hiện xong thì nó mới chạy tiếp task đó lại lần nữa. Ví dụ sau 1 giây mà method scheduleFixedDelayTask chưa chạy xong thì nó sẽ chờ cho tới khi nào xong mới chạy lại lần tiếp theo

Tạo schedule task với fixedRate

@Scheduled(fixedRate = 1000)
public void scheduleFixedRateTask() throws InterruptedException {
  System.out.println("Task2 - " + new Date());
}
  • fixedRate thì giống với fixedDelay, tuy nhiên cứ sau khoảng thời gian fixedRate thì nó chạy tiếp 1 lần nữa mà không cần quan tâm lần chạy trước đã hoàn thành chưa.
  • Ví dụ sau 1s mà method scheduleFixedRateTask chưa thực hiện xong thì nó vẫn chạy lần tiếp theo.

Tạo schedule với cron

Với cron ta sẽ sử dụng cron expression để định nghĩa lịch chạy.

(Xem lại: Cron expression là gì? Hướng dẫn cú pháp cron expression)

Bằng cron ta có thể định nghĩa thời gian chạy theo giờ, phút, giây, ngày tháng năm, trong khoảng thời gian nào… do đó việc đặt lịch linh hoạt hơn so với fixedDelayfixedRate rất nhiều

Ví dụ: từ giây thứ 5 đến giây thứ 10 trong khoảng thời gian 12h-14h các ngày từ thứ 2 đến thứ 6, cứ 1 giây lặp lại một lần

@Scheduled(cron = "5-10/1 * 12-14 * * MON-FRI")
public void scheduleTaskUsingCronExpression() throws InterruptedException {
  System.out.println("Task3 - " + new Date());
}

Ngoài việc sử dụng annotation @Scheduled bạn cũng có thể cấu hình trong file xml như sau:

<beans>
    <bean name="beanA" class="your_class"/>
    <bean name="beanB" class="your_class"/>
    <bean name="beanC" class="your_class"/>
</beans>

<!-- Configure parameters -->
<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="beanA" method="scheduleFixedDelayTask"
      fixed-delay="1000" initial-delay="1000" />
    <task:scheduled ref="beanB" method="scheduleFixedRateTask"
      fixed-rate="1000" />
    <task:scheduled ref="beanC" method="scheduleTaskUsingCronExpression"
      cron="*/5 * * * * MON-FRI" />
</task:scheduled-tasks>

TaskScheduler, thread pool cho schedule task

Mặc định thread pool cho schedule task có giá trị là 1. Tức là hệ thống chỉ tạo ra duy nhất 1 thread để chạy các schedule task. Do đó bạn sẽ gặp trường hợp đến thời gian chỉ định mà task vẫn không được thực hiện vì có 1 task trước đó chưa hoàn thành kể cả với fixedRate,fixedDelay hay cron

Ví dụ method scheduleFixedRateTask ở trên đang để là 1s chạy 1 lần, nhưng method đó mất tới 5s mới hoàn thành thì phải 5s sau method đó mới được thực hiện một lần nữa.

Để giải quyết vấn đề này ta tăng pool size lên để mỗi task chạy với một thread độc lập:

Tăng pool size cho TaskScheduler bằng cách tạo bean:

@Bean
public TaskScheduler taskScheduler() {
  final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
  scheduler.setPoolSize(10);
  return scheduler;
}

Hoặc cấu hình trong file .xml

<task:scheduler id="myScheduler" pool-size="10" />

 

Okay, Done!

Trong bài tiếp theo mình sẽ thực hiện code ví dụ Spring Boot đặt lịch với @Scheduled

References:

https://spring.io/guides/gs/scheduling-tasks/

stackjava.com