اول از همه در مورد مفهوم watchdog صحبت کنیم که اصلا چی هست؟
این مفهوم که بیشتر در سخت افزار و برنامه های کامپیوتری mission critical استفاده میشه، کارش اینه که مدام وضعیت healthy یک موجودیت دیگه رو بررسی کنه، و به محض تشخیص معیوب بودن اون سخت افزار یا نرم افزار، مجموعه فرمان های recovery یا alert رو اجرا کنه.
رایج ترین نوع recovery الگوریتم ostrich هست که همونطور از نامش پیداست مثل شترمرغ از روی مسئله پیش آمده میپره و بیخیال مشکل میشه و سیستم هدف رو restart میکنه :)
برگردیم به مسئله خودمون و پیاده سازی این مفهوم
صورت مسئله
می خواهیم یک برنامه single instance داشته باشیم که با selenium یک دستور رو برای crawl کردن تو یک سایتی اجرا کنه، و نکته ای که هست به دلیل وجود rate limit و bot detector توی اون وب سایت باید همه درخواست ها queue بشن و در هر لحظه فقط یک درخواست اجرا بشه و نکته دوم اینه که این سیستم روی ویندوز باید هاست بشه.
بدیهیه که با ویندوزی شدن ماجرا کلی از ابزارهای اماده این کار رو از دست میدیم و البته که همیشه هم عجله داریم و باید در کمترین زمان ممکن این کار انجام بشه.
الان با چالش های مختلفی رو به رو میشیم که باید همه اونها رو با کمترین ریسورس، که اینجا زمانه حل کنیم.
اولین چالش معماری سیستم هست، که crawler و watchdog هر کدوم کجای سیستم باید قرار بگیرن و اصلا health monitoring به چه صورتی انجام بشه؟
رایج ترین نوع health check (از این به بعد hc میگیم) به این صورته که یک port مجزا روی برنامه ای که قراره مانیتورینگ روش انجام بشه به بیرون expose میشه و برنامه monitoring که کاملا مستقل اجرا شده این port رو با روش pulling بررسی میکنه و اگر جوابی که انتظار داره رو نگیره (خود برنامه بگه وضعیت خوب نیست یا هیچ جوابی در مدت زمان مشخص نده) وضعیت DOWN به alert manager اعلام میکنه (مرسومه که در زبان hc وضعیت رو به صورت UP و DOWN اعلام میکنن)
خب این روش یه مشکل داره و اون اینه که باید در برنامه crawler کد hc نوشته بشه و crawler باید بتونه تو این مورد ما، وضعیت سلامت خودشو نگه داره، حالا چون ممکنه برنامه restart هم بشه پس باید اون رو جایی persist کنه.
خب تا همینجا کافیه که تشخیص بدیم این روش کلی پیچیدگی داره و کد زیادی باید براش بنویسیم.
از اون طرف ما از third party مثل selenium استفاده کردیم که خودش وابسته به browser و فایل اجرایی selenium هست و عملا به دلایل مختلفی هر بخش اون ممکنه crash کنه، ولی watchdog یک برنامه بسیار سبکه که تقریبا مطمئنیم که up-time بالایی داره. بنابراین تصمیم بر این شد خود watchdog مسئول تشخیص سلامت crawler بشه بدون اینکه که crawler اصلا خبر دار بشه که watch dogِی وجود داره.
برای این کار ما اومدیم watchdog رو تبدیل به یک reverse proxy کردیم و با عبور دادن همه درخواست ها از اون، هم تمامی response time نگهداری کردیم و هم تمام منطق hc رو توی اون پیاده سازی کردیم و اجازه دادیم crawler فقط روی منطق خودش تمرکز کنه.
کدی که نوشتیم این منطق رو پیاده میکنه که اگر در 5 دقیقه گذشته تعداد درخواست هایی که به خطا یا timeout خورده بیشتر از درخواست های موفقیت آمیز بشه به شرطی که از اخرین restart کردن سیستم بیشتر از 5 دقیقه گذشته باشد. فرمان restart را به crawler ارسال کنه.
نکته مهمی که اینجا وجود داره استفاده از Content.CopyToAsync هست که باعث میشه بدون materialize کردن جواب صرفا stream خونده شده مستقیم به خروجی فرستاده بشه و روی سرعت تاثیر چشم گیری داره.
و در اینجا اجرای فرمان restart رو به خود سیستم عامل واگذار کردیم که از طریق دستورات CLI اون رو اجرا میکنه و ما کافیه فقط اون فایل .cmd رو که ساختیم توسط watchdog اجرا کنیم (تابع Restart).
حالا برای اینکه خود watchdog رو مطمئن بشیم همیشه در حال اجراست میتونیم استراتژی های مختلفی داشته باشیم ولی در اینجا چون با هر رکوست کلاینت، trigger میشه و اگر خطایی هم رخ بده فقط همون رکوست رو تحت تاثیر قرار میده بنابراین میتونیم این بخش رو بیخیال بشیم و تمرکز رو بذاریم روی up بودن خود اپلیکیشن watchdog.
برای این منظور میتونیم برنامه رو به صورت windows service در ویندوز نصب کنیم و recovery mode برای اون مشخص کنیم که در صورت crash کردن برنامه رو restart کنه یا اون رو در IIS هاست کنیم، ولی خب با این روش ما output console رو از دست میدیم و باید لاگ ها رو جایی persist کنیم تا بتونیم بعدا بخونیم.
تو این مورد این خیلی مناسب کیس ما نیست پس تصمیم گرفتیم که هم output console رو داشته باشیم که بتونیم در لحظه عمل debug رو انجام بدیم و هم مکانیزم بازیابی خودکار در صورتی که crash کنه رو داشته باشیم. که البته این روش مشکلاتی رو هم داره مانند اینکه که حتما باید کاربر لاگین شده داشته باشیم و اون session براش باز بمونه ولی تو این کیس ما، این حالت مناسب بود.
پس یک فایل اسکریپت ویندوز ساختیم با محتویات زیر که در صورتی که برنامه watchdog کرش کنه و خارج بشه چون توی یک loop هست دوباره به صورت خودکار اجرا میشه
چالش بعدی که داشتیم این بود که درخواست ها همزمان اجرا نشن و سریالی این اتفاق بیفته خب برای این کار هم روش های خیلی زیادی داشتیم که هزینه بر بود به همین منظور خیلی ساده اومدیم از ویژگی lock در برنامه crawler استفاده کردیم و اون بخشی که قراره درخواست رو در وب سایت اجرا کنه رو به صورت critical section نوشتیم و با این روش صف رو پیاده سازی کردیم.
همچنین برای پیاده سازی صف زماندار اومدیم از cancellation token استفاده کردیم که داخل critical section وضعیت منقضی شدنش چک میشه که در صورتی که منقضی شده باشه ادامه درخواست پردازش نمیشه.
اینجا دیگه کار watchdog ما تموم شد و میتونیم اجراش کنیم و مطمئن باشیم که حواسش به همه چی هست :)