Header Ads

Seo Services

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 địnhtự độ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:

Được tạo bởi Blogger.