Thiết Kế Hệ Thống: Cùng Xây Dựng Hệ Thống Lên Lịch Tác Vụ Phân Tán Triệu Đô!
Lê Lân
0
Thiết Kế Hệ Thống Phân Tán Cho Bộ Lập Lịch Công Việc (Distributed Job Scheduler)
Mở Đầu
Trong thế giới phần mềm hiện đại, việc thực thi các tác vụ nền (background tasks) là vô cùng phổ biến và thiết yếu. Đặc biệt, khi yêu cầu thực thi các công việc này ở mức độ quy mô lớn, tính khả dụng cao và đúng thời điểm càng được đặt lên hàng đầu. Bài viết này sẽ cung cấp một cái nhìn toàn diện về thiết kế hệ thống cho một Bộ Lập Lịch Công Việc phân tán (Distributed Job Scheduler), tập trung vào các yêu cầu chức năng, phi chức năng, thiết kế tổng quan, lựa chọn lưu trữ, xử lý tác vụ lặp lại (idempotency), và tích hợp với các công nghệ như AWS SQS và Kafka.
Chúng ta sẽ cùng nhau phân tích chi tiết từng yếu tố, từ cách người dùng gửi tác vụ cho đến quá trình thực thi và giám sát, nhằm đảm bảo hệ thống vận hành hiệu quả, đáng tin cậy, và mở rộng tốt.
Yêu Cầu Chức Năng
Đăng Ký và Quản Lý Công Việc
Người dùng có thể đăng ký các công việc (jobs/tasks) theo lịch trình cụ thể, ví dụ mỗi 5 phút hoặc theo biểu thức cron.
Hỗ trợ kích hoạt công việc thủ công (manual trigger).
Các công việc hiện tại được viết dưới dạng script Python, có thể mở rộng cho các ngôn ngữ khác trong tương lai.
Cam kết thực thi công việc ít nhất một lần (at-least-once execution).
Hỗ trợ công việc định kỳ với khả năng bật/tắt.
Hỗ trợ chuỗi công việc (job sequencing) theo trình tự, ví dụ Job1 → Job2 → Job3.
Lưu ý quan trọng: Job sequencing giúp các tác vụ liên quan được thực hiện theo thứ tự, đảm bảo tính chính xác và đồng bộ trong quy trình.
Yêu Cầu Phi Chức Năng
Tính sẵn sàng cao (High availability): Hệ thống phải hoạt động liên tục, không gián đoạn.
Cam kết ít nhất một lần trong việc chạy mỗi công việc.
Thời gian thực thi tác vụ gần đúng lịch trình (chậm trễ chấp nhận được khoảng 2-4 giây).
Độ bền dữ liệu cao: Không được mất bất kỳ công việc nào dù hệ thống có sự cố.
Thông báo trễ (delay notifications) cho người dùng khi công việc không được thực hiện đúng hạn.
Khả năng mở rộng để xử lý tới 10 tỷ công việc mỗi ngày.
Ước Lượng Lưu Lượng và Lưu Trữ
Thông số
Giá trị
Số công việc mỗi ngày
10
9
(tỷ)
Số công việc/giây
~10.000
Kích thước job (ước tính)
200 dòng × 50 bytes/dòng = 10KB
Dung lượng lưu trữ/ngày
10TB
Việc lưu trữ khối lượng lớn dữ liệu này đòi hỏi hệ thống phải lựa chọn công nghệ phù hợp, tập trung vào khả năng ghi nhanh, mở rộng và độ bền cao.
Thiết Kế Tổng Quan
API Thiết Kế
POST /api/v1/jobs để đăng ký công việc mới
{
"taskId":"unique_task_id",
"cron":"*/5 * * * *",
"params":{
"p1":"val1",
"p2":"val2"
}
}
GET /api/v1/jobs/:jobId trả về tình trạng của công việc
Cơ Sở Dữ Liệu
Vì hệ thống chủ yếu ghi dữ liệu lớn và không cần tính chuẩn ACID phức tạp, nên chọn các NoSQL database như DynamoDB, Cassandra, hoặc MongoDB sẽ phù hợp hơn so với hệ thống SQL truyền thống.
Vì sao không chọn SQL?
Không cần quan hệ phức tạp hay chuẩn hóa dữ liệu.
ACID không quá quan trọng với tính năng này.
NoSQL hỗ trợ mở rộng linh hoạt, phù hợp khối lượng lớn.
Schema mẫu lưu trữ:
Trường
Mô tả
time_bucket
Khoảng thời gian theo từng phút (ví dụ: 2025-05-24T10:00:00Z)
execution_time
Thời điểm dự kiến thực thi
job_id
Mã định danh công việc
user_id
Mã người dùng đăng ký
status
Trạng thái (PENDING, RUNNING, SUCCESS, FAILED)
attempt
Số lần thử chạy
script_url
Đường dẫn tới script (ví dụ S3)
Luồng Thực Thi và Lập Lịch Công Việc
Quy trình vận hành
Watcher Service: Chạy định kỳ (ví dụ mỗi phút) sẽ truy vấn DB để lấy danh sách các công việc dự kiến thực thi trong 1 phút kế tiếp.
Đẩy các công việc này vào hàng đợi FIFO SQS (AWS) để đảm bảo thứ tự.
Executor Service: Lấy công việc từ hàng đợi, khởi tạo một container ECS độc lập để chạy script.
Thực hiện kiểm tra idempotency để tránh chạy trùng.
Cập nhật trạng thái công việc trong DB.
Xử Lý Lặp Lại (Idempotency) và Quản Lý Tin Nhắn
Vấn đề Duplicate Messages
Trong trường hợp ExecutorService bị crashes giữa chừng hoặc chưa kịp xóa tin nhắn khỏi SQS, tin nhắn sẽ được gửi lại.
Giải pháp Idempotency
Kiểm tra xem jobRunId đã được đánh dấu hoàn thành chưa trong DB, nếu có thì bỏ qua, nếu chưa thì thực thi và cập nhật.
if (jobRunIdAlreadyProcessed(jobRunId)) {
// Bỏ qua
} else {
// Thực thi và cập nhật trạng thái thành công
}
Quy trình làm việc với SQS FIFO
ReceiveMessage: Executor nhận tin nhắn, Visibility Timeout bắt đầu tính.
Sau khi xử lý thành công: gọi DeleteMessage để xóa tin nhắn khỏi queue.
Nếu không gọi Delete trước timeout, tin nhắn sẽ được gửi lại.
Lưu ý: Việc xóa tin nhắn thủ công là bắt buộc với SQS để tránh xử lý trùng lặp.
Người dùng phải commit offsets thủ công khi xử lý thành công.
Giảm thiểu trùng lặp dựa trên tính chất commit offset.
Vẫn cần idempotency kiểm tra trạng thái để tránh thực thi lại công việc.
Ví dụ sử dụng Kafka consumer:
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
if (!jobAlreadyProcessed(record.key())) {
process(record.value());
markJobComplete(record.key());
}
}
consumer.commitSync();
Định Nghĩa Idempotency
Một quy trình được gọi là idempotent nếu việc thực thi nhiều lần có cùng kết quả như chỉ chạy một lần.
Ví dụ trong Scheduler:
Nếu một jobRunId đã được đánh dấu hoàn thành trong DB, sẽ không chạy lại lần nữa.
Điều này giúp đảm bảo an toàn khi hệ thống có retry hoặc lỗi.
Giám Sát và Đo Lường Hiệu Suất
Các chỉ số nên theo dõi
Tỷ lệ công việc được submit và thực thi theo phút, giờ, ngày.
Thời gian chạy trung bình của mỗi công việc.
Tỷ lệ thành công và thất bại.
Tình trạng sức khỏe hạ tầng: CPU, RAM của các Executor nodes.
Kích thước hàng đợi SQS hoặc độ trễ Kafka lag.
Độ trễ đọc/ghi của DB.
Công cụ khuyến nghị
Thiết lập cảnh báo khi có trễ, lỗi, hoặc retry nhiều lần.
Sử dụng Prometheus + Grafana hoặc AWS CloudWatch để hiển thị dashboard.
Sử dụng Dead Letter Queue (DLQ) cho các job lỗi nhiều lần.
Lưu trữ log audit toàn bộ các sự kiện có liên quan đến job.
Kết Luận
Bài viết đã trình bày chi tiết thiết kế một Distributed Job Scheduler quy mô lớn, với các đặc điểm nổi bật:
Thiết kế API đơn giản, hiệu quả.
Lựa chọn database NoSQL phù hợp với mô hình write-heavy, scale cao.
Đảm bảo tính bền vững, an toàn với idempotent processing.
Quản lý công việc bằng hàng đợi FIFO (AWS SQS) hoặc có thể mở rộng với Kafka.
Các phương án giám sát, báo cáo giúp tăng khả năng vận hành và phát hiện lỗi nhanh chóng.
Việc thiết kế và triển khai một hệ thống như vậy không chỉ giúp xử lý hàng tỷ công việc mỗi ngày mà còn đảm bảo tính ổn định và đáp ứng gần real-time cho các ứng dụng đòi hỏi cao.