برنامه نویسی

وظایف طولانی مدت با Next.js: داستانی از اختراع مجدد چرخ

یک هدف ساده

من در حال ساختن یک برنامه بک آفیس با استفاده از Next.js هستم که نیاز به انجام وظایف اتوماسیون مختلف، مانند تولید فایل های XML غول پیکر، اجرای آزمایش های منظم بر روی ویژگی های اصلی، و مدیریت چند خزنده Puppeteer دارد. برخی از کارها کمتر از یک دقیقه طول می کشد، برخی دیگر حدود پنج دقیقه، در حالی که خزنده ها می توانند زمان اجرا نامحدود یا حتی بی نهایت داشته باشند. من به یک راه حل نیاز داشتم تا این وظایف را به صورت موازی کنترل کنم و به روز رسانی های آنها را به دفتر پشتیبان (BO) ارسال کنم.

چرخ را دوباره اختراع نکنید، فقط محبوب ترین و پرکاربردترین راه حل منبع باز را انتخاب کنید و به تلاش جمعی افرادی که صدها ساعت برای تکمیل آن صرف کرده اند تکیه کنید. منطقی به نظر می رسد، درست است؟ موافقم و معمولا همه چیز اینطور شروع می شود. با این حال، بیشتر اوقات، من با رویکردی بدوی و ناقص مواجه می‌شوم که از پروژه‌ای به پروژه دیگر انجام می‌دهم. من مطمئن نیستم که آیا این معنای کلمه تجربه است یا فقط عادت های بد را جمع می کنم، اما در هر صورت، این روش های مورد علاقه من برای یادگیری است.

Trigger.dev: پلتفرم مشاغل پس زمینه منبع باز

فوق العاده است، فکر کردم. در عرض چند روز، موفق شدم آن را روی VPS خود بچرخانم و یک داشبورد جداگانه داشتم که نمودار گانت را با تمام کارهای در حال اجرا و تکمیل شده نمایش می داد. با این حال، هنگام نمونه سازی راه حل، با مشکلی در جریان داده ها به دفتر پشتی (BO) مواجه شدم. انجمن Discord آنها حمایت می کرد و شخصی اشاره کرد که این مشکل قبلا گزارش شده بود و اکنون باید برطرف شود. متأسفانه، پس از به روز رسانی به آخرین نسخه، هنوز نتوانستم در جریان ورود به سیستم مشترک شوم.

پس از روزها عیب یابی، به این فکر کردم که ممکن است ابزار اشتباهی را انتخاب کرده باشم. شاید Trigger.dev در محیط ابری خود بی عیب و نقص کار کند، اما من می خواستم کل زیرساخت خود را به چند VPS محدود کنم. در نهایت، من تصمیم گرفتم که اگرچه Trigger.dev یک راه حل قدرتمند و پیچیده است، اما به همه ویژگی های آن نیازی ندارم. راه حل ساده تر و سفارشی ساخته شده در بالای Next.js مناسب تر خواهد بود

BullMQ: کتابخانه صف وظایف

من راه حل را به عنوان یک صف کار در نظر گرفتم، بنابراین BullMQ، یک کتابخانه Node.js برای مدیریت صف های پیام را بررسی کردم. من یک نمونه redis را مستقر کردم و نمونه سازی را شروع کردم. به زودی متوجه شدم که مشاغل را نمی توان با قفل شدن توسط یک کارگر متوقف کرد. پس از چند روز آزمایش با صف‌های رویداد BullMQ، کارگران و مشاغل، مشخص شد که BullMQ برای لغو کار یا پخش پیام‌ها در زمان واقعی طراحی نشده است.

نه، من نیاز به اختراع چرخ خاصی دارم، بنابراین دیگر شخص ثالثی وجود ندارد

تصمیم گرفتم به طور کامل نیازهای خود را تجدید نظر کنم و یک راه حل ساده تر و 100٪ سفارشی را انتخاب کردم. در ابتدا، Web Workers را در نظر گرفتم که برای بارگذاری وظایفی مانند تولید فایل‌های XML بزرگ در مرورگر عالی هستند. با این حال، ایده اجرای Puppeteer (یک مرورگر بدون سر) در داخل یک Web Worker ناخوشایند بود. Web Workers به ​​چرخه حیات صفحه ای که آنها را ایجاد کرده است گره خورده اند. اگر صفحه بازخوانی یا حذف شود، کارگر خاتمه می یابد و وضعیت یا ارتباط آن از بین می رود. این محدودیت Web Workers را برای کارهای طولانی مدت یا مدت نامحدود نامناسب کرد.

ایجاد پردازش‌ها یا فورک‌های فرزند در Node.js به من این امکان را می‌دهد که وظایف را به صورت موازی اجرا کنم و در عین حال کنترل را حفظ کنم تا آنها را در هر زمان متوقف یا متوقف کنم. علاوه بر این، فرآیندهای فرزند می توانند از process.send() برای تبادل اشیاء پیام با فرآیند والد استفاده کنند که به نظر رویکرد درستی است. با این تنظیم، هدف به ایجاد یک تبادل پیام دوطرفه قابل اعتماد بین پردازش های فرزند و مرورگر محدود شد.

وب سوکت ها

اولین روش ارتباطی که به ذهنم رسید WebSockets بود. من نمی خواستم سرور دیگری را به غیر از سرور ارائه شده توسط Next.js حفظ کنم، بنابراین سعی کردم wss:// را روی https:// Next.js اجرا کنم. با این حال، ادغام سرورهای WebSocket در یک برنامه Next.js چالش برانگیزتر از آن چیزی بود که من پیش بینی می کردم. در اینجا مهمترین مسائلی است که من با آن مواجه شدم:

راه اندازی قلاب ابزار دقیق:

من سعی کردم سرور WebSocket را در هنگام راه اندازی سرور با استفاده از قلاب ابزار دقیق (register) مقداردهی کنم. در حالی که در نظر گرفته شده است که هنگام راه اندازی سرور اجرا شود، بسته به محیط میزبانی (مثلاً Vercel، Docker یا یک سرور مستقل Node.js) تفاوت های ظریفی وجود دارد. مشکل این بود که نمونه سرور HTTP (global.server) بلافاصله در این محیط ها در دسترس نبود. این امر اتصال سرور WebSocket را به سرور HTTP موجود در زمان مناسب غیرممکن کرد.

دسترسی به سرور HTTP Next.js:

Next.js سرور HTTP داخلی خود را مستقیماً نشان نمی دهد، که مانع از اتصال آسان سرور WebSocket به آن می شود. در حالی که از نظر فنی امکان چرخش یک سرور Node.js جداگانه برای WebSockets وجود دارد، این یک راه‌حل بی‌معنا و بی‌ظرافت به نظر می‌رسد که پیچیدگی غیر ضروری را به پروژه اضافه می‌کند.

سازگاری ارائه دهنده ابر:

اجرای یک سرور WebSocket سفارشی سازگاری با پلتفرم‌های ابری مانند Vercel را کاهش می‌دهد که از اتصالات پایدار و طولانی مدت WebSocket در عملکردهای بدون سرور خود پشتیبانی نمی‌کنند. به عنوان مثال، ورسل استفاده از سرویس‌های خارجی مانند Ably یا Pusher را برای پیام‌رسانی بلادرنگ توصیه می‌کند، و اگر می‌خواهم در گزینه‌های استقرار انعطاف‌پذیری داشته باشم، این رویکرد کمتر مطلوب است.

سرور Singleton WebSocket در مسیرهای API Next.js

سپس سعی کردم یک سرور WebSocket تک تنی را مستقیماً در یک مسیر API Next.js مقداردهی کنم و اطمینان حاصل کنم که به چرخه حیات درخواست-پاسخ گره خورده است. در حالی که این رویکرد به من اجازه داد برخی از مشکلات قبلی را دور بزنم، اما هنوز ایده‌آل نبود: طبیعت بدون سرور Next.js به این معنی است که برای حفظ اتصالات طولانی مدت مانند WebSockets به صورت بومی طراحی نشده است. این رویکرد سازگاری با ارائه دهندگان ابر را کاهش داد (حتی اگر اکنون آنها را هدف قرار نمی دادم، محدود کردن گزینه ها مانند یک استراتژی بد به نظر می رسید). ایجاد یک سرور Node.js جداگانه برای اجرای WebSockets در کنار Next.js می‌تواند کارساز باشد، اما راه‌حلی بی‌ظرافت و بیش از حد پیچیده برای نیازهای من بود.

چرا نظرسنجی طولانی نیست؟

من به طور خلاصه با نظرسنجی طولانی به عنوان ساده ترین جایگزین آزمایش کردم. با این حال، راه‌اندازی فرآیندهای فرزند از BO و اشتراک در گزارش‌های چندین فرآیند به طور همزمان منجر به سقوط قابل‌توجه FPC و یخ‌های جزئی شد. این با این واقعیت تشدید شد که رابط BO من همچنین یک شبکه به شدت مجازی سازی شده با بیش از 300000 ردیف و بارگذاری بی‌نهایت را ارائه می‌کرد که بر واکشی و فیلتر کردن داده‌ها از باطن متکی بود. افزودن نظرسنجی طولانی مدت باعث بدتر شدن مشکل بالقوه می شود. بنابراین من در نظر گرفتم که در این مورد آن را برای ارتباط بلادرنگ غیر عملی کنم.

رویدادهای ارسال شده توسط سرور: راه حل نهایی (در نهایت)

سرانجام، رویدادهای ارسال شده از سرور (SSE MDN Reference) را کشف کردم که دقیقاً همان چیزی بود که من به آن نیاز داشتم. SSE به سرور اجازه می‌دهد تا به‌روزرسانی‌های بلادرنگ را برای مشتری از طریق یک اتصال HTTP پخش کند، که آن را برای موارد استفاده من عالی می‌کند.

راه حل با سه جزء ساده ساخته شده است:

  1. Process Manager: کلاسی که فرآیندهای فرزند را ایجاد و کنترل می کند. اجرای کار، مکث، سقط و تبادل پیام را مدیریت می کند.
  2. مسیر API: next.js route /api/[task-name] با: – GET: یک جریان متنی بی‌درنگ از به‌روزرسانی‌ها را از کار پخش می‌کند. – POST: کار را شروع یا متوقف می کند. – DELETE: کار را لغو می کند.
  3. React hook: این قلاب از EventSource API برای مصرف به‌روزرسانی‌های بی‌درنگ سرور استفاده می‌کند و نقاط پایانی REST برای کنترل چرخه عمر کار (شروع، توقف، توقف) استفاده می‌شود.

کلنگ جمع شده

  1. کارگران وب
    آنها برای اجرای محاسبات موازی در مرورگر عالی هستند، مانند تولید xml، اسکریپت های شخص ثالث که بارگذاری صفحه بارگذاری را به هم متصل می کنند، تجزیه داده ها. با این حال، آنها به شدت به چرخه حیات صفحه ای که آنها را ایجاد می کند گره خورده اند. برای کارهای طولانی مدت یا سمت سرور، آنها به سادگی مناسب نیستند.

  2. Trigger.dev: این یک راه حل فوق العاده برای مدیریت مشاغل پس زمینه در سیستم های توزیع شده است. اگر من یک پلتفرم هماهنگ‌سازی وظایف قوی و بومی ابری با تلاش‌های مجدد، نظارت و داشبورد می‌خواستم، Trigger.dev ایده‌آل بود. با این حال، اشکال ممکن است به زودی برطرف شود، ناتوانی آن در استریم بومی گزارش های بلادرنگ به دفتر پشتی من، آن را برای نیازهای من نامناسب کرده است. اگر جریان‌های کاری را اجرا می‌کنید که نیازی به بازخورد بی‌درنگ برای UI شما ندارند، این یک انتخاب عالی است.

  3. BullMQ: در مدیریت صف ها برای کارهایی مانند پردازش دسته های بزرگ، بازاریابی ایمیلی، هر دنباله بازاریابی دیگر، اجرای کارهای تاخیری یا برنامه ریزی شده با cron عالی است. برای لغو مشاغل فعال یا پخش به‌روزرسانی‌های هم‌زمان در قسمت جلویی طراحی نشده است.
    شاید بهتر است از آن برای مدیریت ویژگی‌های بازاریابی مانند قیف‌های فروش استفاده کنم.

  4. WebSockets: نگهداری از سرورهای WebSocket در کنار یک برنامه Next.js، به ویژه در محیط های ابری، دست و پا گیر است و اغلب برای نیازهای پخش ساده تر، بیش از حد نیاز است.

پس از امتحان همه اینها، رویدادهای ارسال شده از سرور (SSE) را کشف کردم – یک راه حل ساده تر و ظریف برای مشکل من. این به من اجازه داد تا «چرخ» را دوباره اختراع کنم، و یک سیستم مدیریت کار با به‌روزرسانی‌های جریانی در زمان واقعی از فرآیندهای فرزند ایجاد کنم. اگرچه این شیک ترین ابزار موجود در جعبه نیست، اما برای نیازهای خاص من طراحی شده است و من از نتایج راضی هستم.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا