Xây dựng cho các hệ thống lớn và các công việc nền chạy lâu dài. Nguồn: Ilias Chebbi trên Unsplash Vài tháng trước, tôi đảm nhận vai trò yêu cầu xây dựng cơ sở hạ tầngXây dựng cho các hệ thống lớn và các công việc nền chạy lâu dài. Nguồn: Ilias Chebbi trên Unsplash Vài tháng trước, tôi đảm nhận vai trò yêu cầu xây dựng cơ sở hạ tầng

Xây dựng Spotify cho các bài giảng.

2025/12/11 21:15

Xây dựng cho hệ thống lớn và các công việc nền chạy lâu dài.

Nguồn: Ilias Chebbi trên Unsplash

Vài tháng trước, tôi đảm nhận vai trò xây dựng cơ sở hạ tầng cho việc phát trực tuyến media (âm thanh). Nhưng ngoài việc phục vụ âm thanh dưới dạng các phần có thể phát trực tuyến, còn có các công việc xử lý media chạy lâu dài và một pipeline RAG mở rộng phục vụ cho việc phiên âm, chuyển mã, nhúng và cập nhật media tuần tự. Việc xây dựng MVP với tư duy sản phẩm đã khiến chúng tôi phải lặp đi lặp lại cho đến khi đạt được một hệ thống liền mạch. Cách tiếp cận của chúng tôi là tích hợp các tính năng và ngăn xếp ưu tiên cơ bản.

Mối quan tâm chính:

Trong quá trình xây dựng, mỗi lần lặp lại đều là phản ứng với nhu cầu tức thời và thường là "toàn diện". Mối quan tâm ban đầu là xếp hàng các công việc, Redis đã đáp ứng đủ; chúng tôi chỉ đơn giản là kích hoạt và quên đi. Bull MQ trong framework NEST JS đã cho chúng tôi kiểm soát tốt hơn đối với việc thử lại, tồn đọng và hàng đợi thư chết. Cục bộ và với một vài tải trọng trong sản xuất, chúng tôi đã có được luồng media đúng. Chúng tôi sớm bị đè nặng bởi gánh nặng của Khả năng quan sát:
Logs → Ghi lại các công việc (yêu cầu, phản hồi, lỗi).
Metrics → Bao nhiêu / bao lâu các công việc này chạy, thất bại, hoàn thành, v.v.
Traces → Đường đi mà một công việc đi qua các dịch vụ (các hàm/phương thức được gọi trong đường dẫn luồng).

Bạn có thể giải quyết một số vấn đề này bằng cách thiết kế API và xây dựng bảng điều khiển tùy chỉnh để kết nối chúng, nhưng vấn đề về Khả năng mở rộng sẽ đủ. Và thực tế, chúng tôi đã thiết kế các API.

Xây dựng cho Khả năng quan sát

Thách thức của việc quản lý các quy trình làm việc phức tạp, chạy lâu dài ở backend, nơi các lỗi phải có thể khôi phục được và trạng thái phải bền vững, Inngest đã trở thành giải pháp kiến trúc của chúng tôi. Nó đã cơ bản định hình lại cách tiếp cận của chúng tôi: mỗi công việc nền chạy lâu dài trở thành một hàm nền, được kích hoạt bởi một sự kiện cụ thể.

Ví dụ, một sự kiện Transcription.request sẽ kích hoạt một hàm TranscribeAudio. Hàm này có thể chứa các bước chạy cho: fetch_audio_metadata, deepgram_transcribe, parse_save_trasncription và notify_user.

Phân tích Quy trình làm việc: Hàm Inngest và Step-runs

Nguyên tắc cơ bản về độ bền là step-runs. Một hàm nền được phân tách nội bộ thành các step-runs này, mỗi step chứa một khối logic nguyên tử, tối thiểu.

  • Logic nguyên tử: Một hàm thực thi logic kinh doanh của bạn từng bước một. Nếu một bước thất bại, trạng thái của toàn bộ lần chạy được bảo toàn và có thể thử lại. Điều này khởi động lại hàm từ đầu. Các bước riêng lẻ hoặc step-runs không thể được thử lại một cách riêng biệt.
  • Tuần tự hóa phản hồi: Một step-run được xác định bởi phản hồi của nó. Phản hồi này được tự động tuần tự hóa, điều này rất cần thiết để bảo toàn các cấu trúc dữ liệu phức tạp hoặc được định kiểu mạnh mẽ qua các ranh giới thực thi. Các step-runs tiếp theo có thể phân tích cú pháp phản hồi đã tuần tự hóa này một cách đáng tin cậy, hoặc logic có thể được hợp nhất thành một bước duy nhất để tăng hiệu quả.
  • Tách rời và Lập lịch: Trong một hàm, chúng tôi có thể xếp hàng đợi hoặc lập lịch có điều kiện các sự kiện phụ thuộc mới, cho phép các mẫu fan-out/fan-in phức tạp và lập lịch dài hạn lên đến một năm. Lỗi và thành công tại bất kỳ điểm nào đều có thể được bắt, phân nhánh và xử lý tiếp theo trong quy trình làm việc.

Trừu tượng hóa hàm Inngest:

import { inngest } from 'inngest-client';

export const createMyFunction = (dependencies) => {
return inngest.createFunction(
{
id: 'my-function',
name: 'My Example Function',
retries: 3, // retry the entire run on failure
concurrency: { limit: 5 },
onFailure: async ({ event, error, step }) => {
// handle errors here
await step.run('handle-error', async () => {
console.error('Error processing event:', error);
});
},
},
{ event: 'my/event.triggered' },
async ({ event, step }) => {
const { payload } = event.data;

// Step 1: Define first step
const step1Result = await step.run('step-1', async () => {
// logic for step 1
return `Processed ${payload}`;
});

// Step 2: Define second step
const step2Result = await step.run('step-2', async () => {
// logic for step 2
return step1Result + ' -> step 2';
});

// Step N: Continue as needed
await step.run('final-step', async () => {
// finalization logic
console.log('Finished processing:', step2Result);
});

return { success: true };
},
);
};

Mô hình hướng sự kiện của Inngest cung cấp cái nhìn chi tiết về mọi thực thi quy trình làm việc:

  • Theo dõi sự kiện toàn diện: Mọi thực thi hàm được xếp hàng đều được ghi lại dựa trên sự kiện gốc của nó. Điều này cung cấp một dấu vết rõ ràng, cấp cao về tất cả các hoạt động liên quan đến một hành động người dùng duy nhất.
  • Chi tiết về lần chạy: Đối với mỗi lần thực thi hàm (cả thành công và thất bại), Inngest cung cấp nhật ký chi tiết thông qua báo cáo ack (xác nhận) và nack (xác nhận âm) của nó. Các nhật ký này bao gồm dấu vết ngăn xếp lỗi, tải trọng yêu cầu đầy đủ và tải trọng phản hồi đã tuần tự hóa cho mỗi step-run riêng lẻ.
  • Số liệu hoạt động: Ngoài nhật ký, chúng tôi đã có được các số liệu quan trọng về tình trạng hàm, bao gồm tỷ lệ thành công, tỷ lệ thất bại và số lần thử lại, cho phép chúng tôi liên tục giám sát độ tin cậy và độ trễ của quy trình làm việc phân tán của chúng tôi.

Xây dựng cho Khả năng phục hồi

Điểm cần lưu ý khi dựa vào xử lý sự kiện thuần túy là mặc dù Inngest xếp hàng đợi thực thi hàm một cách hiệu quả, bản thân các sự kiện không được xếp hàng đợi nội bộ theo nghĩa của một message broker truyền thống. Sự thiếu vắng của một hàng đợi sự kiện rõ ràng có thể gây ra vấn đề trong các tình huống lưu lượng cao do các điều kiện đua tiềm ẩn hoặc các sự kiện bị bỏ qua nếu điểm cuối tiếp nhận bị quá tải.

Để giải quyết vấn đề này và thực thi tính bền vững của sự kiện nghiêm ngặt, chúng tôi đã triển khai một hệ thống xếp hàng đợi chuyên dụng như một bộ đệm.

AWS Simple Queue System (SQS) là hệ thống được lựa chọn (mặc dù bất kỳ hệ thống xếp hàng đợi mạnh mẽ nào cũng có thể thực hiện được), với cơ sở hạ tầng hiện có của chúng tôi trên AWS. Chúng tôi đã thiết kế một hệ thống hai hàng đợi: một Hàng đợi chính và một Hàng đợi thư chết (DLQ).

Chúng tôi đã thiết lập một Môi trường Worker Elastic Beanstalk (EB) được cấu hình đặc biệt để tiêu thụ tin nhắn trực tiếp từ Hàng đợi chính. Nếu một tin nhắn trong Hàng đợi chính không được xử lý bởi EB Worker một số lần nhất định, Hàng đợi chính sẽ tự động chuyển tin nhắn thất bại đến DLQ chuyên dụng. Điều này đảm bảo không có sự kiện nào bị mất vĩnh viễn nếu nó không kích hoạt hoặc được Inngest nhận. Môi trường worker này khác với môi trường máy chủ web EB tiêu chuẩn, vì trách nhiệm duy nhất của nó là tiêu thụ và xử lý tin nhắn (trong trường hợp này, chuyển tiếp tin nhắn đã tiêu thụ đến điểm cuối API Inngest).

HIỂU GIỚI HẠN VÀ THÔNG SỐ KỸ THUẬT

Một phần quan trọng và khá liên quan của việc xây dựng cơ sở hạ tầng quy mô doanh nghiệp là nó tiêu thụ tài nguyên và chúng chạy lâu dài. Kiến trúc microservices cung cấp Khả năng mở rộng cho mỗi dịch vụ. Lưu trữ, RAM và thời gian chờ của tài nguyên sẽ đóng vai trò quan trọng. Thông số kỹ thuật của chúng tôi cho loại instance AWS, ví dụ, đã nhanh chóng chuyển từ t3.micro sang t3.small, và hiện đang được cố định ở t3.medium. Đối với các công việc nền chạy lâu dài, sử dụng nhiều CPU, việc mở rộng theo chiều ngang với các instance nhỏ sẽ thất bại vì nút thắt cổ chai là thời gian cần thiết để xử lý một công việc duy nhất, không phải khối lượng công việc mới đi vào hàng đợi.

Công việc hoặc hàm như chuyển mã, nhúng thường bị ràng buộc bởi CPUràng buộc bởi bộ nhớ. Ràng buộc bởi CPU vì chúng yêu cầu sử dụng CPU liên tục, mạnh mẽ, và ràng buộc bởi bộ nhớ vì chúng thường yêu cầu RAM đáng kể để tải các mô hình lớn hoặc xử lý các tệp hoặc tải trọng lớn một cách hiệu quả.

Cuối cùng, kiến trúc tăng cường này, đặt tính bền vững của SQS và việc thực thi có kiểm soát của môi trường Worker EB trực tiếp ở thượng nguồn của API Inngest, đã cung cấp khả năng phục hồi thiết yếu. Chúng tôi đã đạt được quyền sở hữu sự kiện nghiêm ngặt, loại bỏ các điều kiện đua trong các đợt tăng đột biến lưu lượng và có được cơ chế thư chết không dễ bay hơi. Chúng tôi đã tận dụng Inngest cho khả năng điều phối quy trình làm việc và gỡ lỗi của nó, trong khi dựa vào các nguyên tắc cơ bản của AWS để đạt được thông lượng tin nhắn và độ bền tối đa. Hệ thống kết quả không chỉ có Khả năng mở rộng mà còn có khả năng kiểm toán cao, chuyển đổi thành công các công việc backend phức tạp, chạy lâu dài thành các micro-steps an toàn, có thể quan sát được và chịu được lỗi.


Building Spotify for Sermons. được xuất bản lần đầu trong Coinmonks trên Medium, nơi mọi người đang tiếp tục cuộc trò chuyện bằng cách làm nổi bật và phản hồi câu chuyện này.

Tuyên bố miễn trừ trách nhiệm: Các bài viết được đăng lại trên trang này được lấy từ các nền tảng công khai và chỉ nhằm mục đích tham khảo. Các bài viết này không nhất thiết phản ánh quan điểm của MEXC. Mọi quyền sở hữu thuộc về tác giả gốc. Nếu bạn cho rằng bất kỳ nội dung nào vi phạm quyền của bên thứ ba, vui lòng liên hệ service@support.mexc.com để được gỡ bỏ. MEXC không đảm bảo về tính chính xác, đầy đủ hoặc kịp thời của các nội dung và không chịu trách nhiệm cho các hành động được thực hiện dựa trên thông tin cung cấp. Nội dung này không cấu thành lời khuyên tài chính, pháp lý hoặc chuyên môn khác, và cũng không được xem là khuyến nghị hoặc xác nhận từ MEXC.