การสร้างระบบขนาดใหญ่และงานพื้นหลังที่ทำงานเป็นเวลานาน
เครดิต: Ilias Chebbi บน Unsplashหลายเดือนก่อน ฉันได้รับบทบาทที่ต้องสร้างโครงสร้างพื้นฐานสำหรับการสตรีมมีเดีย (เสียง) แต่นอกเหนือจากการให้บริการเสียงเป็นชิ้นส่วนที่สตรีมได้แล้ว ยังมีงานประมวลผลสื่อที่ทำงานเป็นเวลานานและไปป์ไลน์ RAG ที่ครอบคลุมซึ่งรองรับการถอดเสียง การแปลงรหัส การฝังตัว และการอัปเดตสื่อตามลำดับ การสร้าง MVP ด้วยแนวคิดการผลิตทำให้เราต้องทำซ้ำจนกว่าเราจะได้ระบบที่ไร้รอยต่อ แนวทางของเราเป็นแนวทางที่เราได้ผสานคุณสมบัติและชุดลำดับความสำคัญพื้นฐาน
ในระหว่างการสร้าง การทำซ้ำแต่ละครั้งเกิดขึ้นเพื่อตอบสนองต่อความต้องการทันทีและมักจะ "ครอบคลุม" ความกังวลเริ่มแรกคือการจัดคิวงาน ซึ่งเพียงพอด้วย Redis เราเพียงแค่ยิงและลืม Bull MQ ในเฟรมเวิร์ก NEST JS ให้การควบคุมที่ดีขึ้นเกี่ยวกับการลองใหม่ งานค้าง และคิวจดหมายที่ตาย ในท้องถิ่นและด้วยเพย์โหลดไม่กี่รายการในการผลิต เราได้รับการไหลของสื่ออย่างถูกต้อง เราเริ่มถูกกดดันด้วยน้ำหนักของการสังเกตการณ์:
บันทึก → บันทึกของงาน (คำขอ, การตอบสนอง, ข้อผิดพลาด)
เมตริก → มากแค่ไหน / บ่อยแค่ไหนที่งานเหล่านี้ทำงาน, ล้มเหลว, เสร็จสมบูรณ์ ฯลฯ
การติดตาม → เส้นทางที่งานใช้ข้ามบริการ (ฟังก์ชัน/วิธีการที่เรียกใช้ภายในเส้นทางการไหล)
คุณสามารถแก้ไขบางส่วนของปัญหาเหล่านี้ได้โดยการออกแบบ API และสร้างแดชบอร์ดที่กำหนดเองเพื่อเชื่อมต่อเข้ากับระบบ แต่ปัญหาของการขยายขนาดจะเพียงพอ และจริงๆ แล้ว เราได้ออกแบบ API
ความท้าทายในการจัดการเวิร์กโฟลว์แบ็กเอนด์ที่ซับซ้อนและทำงานเป็นเวลานาน ซึ่งความล้มเหลวต้องสามารถกู้คืนได้ และสถานะต้องคงทน Inngest กลายเป็นทางรอดทางสถาปัตยกรรมของเรา มันได้ปรับเปลี่ยนแนวทางของเราอย่างพื้นฐาน: งานพื้นหลังที่ทำงานเป็นเวลานานแต่ละงานกลายเป็นฟังก์ชันพื้นหลังที่ทริกเกอร์โดยเหตุการณ์เฉพาะ
ตัวอย่างเช่น เหตุการณ์ Transcription.request จะทริกเกอร์ ฟังก์ชัน TranscribeAudio ฟังก์ชันนี้อาจมีการทำงานเป็นขั้นตอนสำหรับ: fetch_audio_metadata, deepgram_transcribe, parse_save_trasncription และ notify_user
พื้นฐานความคงทนหลักคือการทำงานเป็นขั้นตอน ฟังก์ชันพื้นหลังถูกแบ่งย่อยภายในเป็นการทำงานเป็นขั้นตอนเหล่านี้ แต่ละขั้นตอนมีบล็อกตรรกะที่เป็นอะตอมและขั้นต่ำ
ฟังก์ชัน 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 };
},
);
};
โมเดลที่ขับเคลื่อนด้วยเหตุการณ์ของ Inngest ให้ข้อมูลเชิงลึกอย่างละเอียดในทุกการดำเนินการเวิร์กโฟลว์:
ข้อควรระวังในการพึ่งพาการประมวลผลเหตุการณ์ล้วนๆ คือในขณะที่ Inngest จัดคิวการดำเนินการฟังก์ชันอย่างมีประสิทธิภาพ เหตุการณ์เองไม่ได้ถูกจัดคิวภายใน ในความหมายของนายหน้าข้อความแบบดั้งเดิม การไม่มีคิวเหตุการณ์ที่ชัดเจนนี้อาจเป็นปัญหาในสถานการณ์ที่มีการจราจรสูงเนื่องจากอาจเกิดเงื่อนไขการแข่งขันหรือเหตุการณ์ที่หายไปหากจุดสิ้นสุดการรับข้อมูลถูกโอเวอร์โหลด
เพื่อแก้ไขปัญหานี้และบังคับใช้ความคงทนของเหตุการณ์อย่างเข้มงวด เราได้ใช้ระบบการจัดคิวเฉพาะเป็นบัฟเฟอร์
AWS Simple Queue System (SQS) เป็นระบบที่เราเลือก (แม้ว่าระบบการจัดคิวที่แข็งแกร่งใดๆ ก็สามารถทำได้) เนื่องจากโครงสร้างพื้นฐานที่มีอยู่ของเราบน AWS เราได้ออกแบบระบบสองคิว: คิวหลัก และ คิวจดหมายที่ตาย (DLQ)
เราได้สร้างสภาพแวดล้อม Elastic Beanstalk (EB) Worker ที่กำหนดค่าเฉพาะเพื่อบริโภคข้อความโดยตรงจาก คิวหลัก หากข้อความในคิวหลักไม่สามารถประมวลผลโดย EB Worker ตามจำนวนครั้งที่กำหนด คิวหลักจะย้ายข้อความที่ล้มเหลวไปยัง DLQ โดยอัตโนมัติ ซึ่งทำให้มั่นใจได้ว่าไม่มีเหตุการณ์ใดสูญหายอย่างถาวรหากไม่สามารถทริกเกอร์หรือถูกเลือกโดย Inngest สภาพแวดล้อมเวิร์กเกอร์นี้แตกต่างจากสภาพแวดล้อมเว็บเซิร์ฟเวอร์ EB มาตรฐาน เนื่องจากความรับผิดชอบเพียงอย่างเดียวคือการบริโภคและประมวลผลข้อความ (ในกรณีนี้ คือการส่งต่อข้อความที่บริโภคไปยังจุดสิ้นสุด API ของ Inngest)
ส่วนที่ไม่ได้กล่าวถึงและค่อนข้างเกี่ยวข้องของการสร้างโครงสร้างพื้นฐานระดับองค์กรคือมันใช้ทรัพยากร และพวกมันทำงานเป็นเวลานาน สถาปัตยกรรมไมโครเซอร์วิสให้ความสามารถในการขยายขนาดต่อบริการ การจัดเก็บ RAM และการหมดเวลาของทรัพยากรจะเข้ามามีบทบาท ข้อกำหนดของเราสำหรับประเภทอินสแตนซ์ AWS ตัวอย่างเช่น เปลี่ยนอย่างรวดเร็วจาก t3.micro เป็น t3.small และตอนนี้ถูกกำหนดที่ t3.medium สำหรับงานพื้นหลังที่ทำงานเป็นเวลานานและใช้ CPU อย่างเข้มข้น การขยายขนาดแนวนอนด้วยอินสแตนซ์ขนาดเล็กจะล้มเหลวเพราะคอขวดคือเวลาที่ใช้ในการประมวลผลงานเดียว ไม่ใช่ปริมาณของงานใหม่ที่เข้าสู่คิว
งาน หรือ ฟังก์ชัน เช่น การแปลงรหัส การฝังตัว มักจะถูก จำกัดด้วย CPU และ จำกัดด้วยหน่วยความจำ จำกัดด้วย CPU เพราะต้องการการใช้ CPU อย่างเข้มข้นและต่อเนื่อง และ จำกัดด้วยหน่วยความจำ เพราะมักต้องการ RAM จำนวนมากเพื่อโหลดโมเดลขนาดใหญ่หรือจัดการไฟล์หรือเพย์โหลดขนาดใหญ่อย่างมีประสิทธิภาพ
ในที่สุด สถาปัตยกรรมที่เพิ่มขึ้นนี้ ซึ่งวางความคงทนของ SQS และการดำเนินการควบคุมของสภาพแวดล้อม EB Worker โดยตรงต้นน้ำของ API Inngest ให้ความยืดหยุ่นที่จำเป็น เราได้บรรลุความเป็นเจ้าของเหตุการณ์อย่างเข้มงวด กำจัดเงื่อนไขการแข่งขันระหว่างการเพิ่มขึ้นของการจราจร และได้รับกลไกจดหมายที่ตายที่ไม่ระเหย เราใช้ประโยชน์จาก Inngest สำหรับความสามารถในการจัดการเวิร์กโฟลว์และการดีบัก ในขณะที่พึ่งพาพื้นฐานของ AWS สำหรับปริมาณข้อความสูงสุดและความคงทน ระบบที่เกิดขึ้นไม่เพียงแต่ขยายขนาดได้แต่ยังตรวจสอบได้สูง ซึ่งแปลงานพื้นหลังแบ็กเอนด์ที่ซับซ้อนและทำงานเป็นเวลานานให้เป็นไมโครสเต็ปที่ปลอดภัย สังเกตได้ และทนต่อความล้มเหลว
การสร้าง Spotify สำหรับเทศนา เผยแพร่ครั้งแรกใน Coinmonks บน Medium ซึ่งผู้คนกำลังสานต่อการสนทนาโดยการไฮไลต์และตอบสนองต่อเรื่องราวนี้


