Backend

99 Ngày Spring — Ngày 64: Scheduling với @Scheduled

SSite Admin
29 tháng 09, 2026 3 phút đọc 0 lượt xem
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.

Scheduling với @Scheduled

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

S

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.

Bài viết liên quan

99 Ngày Java — Ngày 64: ExecutorService và thread pool

Tách tác vụ khỏi luồng thực thi, chọn pool, hiểu execute/submit và shutdown có kiểm soát trên Java 21.

29 thg 9, 20262 phút1
99 Ngày Java — Ngày 63: volatile và Atomic

Phân biệt visibility với atomicity, happens-before cơ bản, volatile flag, AtomicInteger và CAS.

28 thg 9, 20263 phút5
99 Ngày Spring — Ngày 63: Application events

Publish event trong Spring, @EventListener đồng bộ, @TransactionalEventListener sau commit và giới hạn độ bền.

28 thg 9, 20262 phút6