Asynchronous Request-Reply Pattern – Cách xử lý tác vụ nặng hiệu quả mà vẫn phản hồi kịp thời

1. Tại sao xử lý đồng bộ không phù hợp với tác vụ nặng?

 

Hãy tưởng tượng bạn có một API như POST /api/generate-report. Khi nhận request, server cần truy vấn dữ liệu lớn, xử lý và xuất ra file Excel hoặc PDF – những thao tác có thể mất hàng chục giây đến vài phút.

 

Nếu xử lý trực tiếp theo cách truyền thống:

 

  • Dễ bị timeout: Nhiều server/web proxy sẽ ngắt kết nối nếu request kéo dài quá 30–60 giây.

 

  • Người dùng phải chờ lâu: Không có phản hồi nào trong lúc xử lý, gây khó chịu.

 

  • Tốn tài nguyên server: Kết nối vẫn giữ mở dù backend đang “bận”, khiến hệ thống kém hiệu quả.

 

2. Asynchronous Request-Reply là gì?

 

Đây là một kiến trúc cho phép xử lý các request lớn trong background, đồng thời vẫn đảm bảo người dùng nhận được kết quả sau cùng.

 

Microsoft định nghĩa như sau:

 

“Tách biệt phần xử lý backend khỏi frontend, nơi backend hoạt động bất đồng bộ nhưng frontend vẫn được thông báo kết quả cuối cùng.”

 

Tin tức công nghệ,  Asynchronous, Pattern Asynchronous Request-Reply

 

Nguyên lý hoạt động:

 

  1. Frontend gửi yêu cầu → Backend lập tức phản hồi đã tiếp nhận yêu cầu, kèm theo jobId hoặc URL tra cứu trạng thái.

  2. Backend xử lý trong background – không giữ kết nối với client.

  3. Client kiểm tra trạng thái job bằng jobId, và nhận kết quả khi hoàn thành.

 

Giống như bạn đặt món ăn ở quán: bạn nhận thiết bị rung, đi làm việc khác, khi món sẵn sàng thì thiết bị báo – không cần đứng chờ.

 

 

3. Luồng hoạt động chi tiết

 

Mô hình này thường bao gồm 4 thành phần chính: Client, API Server, Message Queue và Worker.

 

Tin tức công nghệ,  Asynchronous, Pattern Asynchronous Request-Reply

 

Chi tiết các bước:

 

  • Client gửi request tới endpoint /export.

 

  • API Server tạo một jobId, ghi lại job này và đẩy vào hàng đợi (Message Queue).

 

  • Server phản hồi ngay với mã 202 Accepted, kèm URL kiểm tra trạng thái.

 

  • Worker (một tiến trình xử lý riêng) lắng nghe job mới và thực hiện công việc.

 

  • Trong quá trình này, Worker sẽ cập nhật trạng thái job vào database.

 

  • Client có thể polling theo định kỳ để xem job đã hoàn thành hay chưa.

 

  • Khi hoàn tất, client sẽ nhận được link tải kết quả (như file zip, excel...).

 

Ngoài việc polling, bạn cũng có thể dùng các cách thông báo chủ động hơn như WebSocket, Webhook, hoặc gửi email cho người dùng.

 

4. Triển khai demo bằng Node.js & TypeScript

 

Để minh họa, chúng ta sẽ xây dựng một API mô phỏng tác vụ xuất báo cáo bằng cách xử lý job bất đồng bộ trong memory.

 

Cài đặt và chuẩn bị

 

npm init -y npm install express typescript ts-node @types/express @types/node uuid @types/uuid npx tsc --init

 

Cấu trúc thư mục

 

/src
  ├── server.ts
  └── types.ts
 

src/types.ts: Định nghĩa kiểu dữ liệu

 

export type JobStatus = 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED';
 
export interface Job {
 
  id: string;
 
  status: JobStatus;
 
  createdAt: Date;
 
  updatedAt: Date;
 
  resultUrl?: string;
 
  error?: string;
 
}
 

src/server.ts: Logic chính


 
import express from 'express';
 
import { v4 as uuidv4 } from 'uuid';
 
import { Job } from './types';
 
 
const app = express();
 
const PORT = 3000;
 
// Giả lập storage và queue
 
const jobStore = new Map<string, Job>();
 
 
// Mô phỏng worker xử lý job
 
const processExportJob = async (jobId: string) => {
 
  const job = jobStore.get(jobId);
 
  if (!job) return;
 
  job.status = 'PROCESSING';
 
  job.updatedAt = new Date();
 
  jobStore.set(jobId, job);
 
  // Mô phỏng xử lý nặng
 
  await new Promise(resolve => setTimeout(resolve, 15000));
 
  const resultUrl = `https://storage.example.com/exports/${jobId}.zip`;
 
  job.status = 'COMPLETED';
 
  job.resultUrl = resultUrl;
 
  job.updatedAt = new Date();
 
  jobStore.set(jobId, job);
 
};
 
// Tạo job mới
 
app.post('/export', (req, res) => {
 
  const jobId = uuidv4();
 
  const newJob: Job = {
 
    id: jobId,
 
    status: 'PENDING',
 
    createdAt: new Date(),
 
    updatedAt: new Date(),
 
  };
 
  jobStore.set(jobId, newJob);
 
  processExportJob(jobId); // Push vào queue (mô phỏng)
 
  res.status(202).json({
 
    message: 'Yêu cầu đã được ghi nhận.',
 
    jobId,
 
    statusUrl: `http://localhost:${PORT}/export/status/${jobId}`
 
  });
 
});
 
// Kiểm tra trạng thái
 
app.get('/export/status/:jobId', (req, res) => {
 
  const job = jobStore.get(req.params.jobId);
 
  if (!job) return res.status(404).json({ error: 'Job không tồn tại' });
 
  if (job.status === 'COMPLETED') return res.status(200).json(job);
 
  if (job.status === 'FAILED') return res.status(200).json(job);
 
  res.status(200).json({
 
    status: job.status,
 
    message: 'Đang xử lý, vui lòng kiểm tra lại sau.'
 
  });
 
});
 
app.listen(PORT, () => {
 
  console.log(`Server đang chạy tại http://localhost:${PORT}`);
 
});
 

5. Kiểm thử nhanh

 

  • Khởi động server:

    
    		
     
    npx ts-node src/server.ts
     
  • Gửi yêu cầu export:

    
    		
     
    curl -X POST http://localhost:3000/export
     
  • Nhận lại jobId và URL kiểm tra trạng thái.

 

  • Sau vài giây, kiểm tra:

    
    		
     
    curl http://localhost:3000/export/status/{jobId}
     

6. Tổng kết

 

Asynchronous Request-Reply không chỉ là kỹ thuật backend giúp tránh timeout mà còn là một hướng đi hiệu quả để xây dựng các hệ thống có tính mở rộng cao. Bằng cách tách riêng phần xử lý khỏi phần phản hồi, bạn vừa tối ưu tài nguyên server, vừa cải thiện trải nghiệm người dùng đáng kể.

 

 HỖ TRỢ TRỰC TUYẾN