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 UnsplashVà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.
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.
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.
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.
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:
Đ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).
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 CPU và rà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.


