Hệ Thống Quản Lý Công Việc Phân Tán ScheduleMaster

1.ScheduleMaster Là Gì

ScheduleMaster là hệ thống quản lý công việc phân tán, được viết bởi một nhà phát triển trong nước. Hiểu đơn giản, ScheduleMaster là một khung để quản lý tập trung các công việc từ các hệ thống khác nhau.

Ví dụ, nếu chúng ta có nhiều hệ thống với các tác vụ riêng biệt và mỗi hệ thống phải tự duy trì thì sẽ rất khó khăn khi số lượng tác vụ tăng lên. Điều này làm gia tăng chi phí bảo trì và kỹ thuật. Vì vậy, cần sử dụng một khung hệ thống phân tán để quản lý tập trung.

Chúng ta sẽ học về ScheduleMaster, một trong những khung hệ thống phân tán tốt nhất hiện nay.

2.Sử Dụng ScheduleMaster

1.Tạo vài dịch vụ vi mô bằng ASP.NET Core Web API cho các chức năng như chấm công, tính lương, email, và tin nhắn.

2.Tải xuống mã nguồn mở của ScheduleMaster và dùng nó để điều phối các giao diện API của chúng ta.

- sqlserver: "Persist Security Info = False; User ID =sa; Password =abcdef; Initial Catalog =job_master; Server =."
- postgresql: "Server=localhost;Port=5432;Database=job_master;User Id=admin;Password=abcdef;Pooling=true;MaxPoolSize=20;"
- mysql: "Data Source=localhost;Database=job_master;User ID=root;Password=abcdef;pooling=true;CharSet=utf8mb4;port=3306;sslmode=none;TreatTinyAsBoolean=true"

Chỉnh sửa cấu hình Host và hỗ trợ cơ sở dữ liệu, mặc định khung dùng MySQL.

Chỉnh sửa cấu hình Web và hỗ trợ cơ sở dữ liệu, mặc định khung dùng MySQL.

3.Vào thư mục xuất bản của dự án Hos.ScheduleMaster.Web, chạy lệnh dotnet Hos.ScheduleMaster.Web.dll để khởi động dự án, lúc này cơ sở dữ liệu sẽ được tạo ra.

Tài khoản đăng nhập: admin

Mật khẩu: 654321

4.Vào thư mục xuất bản của dự án Hos.ScheduleMaster.QuartzHost, thực thi lệnh sau để khởi động:

dotnet Hos.ScheduleMaster.QuartzHost.dll --urls http://*:30004

1.Cấu Hình Nhiệm Vụ HTTP

5.Sau khi sẵn sàng, vào phần quản lý nút trên giao diện web, bạn sẽ thấy nút chính 30001 và giao diện nhiệm vụ 30003 đang hoạt động.

6.Sử dụng menu danh sách nhiệm vụ, tạo nhiệm vụ lập lịch, cấu hình thông tin cơ bản và siêu dữ liệu, sau đó lưu lại để bắt đầu thực hiện nhiệm vụ.

2.Cấu Hình Nhiệm Vụ Tập Tin Chương Trình

1.Tạo một thư viện lớp, cài đặt thư viện ScheduleMaster, tạo một lớp kế thừa TaskBase, triển khai phương thức trừu tượng và biên dịch chương trình.

namespace JobServiceLib
{
    class ProgramTask : TaskBase
    {
        public override void Execute(JobContext context)
        {
            context.WriteLog("Nhiệm vụ tập tin chương trình");
        }
    }
}

2.Tìm thư mục debug, loại bỏ tập tin Base, sau đó thêm nó vào tệp nén.

3.Trong giao diện web, tìm phần cấu hình nhiệm vụ, chọn tập tin chương trình để cấu hình cơ bản, cấu hình siêu dữ liệu, nhập tên tập tin chương trình, sau đó tải lên và lưu để chạy.

3.Sử Dụng API Để Kết Nối Nhiệm Vụ

Để tích hợp dễ dàng hơn, ScheduleMaster không chỉ cho phép tạo nhiệm vụ qua giao diện mà còn cung cấp WebAPI để kết nối từ hệ thống kinh doanh.

1.Quy Trình Kết Nối API Server
  • Tạo tài khoản người dùng chuyên dụng trong giao diện.
  • Sử dụng tên người dùng làm giá trị api_user trong header HTTP.
  • Sử dụng khóa bí mật đã băm làm giá trị api_secret trong header HTTP, tính toán theo quy tắc: ghép chuỗi {username}{hash(password)}{username}, sau đó băm chuỗi kết quả bằng hàm MD5 32 bit chữ thường.
  • Thực hiện yêu cầu HTTP dạng form, nếu không hợp lệ sẽ trả về lỗi 401 - Unauthorized.
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("api_user", "admin");
client.DefaultRequestHeaders.Add("api_secret", HashHelper.MD5($"admin{HashHelper.MD5("654321")}}admin"));

Các giao diện đều trả về định dạng thống nhất với các trường sau:

Tên Trường Kiểu Dữ Liệu Mô Tả
Success bool Có thành công hay không
Status int Trạng thái kết quả, 0-Thất bại 1-Thành công 2-Lỗi đăng nhập 3-Lỗi tham số 4-Lỗi dữ liệu
Message string Thông điệp trả về
Data object Dữ liệu trả về
2.Tạo Nhiệm Vụ Tập Tin Chương Trình

Không thể tải tập tin chương trình qua API nên cần đảm bảo tập tin đã được tải lên trước khi khởi động nhiệm vụ.

  • Địa chỉ giao diện: http://yourip:30001/api/job/create
  • Loại yêu cầu: POST
  • Định dạng tham số: application/x-www-form-urlencoded
  • Kết quả trả về: ID nhiệm vụ khi tạo thành công
  • Danh sách tham số:
Tên Trường Kiểu Dữ Liệu Bắt Buộc Mô Tả
TypeMeta int Loại nhiệm vụ, cố định là 1
Title string Tên nhiệm vụ
RunLoop bool Có chạy tuần hoàn hay không
CronExpression string Không Biểu thức cron, nếu RunLoop là true thì bắt buộc
AssemblyName string Tên tập tin chương trình
ClassName string Tên lớp thực thi, gồm toàn bộ namespace
StartDate DateTime Thời gian bắt đầu nhiệm vụ
EndDate DateTime Không Thời gian dừng nhiệm vụ, nếu trống thì không giới hạn
Description string Không Mô tả nhiệm vụ
Keepers List<int> Không ID giám sát viên
Nexts List<guid> Không ID nhiệm vụ con
Executors List<string> Không Tên nút thực thi
RunNow bool Không Có chạy ngay sau khi tạo thành công hay không
Params List<JobParam> Không Danh sách tham số tùy chỉnh, cũng có thể truyền chuỗi JSON qua trường CustomParamsJson

JobParam:

Tên Trường Kiểu Dữ Liệu Bắt Buộc Mô Tả
ParamKey string Tên tham số
ParamValue string Giá trị tham số
ParamDescription string Không Mô tả tham số
HttpClient client = new HttpClient();
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>();
args.Add(new KeyValuePair<string, string>("TypeMeta", "1"));
args.Add(new KeyValuePair<string, string>("RunLoop", "true"));
args.Add(new KeyValuePair<string, string>("CronExpression", "30 0/7 * * * ?"));
args.Add(new KeyValuePair<string, string>("Description", "Tạo bởi Xunit Tester"));
args.Add(new KeyValuePair<string, string>("StartDate", DateTime.Today.ToString("yyyy-MM-dd HH:mm:ss")));
args.Add(new KeyValuePair<string, string>("Title", "Nhiệm vụ kiểm tra tập tin chương trình"));
args.Add(new KeyValuePair<string, string>("AssemblyName", "Hos.ScheduleMaster.Sample"));
args.Add(new KeyValuePair<string, string>("ClassName", "Hos.ScheduleMaster.Sample.Simple"));
args.Add(new KeyValuePair<string, string>("CustomParamsJson", "[{\"ParamKey\":\"k1\",\"ParamValue\":\"1111\",\"ParamDescription\":\"r1\"},{\"ParamKey\":\"k2\",\"ParamValue\":\"2222\",\"ParamDescription\":\"r2\"}]"));
args.Add(new KeyValuePair<string, string>("Keepers", "1"));
args.Add(new KeyValuePair<string, string>("Keepers", "2"));
//args.Add(new KeyValuePair<string, string>("Nexts", ""));
//args.Add(new KeyValuePair<string, string>("Executors", ""));
HttpContent reqContent = new FormUrlEncodedContent(args);
var response = await client.PostAsync("http://localhost:30001/api/Job/Create", reqContent);
var content = await response.Content.ReadAsStringAsync();
Debug.WriteLine(content);

3.Tạo Nhiệm Vụ HTTP
  • Địa chỉ giao diện: http://yourip:30001/api/job/create
  • Loại yêu cầu: POST
  • Định dạng tham số: application/x-www-form-urlencoded
  • Kết quả trả về: ID nhiệm vụ khi tạo thành công
  • Danh sách tham số:
Tên Trường Kiểu Dữ Liệu Bắt Buộc Mô Tả
TypeMeta int Loại nhiệm vụ, cố định là 2
Title string Tên nhiệm vụ
RunLoop bool Có chạy tuần hoàn hay không
CronExpression string Không Biểu thức cron, nếu RunLoop là true thì bắt buộc
StartDate DateTime Thời gian bắt đầu nhiệm vụ
EndDate DateTime Không Thời gian dừng nhiệm vụ, nếu trống thì không giới hạn
Description string Không Mô tả nhiệm vụ
HttpRequestUrl string Địa chỉ yêu cầu
HttpMethod string Phương thức yêu cầu, chỉ hỗ trợ GET\\POST\\PUT\\DELETE
HttpContentType string Định dạng tham số, chỉ hỗ trợ application/json và application/x-www-form-urlencoded
HttpHeaders string Không Thiết lập tiêu đề yêu cầu tùy chỉnh, chuỗi JSON của danh sách JobParam
HttpBody string Nếu là tham số json thì là chuỗi JSON tương ứng; nếu là tham số form thì là chuỗi JSON của danh sách JobParam.
Keepers List<int> Không ID giám sát viên
Nexts List<guid> Không ID nhiệm vụ con
Executors List<string> Không Tên nút thực thi
RunNow bool Không Có chạy ngay sau khi tạo thành công hay không
HttpClient client = new HttpClient();
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>();
args.Add(new KeyValuePair<string, string>("TypeMeta", "2"));
args.Add(new KeyValuePair<string, string>("RunLoop", "true"));
args.Add(new KeyValuePair<string, string>("CronExpression", "20 0/7 * * * ?"));
args.Add(new KeyValuePair<string, string>("Description", "Tạo bởi Xunit Tester"));
args.Add(new KeyValuePair<string, string>("StartDate", DateTime.Today.ToString("yyyy-MM-dd HH:mm:ss")));
args.Add(new KeyValuePair<string, string>("Title", "Nhiệm vụ kiểm tra HTTP"));
args.Add(new KeyValuePair<string, string>("HttpRequestUrl", "http://localhost:56655/api/1.0/value/jsonpost"));
args.Add(new KeyValuePair<string, string>("HttpMethod", "POST"));
args.Add(new KeyValuePair<string, string>("HttpContentType", "application/json"));
args.Add(new KeyValuePair<string, string>("HttpHeaders", "[]"));
args.Add(new KeyValuePair<string, string>("HttpBody", "{ \"Posts\": [{ \"PostId\": 666, \"Title\": \"tester\", \"Content\":\"testtesttest\" }], \"BlogId\": 111, \"Url\":\"qweqrrttryrtyrtrtrt\" }"));
HttpContent reqContent = new FormUrlEncodedContent(args);
var response = await client.PostAsync("http://localhost:30001/api/Job/Create", reqContent);
var content = await response.Content.ReadAsStringAsync();
Debug.WriteLine(content);

4.Tạo Nhiệm Vụ Trễ
  • Địa chỉ giao diện: http://yourip:30001/api/delayjob/create
  • Loại yêu cầu: POST
  • Định dạng tham số: application/x-www-form-urlencoded
  • Kết quả trả về: ID nhiệm vụ khi tạo thành công
  • Danh sách tham số:
Tên Trường Kiểu Dữ Liệu Bắt Buộc Mô Tả
SourceApp string Nguồn
Topic string Chủ đề
ContentKey string Khóa nội dung nghiệp vụ
DelayTimeSpan int Thời gian trễ tương đối
DelayAbsoluteTime DateTime Thời gian trễ tuyệt đối
NotifyUrl string Địa chỉ gọi lại
NotifyDataType string Định dạng tham số gọi lại, chỉ hỗ trợ application/json và application/x-www-form-urlencoded
NotifyBody string Tham số gọi lại, chuỗi JSON
for (int i = 0; i < 5; i++)
{
    int rndNum = new Random().Next(20, 500);
    List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>();
    args.Add(new KeyValuePair<string, string>("SourceApp", "TestApp"));
    args.Add(new KeyValuePair<string, string>("Topic", "TestApp.Trade.TimeoutCancel"));
    args.Add(new KeyValuePair<string, string>("ContentKey", i.ToString()));
    args.Add(new KeyValuePair<string, string>("DelayTimeSpan", rndNum.ToString()));
    args.Add(new KeyValuePair<string, string>("DelayAbsoluteTime", DateTime.Now.AddSeconds(rndNum).ToString("yyyy-MM-dd HH:mm:ss")));
    args.Add(new KeyValuePair<string, string>("NotifyUrl", "http://localhost:56655/api/1.0/value/delaypost"));
    args.Add(new KeyValuePair<string, string>("NotifyDataType", "application/json"));
    args.Add(new KeyValuePair<string, string>("NotifyBody", "{ \"Posts\": [{ \"PostId\": 666, \"Title\": \"tester\", \"Content\":\"testtesttest\" }], \"BlogId\": 111, \"Url\":\"qweqrrttryrtyrtrtrt\" }"));
    HttpContent reqContent = new FormUrlEncodedContent(args);
    var response = await client.PostAsync("http://localhost:30001/api/DelayJob/Create", reqContent);
    var content = await response.Content.ReadAsStringAsync();
    Debug.WriteLine(content);
}

4.Phân Tích Khung Hệ Thống
1.Thiết Kế Toàn Cục

Theo sơ đồ thiết kế trên trang web chính thức và quy trình vận hành, có thể tổng kết quy trình toàn cục của nhiệm vụ như sau: Client ——> Master ——> Work ——> Thực Hiện Nhiệm Vụ. Về mặt kiến trúc, ý tưởng lớn là tách rời nhiệm vụ khỏi dịch vụ đơn lẻ để quản lý và gọi độc lập.

2.Phân Tích Master Và Work

Chúng ta phân tích từ nút chính, nút phụ, và bảng dữ liệu. Quan hệ giữa MasterWork là hỗ trợ lẫn nhau. Master thêm nhiệm vụ vào cơ sở dữ liệu, Work lấy nhiệm vụ từ cơ sở dữ liệu, tạo cấu hình và thực thi bằng Quartz.

Master và Work ngoài việc cung cấp giao diện người dùng và xử lý hậu trường, còn thực hiện các công việc cụ thể như sau:

Master

  • 1.Phân bổ nhiệm vụ và chọn nút
  • 2.Kiểm tra sức khỏe của nút Work, chuyển đổi nhiệm vụ khi gặp sự cố

Work

  • 1.Lấy thông tin cấu hình nhiệm vụ
  • 2.Chạy nhiệm vụ bằng Quartz theo cấu hình
  • 3.Gọi phản chiếu tập tin chương trình
  • 4.Gọi giao diện HTTP bằng HttpClient

Thẻ: ScheduleMaster Quartz .net core ASP.NET Core API Integration

Đăng vào ngày 13 tháng 6 lúc 03:25