99 Ngày Spring — Ngày 64: Scheduling với @Scheduled
Một tác vụ dọn dữ liệu chạy mỗi ngày trông đơn giản cho đến khi đổi múi giờ hoặc tăng ứng dụng lên ba instance. Spring @Scheduled giải quyết việc kích hoạt theo thời gian trong một process; thiết kế hệ thống phải xử lý idempotency, lỗi và quyền chạy trong cụm.

1. Bật scheduling và tách job khỏi nghiệp vụ
@EnableScheduling bật xử lý annotation. Method @Scheduled thuộc Spring bean, thường không nhận đối số và trả void; nó chỉ nên gọi một service nghiệp vụ có thể test riêng. Đừng đặt toàn bộ xử lý và truy vấn DB vào method lập lịch.
@Configuration @EnableScheduling
class SchedulingConfig {}
@Component
class CleanupJob {
private final CleanupService service;
CleanupJob(CleanupService service) { this.service = service; }
@Scheduled(cron = "0 10 18 * * *", zone = "Asia/Ho_Chi_Minh")
public void run() { service.removeExpired(); }
}2. fixedDelay, fixedRate và cron
fixedDelay tính từ lúc lần trước kết thúc đến lúc lần sau bắt đầu. fixedRate tính giữa các mốc bắt đầu dự kiến; nếu job kéo dài, việc chạy thực tế còn phụ thuộc scheduler và số worker. Cron của Spring có sáu trường, bắt đầu bằng giây, khác nhiều ví dụ Unix năm trường. Khai báo zone rõ ràng khi giờ địa phương là yêu cầu nghiệp vụ.
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.MINUTES)
void afterPreviousFinish() { service.poll(); }
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES)
void atRegularStarts() { service.sample(); }
@Scheduled(cron = "0 0 9 * * MON-FRI", zone = "Asia/Ho_Chi_Minh")
void onWeekdayMorning() { service.report(); }3. Nhiều instance sẽ chạy nhiều lần
Mỗi instance có scheduler riêng. Triển khai ba bản ứng dụng có thể chạy job ba lần. Với job cần một leader, dùng khóa phân tán (ví dụ ShedLock với cùng kho DB), scheduler tập trung hoặc hàng đợi có consumer phù hợp. ShedLock cần thêm dependency, @EnableSchedulerLock và LockProvider dùng chung; annotation đơn lẻ chưa đủ. Khóa có thời hạn và idempotency vẫn quan trọng khi process chết giữa chừng; đừng coi khóa là giao dịch exactly-once.
// With ShedLock, @EnableSchedulerLock and a shared LockProvider:
@Scheduled(cron = "0 10 18 * * *", zone = "Asia/Ho_Chi_Minh")
@SchedulerLock(name = "cleanupExpired", lockAtMostFor = "PT10M")
public void run() { service.removeExpired(); }4. Lỗi, thời gian và khả năng quan sát
Bắt lỗi ở ranh giới job để ghi metric và báo động; đừng nuốt rồi coi là thành công. Ghi lại thời điểm bắt đầu, thời lượng và số bản ghi đã xử lý. Test cron với clock/múi giờ cố định, đặc biệt quanh DST nếu dùng vùng có đổi giờ. Với tác vụ dài và quan trọng, cân nhắc Quartz hoặc orchestrator thay vì làm scheduler nội bộ ngày càng phức tạp.
Bài tập: cấu hình hai instance cùng chạy job; so sánh số lần gọi trước và sau khóa phân tán.
Bài tập: chuyển cron năm trường sang sáu trường và đặt zone tường minh.
@Scheduled lo thời điểm kích hoạt trong một instance; tính đúng của job vẫn cần idempotency và cơ chế điều phối khi scale. Ngày 65 sẽ xét @Async để chuyển lời gọi sang executor.
Tài liệu đối chiếu
https://docs.spring.io/spring-framework/reference/integration/scheduling.html
https://github.com/lukas-krecan/ShedLock
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


