Tối ưu hóa hiệu suất API cho các tác vụ xử lý dữ liệu lớn bằng Background Jobs

Trong quá trình phát triển ứng dụng web, các tác vụ xử lý dữ liệu nặng hoặc gọi API bên thứ ba có độ trễ lớn thường làm giảm trải nghiệm người dùng do thời gian chờ đợi phản hồi (timeout). Để giải quyết vấn đề này, việc chuyển đổi các tác vụ đồng bộ sang xử lý bất đồng bộ thông qua Background Jobs là một giải pháp tối ưu. Dưới đây là cách triển khai chi tiết dựa trên nền tảng ABP Framework.

1. Xây dựng lớp xử lý tác vụ nền

Lớp xử lý cần kế thừa từ BackgroundJob<TArgs>. Tại đây, chúng ta thực hiện logic nghiệp vụ chính và sử dụng ICacheManager để lưu trữ trạng thái xử lý, giúp phía giao diện (Frontend) có thể theo dõi tiến độ.


public class DataSyncWorker : BackgroundJob<SyncTaskArgs>, ITransientDependency
{
    private readonly ICacheManager _cacheManager;
    private readonly IBackgroundJobManager _jobManager;
    private readonly IRepository<ClassEntity, Guid> _classRepository;

    public DataSyncWorker(
        ICacheManager cacheManager,
        IBackgroundJobManager jobManager,
        IRepository<ClassEntity, Guid> classRepository)
    {
        _cacheManager = cacheManager;
        _jobManager = jobManager;
        _classRepository = classRepository;
    }

    [UnitOfWork]
    public override void Execute(SyncTaskArgs args)
    {
        try
        {
            // Thiết lập TenantId nếu ứng dụng hỗ trợ đa thuê bao
            UnitOfWorkManager.Current.SetTenantId(1);

            var cacheKey = $"SyncTask_{args.JobIdentifier}";
            var cachedStatus = _cacheManager.GetCache(cacheKey).GetOrDefault(cacheKey);

            if (cachedStatus != null)
            {
                var info = (TaskProgressInfo)cachedStatus;
                // Xóa job cũ nếu cần thiết để tránh trùng lặp
                _jobManager.Delete(info.JobId);
            }

            // Cập nhật trạng thái bắt đầu
            UpdateProgress(args.JobIdentifier, "Đang xử lý", 1, 3, 0);

            // Thực hiện logic nghiệp vụ (ví dụ: xử lý dữ liệu)
            // ... Logic xử lý tại đây ...

            // Cập nhật trạng thái hoàn tất
            UpdateProgress(args.JobIdentifier, "Thành công", 3, 3, 1);
            
            CurrentUnitOfWork.SaveChanges();
        }
        catch (Exception ex)
        {
            // Ghi nhận lỗi vào cache
            _cacheManager.GetCache($"SyncTask_{args.JobIdentifier}")
                .Set($"SyncTask_{args.JobIdentifier}", new TaskProgressInfo 
                { 
                    Status = 2, 
                    Message = "Lỗi hệ thống: " + ex.Message 
                });
        }
    }

    private void UpdateProgress(string id, string msg, int step, int total, int status)
    {
        var progressKey = $"SyncTask_{id}";
        _cacheManager.GetCache(progressKey).Set(progressKey, new TaskProgressInfo
        {
            Status = status,
            Message = msg,
            JobIdentifier = id,
            Progress = $"{step}/{total}"
        });
    }
}

2. Kích hoạt Job và quản lý trạng thái từ Controller/AppService

Thay vì đợi tác vụ hoàn thành, chúng ta đẩy tác vụ vào hàng đợi và trả về một mã định danh (JobIdentifier). Client sẽ sử dụng mã này để truy vấn trạng thái qua một API khác.


public async Task<TaskProgressInfo> StartDataSyncAsync(SyncTaskArgs input)
{
    // Nếu đã có JobIdentifier, tiến hành kiểm tra trạng thái trong cache
    if (!string.IsNullOrEmpty(input.JobIdentifier))
    {
        var cacheKey = $"SyncTask_{input.JobIdentifier}";
        var cachedData = _cacheManager.GetCache(cacheKey).GetOrDefault(cacheKey);
        
        return cachedData != null 
            ? (TaskProgressInfo)cachedData 
            : new TaskProgressInfo { Status = 2, Message = "Không tìm thấy tiến trình" };
    }

    // Khởi tạo tiến trình mới
    string newJobId = Guid.NewGuid().ToString();
    input.JobIdentifier = newJobId;

    // Đưa vào hàng đợi xử lý ngầm
    var internalJobId = await _backgroundJobManager.EnqueueAsync<DataSyncWorker, SyncTaskArgs>(input);

    // Lưu thông tin khởi tạo vào cache (thời gian sống 10 phút)
    var initialStatus = new TaskProgressInfo 
    { 
        Status = 0, 
        Message = "Đang khởi tạo", 
        JobIdentifier = newJobId, 
        JobId = internalJobId 
    };

    _cacheManager.GetCache($"SyncTask_{newJobId}")
        .Set($"SyncTask_{newJobId}", initialStatus, TimeSpan.FromMinutes(10));

    return initialStatus;
}

Lưu ý về tính nhất quán

Khi sử dụng Background Jobs cho các tiến trình dài, cần lưu ý khả năng tác vụ bị thực thi lại (retry mechanism) của các thư viện như Hangfire hoặc mặc định của ABP. Việc kiểm tra trạng thái trong cache hoặc sử dụng kỹ thuật Idempotency là cần thiết để đảm bảo dữ liệu không bị xử lý sai lệch khi có sự cố mạng hoặc khởi động lại server.

Thẻ: ABP Framework Background Jobs .net core Asynchronous Programming ICacheManager

Đăng vào ngày 29 tháng 6 lúc 16:12