Trên thực tế trong quá trình phát triển ứng dụng chúng ta gặp những trường hợp phải chạy một tác vụ nào đó vào một khoảng thời gian xác định và tự động.
Thứ nhất là yếu tố "xác định", có thể xác định về thời gian và xác định về tác vụ. Ví dụ cứ vào 00:00 hàng ngày thì thực hiện backup dữ liệu hoặc là cuối tháng thì thực hiện gửi mail báo cáo...
Thứ hai là yếu tố "tự động", phải được thực hiện tự động bởi ứng dụng (trong một số trường hợp có thể có sự can thiệp của con người nhưng mang tính hỗ trợ).
Trong bài viết này mình sẽ cùng anh em tìm hiểu về cách lập lịch như vậy cho ứng dụng mà cụ thể là với ứng dụng Spring Boot thông qua anntation @Scheduled
. Let's go!
1/ Khởi tạo project.
Có rất nhiều cách khởi tạo một ứng dụng Spring Boot và mình cũng đã đề cập đến trong các bài viết trước. Đơn giản nhất anh em có thể truy cập vào trang https://start.spring.io/ điền đầy đủ thông tin rồi bấm GENERATE là được.
Sau khi tải về anh em giải nén ra rồi sử dụng một công cụ lập trình (IDE) nào đó để mở lên. Ở đây mình sử dụng IntelliJ Idea. Cấu trúc ban đầu của project như sau:
2/ Bật chế độ lập lịch.
Đối với Spring Boot để bật chức năng lập lịch thì anh em có thể thêm annotation @EnableScheduling
vào class chứa hàm main()
như sau:
package com.laptrinhb2a.springbootschedule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class SpringBootScheduleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootScheduleApplication.class, args);
}
}
Note: Ngoài ra anh em có thể thêm vào bất kỳ class cấu hình nào (class có gắn annotation @Configuration
) cũng được.
2/ Lập lịch, tạo task với annotation @Scheduled.
Sau khi bật chế độ lập lịch thì chúng ta có thể sử dụng annotation @Scheduled
để lập lịch cũng như tạo task tự động cho ứng dụng.
Bây giờ mình sẽ tạo một lớp gọi là ScheduledTasks.java
, lớp này sẽ chứa 4 phương thức tương ứng với 4 tham số mà mình có thể truyền vào annotation @Scheduled
package com.laptrinhb2a.schedule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.format.DateTimeFormatter;
@Component
public class ScheduledTasks {
private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
@Scheduled(fixedRate = 2000)
public void fixedRateScheduledTask() {}
@Scheduled(fixedDelay = 2000)
public void fixedDelayScheduledTask() {}
@Scheduled(fixedRate = 2000, initialDelay = 2000)
public void initialDelayScheduledTask() {}
@Scheduled(cron = "0 * * * * ?")
public void cronExpressionScheduledTask() {}
}
Note: những hàm này sẽ theo format: có kiểu trả về void
và không có tham số (nếu khác format này hàm sẽ không chạy!)
Okay, bây giờ mình sẽ cùng anh em triển khai từng hàm với từng loại tham số.
2.1 - Lập lịch với tham số Fixed Rate.
Theo định nghĩa thì fixedRate
là khoảng thời gian từ lúc bắt đầu chạy task/job lần trước đến lúc bắt đầu chạy task/job lần sau (tiếp theo).
Nói cách khác thì cứ sau khoảng thời gian fixedRate
sẽ chạy lại task/job đó một lần (không quan tâm lần trước đã chạy xong chưa)
Anh em có thể xem ảnh bên dưới để dễ hình dung hơn.
Ví dụ mình sẽ logs ra thời điểm chạy một task/job như sau:
@Scheduled(fixedRate = 2000)
public void fixedRateScheduledTask() {
logger.info("Thời điểm bắt đầu - {}", dateTimeFormatter.format(LocalDateTime.now()));
}
Khi đó output sẽ là:
...
2021-08-28 09:56:47.427 INFO 10128 --- [ main] c.l.schedule.ScheduleApplication : Started ScheduleApplication in 1.678 seconds (JVM running for 2.736)
2021-08-28 09:56:49.431 INFO 10128 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 09:56:49
2021-08-28 09:56:51.432 INFO 10128 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 09:56:51
2021-08-28 09:56:53.428 INFO 10128 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 09:56:53
2021-08-28 09:56:55.424 INFO 10128 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 09:56:55
2021-08-28 09:56:57.421 INFO 10128 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 09:56:57
2.2 - Lập lịch với tham số Fixed Delay.
Theo định nghĩa thì fixedDelay
là khoảng thời gian từ lúc kết thúc chạy task/job trước đến lúc bắt đầu chạy task/job sau (tiếp theo).
Nói cách khác thì sau khi chạy xong task/job trước nó sẽ dừng lại (delay) khoảng thời gian là fixedDelay
rồi mới bắt đầu chạy task/job tiếp theo.
Anh em có thể xem ảnh bên dưới để dễ hình dung hơn.
Ví dụ ở đây mình sẽ logs ra thời điểm chạy một task/job (giả sử có thời gian chạy là 5s) sau đó cho task/job này dừng 2s với tham số fixedDelay = 2
rồi mới chạy lần tiếp theo:
@Scheduled(fixedDelay = 2000)
public void fixedDelayScheduledTask() {
logger.info("Thời điểm bắt đầu - {}", dateTimeFormatter.format(LocalDateTime.now()));
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException ex) {
throw new IllegalStateException(ex);
}
}
Khi đó output sẽ là:
...
2021-08-28 10:32:38.456 INFO 16252 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-08-28 10:32:38.470 INFO 16252 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:32:38
2021-08-28 10:32:38.479 INFO 16252 --- [ main] c.l.schedule.ScheduleApplication : Started ScheduleApplication in 2.593 seconds (JVM running for 5.726)
2021-08-28 10:32:45.487 INFO 16252 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:32:45
2021-08-28 10:32:52.513 INFO 16252 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:32:52
2021-08-28 10:32:59.524 INFO 16252 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:32:59
2021-08-28 10:33:06.545 INFO 16252 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:33:06
2021-08-28 10:33:13.555 INFO 16252 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:33:13
2021-08-28 10:33:20.580 INFO 16252 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:33:20
2021-08-28 10:33:27.591 INFO 16252 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:33:27
2.3 - Lập lịch với tham số Initial Delay.
Theo định nghĩa thì Initial Delay là khoảng thời gian bị hoãn (delay) trước khi chạy một/job lần đầu tiên (từ các lần chạy sau thì chạy bình thường).
Anh em có thể xem ảnh bên dưới để dễ hình dung hơn.
Mình sẽ lấy ví dụ việc initialDelay
kết hợp với fixedRate
. Trong đó mình sẽ delay 2s từ lúc bắt đầu chạy ứng dụng tới lúc chạy task/job lần đầu tiên.
Sau đó task/job sẽ chạy bình thường với fixedRate = 2s
như ví dụ phần 2.1
@Scheduled(fixedRate = 2000, initialDelay = 2000)
public void initialDelayScheduledTask() {
logger.info("Thời điểm bắt đầu - {}", dateTimeFormatter.format(LocalDateTime.now()));
}
Khi đó output sẽ như bên dưới.
...
2021-08-28 10:44:43.474 INFO 15144 --- [ main] c.l.schedule.ScheduleApplication : Started ScheduleApplication in 1.421 seconds (JVM running for 2.276)
Thời điểm chạy ứng dụng - 10:44:46
2021-08-28 10:44:48.474 INFO 15144 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:44:48
2021-08-28 10:44:50.477 INFO 15144 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:44:50
2021-08-28 10:44:52.464 INFO 15144 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:44:52
2021-08-28 10:44:54.465 INFO 15144 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:44:54
2021-08-28 10:44:56.479 INFO 15144 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:44:56
2021-08-28 10:44:58.474 INFO 15144 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 10:44:58
2.4 - Lập lịch với biểu thức cron (cron expression).
Với 3 tham số fixedRate
, fixedDelay
hay initialDelay
thì mình tin là nhiều bạn sẽ thắc mắc không thể nào giải quyết hết các bài toán lập lịch mà mình đưa ra ở phần mở đầu.
Chính vì vậy chúng ta cần một công cụ mạnh mẽ và linh hoạt hơn đó là biểu thức cron hay tiếng anh là cron expression.
Về cách tạo cũng như quy tắc tạo ra một biểu thức cron thì cũng đã có một bài viết riêng. Nếu anh em nào chưa biết thì có thể tham khảo tại đây.
Trong bài viết này để đơn giản mình sẽ cho chạy mỗi phút 1 lần với biểu thức cron là: 0 * * * * ?
@Scheduled(cron = "0 * * * * ?")
public void cronExpressionScheduledTask() {
logger.info("Thời điểm bắt đầu - {}", dateTimeFormatter.format(LocalDateTime.now()));
}
Khi đó output sẽ như sau:
...
2021-08-28 22:07:18.053 INFO 4916 --- [ main] c.l.schedule.ScheduleApplication : Started ScheduleApplication in 3.122 seconds (JVM running for 7.533)
Thời điểm chạy ứng dụng - 22:07:18
2021-08-28 22:08:00.010 INFO 4916 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:08:00
2021-08-28 22:09:00.013 INFO 4916 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:09:00
2021-08-28 22:10:00.009 INFO 4916 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:10:00
2021-08-28 22:11:00.004 INFO 4916 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:11:00
2021-08-28 22:12:00.009 INFO 4916 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:12:00
2021-08-28 22:13:00.014 INFO 4916 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:13:00
3/ Tùy chỉnh số luồng (thread) khi chạy.
Mặc định thì @Scheduled sẽ chỉ chạy trên một luồng, anh em có thể thêm đoạn code sau vào từng hàm bên trên để logs ra luồng mà task/job của anh em đang chạy. Ví dụ mình thêm vào hàm chạy với tham số fixedRate như sau:
@Scheduled(fixedRate = 2000)
public void scheduleTaskWithFixedRate() {
logger.info("Thời điểm bắt đầu - {}", dateTimeFormatter.format(LocalDateTime.now()));
logger.info("Luồng hiện tại : {}", Thread.currentThread().getName());
}
Và output sẽ là:
...
2021-08-28 22:21:35.058 INFO 15056 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-08-28 22:21:35.078 INFO 15056 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:21:35
2021-08-28 22:21:35.078 INFO 15056 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Luồng hiện tại : scheduling-1
2021-08-28 22:21:35.083 INFO 15056 --- [ main] c.l.schedule.ScheduleApplication : Started ScheduleApplication in 1.696 seconds (JVM running for 2.66)
2021-08-28 22:21:37.089 INFO 15056 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:21:37
2021-08-28 22:21:37.089 INFO 15056 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Luồng hiện tại : scheduling-1
2021-08-28 22:21:39.079 INFO 15056 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:21:39
2021-08-28 22:21:39.079 INFO 15056 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Luồng hiện tại : scheduling-1
2021-08-28 22:21:41.079 INFO 15056 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:21:41
2021-08-28 22:21:41.079 INFO 15056 --- [ scheduling-1] com.laptrinhb2a.schedule.ScheduledTasks : Luồng hiện tại : scheduling-1
Nếu chỉ chạy trên một luồng duy nhất thì nhiều khi có những task/job chưa chạy xong đã phải chạy lại rồi. Và để khắc phục điều đó chúng ta có thể tùy chỉnh số luồng chạy bằng việc viết thêm một file cấu hình ScheduleConfig như sau:
package com.laptrinhb2a.schedule;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
private final int POOL_SIZE = 5;
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
threadPoolTaskScheduler.setThreadNamePrefix("thread - ");
threadPoolTaskScheduler.initialize();
scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
}
}
Bây giờ mình sẽ chạy lại rồi xem kết quả có gì khác nhé.
...
2021-08-28 22:33:00.628 INFO 13524 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-08-28 22:33:00.648 INFO 13524 --- [ thread - 1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:33:00
2021-08-28 22:33:00.653 INFO 13524 --- [ thread - 1] com.laptrinhb2a.schedule.ScheduledTasks : Luồng hiện tại : thread - 1
2021-08-28 22:33:00.658 INFO 13524 --- [ main] c.l.schedule.ScheduleApplication : Started ScheduleApplication in 1.512 seconds (JVM running for 3.009)
2021-08-28 22:33:02.658 INFO 13524 --- [ thread - 1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:33:02
2021-08-28 22:33:02.658 INFO 13524 --- [ thread - 1] com.laptrinhb2a.schedule.ScheduledTasks : Luồng hiện tại : thread - 1
2021-08-28 22:33:04.669 INFO 13524 --- [ thread - 2] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:33:04
2021-08-28 22:33:04.669 INFO 13524 --- [ thread - 2] com.laptrinhb2a.schedule.ScheduledTasks : Luồng hiện tại : thread - 2
2021-08-28 22:33:06.664 INFO 13524 --- [ thread - 1] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:33:06
2021-08-28 22:33:06.664 INFO 13524 --- [ thread - 1] com.laptrinhb2a.schedule.ScheduledTasks : Luồng hiện tại : thread - 1
2021-08-28 22:33:08.661 INFO 13524 --- [ thread - 3] com.laptrinhb2a.schedule.ScheduledTasks : Thời điểm bắt đầu - 22:33:08
2021-08-28 22:33:08.661 INFO 13524 --- [ thread - 3] com.laptrinhb2a.schedule.ScheduledTasks : Luồng hiện tại : thread - 3
4/ Kết.!
Hi vọng qua bài viết này sẽ giúp anh em hình dung được cách sử dụng @Scheduled để lập lịch cũng như tạo task sẽ như thế nào.
Hẹn gặp lại anh em trong các bài viết tiếp theo nha!
Bài viết có tham khảo nội dung tại:
https://www.callicoder.com/spring-boot-task-scheduling-with-scheduled-annotation/
https://stackjava.com/spring/huong-dan-tao-lich-task-scheduler-voi-schedule-trong-spring.html
Thanks all 💗💗💗
Không có nhận xét nào: