Node.js تمرکز خود را برای ساخت نرم افزارهای با کارایی بالا گذاشته است . در این بخش می خواهیم در مورد مشکل مقیاس I/O صبحت کنیم . سپس راه حل این مشکل را بصورت سنتی بررسی کرده و سپس راه حل آن را توسط Node.js بررسی خواهیم کرد .
بخش 7 – Node.JS ، درک عملکرد Node.js
Node.js تمرکز خود را برای ساخت نرم افزارهای با کارایی بالا گذاشته است . در این بخش می خواهیم در مورد مشکل مقیاس I/O صبحت کنیم . سپس راه حل این مشکل را بصورت سنتی بررسی کرده و سپس راه حل آن را توسط Node.js بررسی خواهیم کرد .
مشکل بدست آوردن مقیاس در I/O
بگذارید تا نگاهی به تخمین زمان تقریبی دسترسی به داده ها از منابع مختلف در چرخه ی شرایط CPU بیاندازیم.
شما بوضوح می توانید ببینید که دسترسی به داده ی دیسک و شبکه کاملن با دسترسی داده توسط RAM و کَش CPU متفاوت می باشد .
اکثر نرم افزارها نیاز دارند تا داده ها را از منبع دیگری مانند دیسک خواند و یا از طریق شبکه به آن دسترسی داشته باشند ( بطور مثال یک کوئری دیتابیس ) . هنگامی که یک تقاضای HTTP دریافت می گردد و ما نیاز به بارگزاری داده از دیتابیس داریم ، معمولن این تقاضا نیاز به انتظار برای خواندن داده از دیسک و یا دریافت آن از شبکه خواهد داشت تا فراخوانی کامل گردد .
این زمان انتظار و همچنین اتصال باز ، منابع سرور ( حافظه و CPU ) را مصرف خواهد کرد . بنابراین بمنظور رسیدگی به تعداد زیادی درخواست از کلاینت های مختلف استفاده از این مدل وب سرور ، ما را با مشکل مقیاس پذیری I/O روبرو خواهد نمود .
روش سنتی
در سرورهای سنتی ، برای رسیدگی به هر تقاضا یک پردازش جدید ایجاد خواهد شد . ایجاد یک پردازش برای هر درخواست عملی بسیار گران قیمت هم از نظر CPU و هم RAM می باشد . نمایش این مفهوم را در شکل زیر مشاهده می نمایید . براساس آن برای پاسخ به درخواست HTTP A نیاز به داده های دیتابیس داریم . این خواندن اطلاعات ممکن است بصورت بالقوه زمان زیادی را بگیرد . برای این عمل ما نیاز داریم تا در هنگام خواندن داده از دیتابیس CPU و RAM را گرفته تا پاسخ دیتابیس برسد . بنابراین پردازش کند بوده و نیاز به سربار زیادی روی RAM خواهیم داشت . اینکار بسیار پرهزینه و طولانی بوده و در واقع دلیلی است بر اینکه نرم افزارهای جدید و مدرن از thread pool استفاده می کنند .
سرورهایی با استفاده از Thread Pool
سرورهای مدرن از یک thread که از thread pool می آید برای پاسخ به هر تقاضا استفاده می کنند . بنابراین ما سیستم عامل هایی داریم که thread ها را می سازند . هنگامی که درخواستی می آید ما یک Thread را به آن برای پاسخگویی اختصاصی می دهیم . و این thread برای این درخواست تا زمان پاسخگویی رزرو خواهد شد .
از آنجایی که ما سربار ساخت پردازش جدید را در هر بار از بین بردیم و thread ها از process ها سبک تر می باشند ، این روش بسیار بهتر از روش قبلی خواهد بود . از سال های پیش خیلی از وب سرورها از این روش استفاده کرده و در حال حاضر خیلی ها این راه را ادامه داده اند . بهرحال این روش هم عاری از مشکلات نیست . مجددن در این بین اتلاف RAM را در میان thread ها خواهیم داشت . در ضمن سیستم عامل نیاز دارد تعویض متنی مابین thread ها در هنگامی که بیکار هستند داشته باشد و در نتیجه در منابع CPU نیز مشکلاتی خواهیم داشت .
روش Nginx
ما دیدیم که ایجاد پردازش های جداگانه و thread های مجزا برای پاسخگویی به درخواست ها منابع سیستم عامل را اتلاف خواهد کرد . اما در Node.js ما برای پاسخگویی به درخواست ها تنها از یک thread منفرد استفاده خواهیم کرد . ایده ی Thread منفرد که می تواند بهتر از thread pool عمل کند یک ایده ی جدید از Node.js نیست . در اصل این ایده ساخته Nginx می باشد .
Nginx یک وب سرور با thread منفرد می باشد و می تواند درخواست های عظیمی که بصورت همزمان وارد می شوند را بررسی نماید .
مقایسه ای بین NGinx و Apache در تصویر زیر انجام شده که در واقع پاسخگویی هر دو به یک فایل استاتیک تکی می باشد .
همانطور که مشاهده می نمایید ، هنگامی که درخواست های همزمان زیاد می شود ، Nginx می تواند درخواست های همزمان بیشتری را در ثانیه نسبت به Apache بررسی نماید . و اما نکته ی جالب تر در مصرف حافظه در این دو مثال هست که در زیر مشاهده می نمایید :
با ورود درخواست های همزمان بیشتر Apache مجبور به اجرای thread های بیشتر بوده و بنابراین نیاز به حافظه ی بیشتری خواهد داشت ، اما همانگونه که مشاهده می نمایید Nginx بصورت پایدار در یک سطح باقیمانده است.
راز عملکرد Node.js
ایجا یک اجرای منفرد thread را در جاوا اسکریپت داریم . راهی که مرورگرهای وب بصورت سنتی عمل می کنند این است که اگر شما یک عمل با اجرای طولانی داشته باشید ( مانند timer انتظار برای اتمام و بازگشت یک کوئری از دیتابیس ) شما باید عملیات را با استفاده از یک callback ادامه دهید . در ادامه مثالی را بررسی می نماییم که در آن جاوا اسکریپت تابع setTimeout شبیه سازی یک عملیات طولانی را انجام خواهد داد . شما می توانید این کد را با استفاده از Node.js طبق آموزش های قبل اجرا نمایید .
این شبیه سازی در جاوا اسکریپت امکان پذیر است چرا که ما ابتدا یک first-class را تعریف کردیم و سپس یک تابع دیگر را به آن پاس دادیم . جذابیت کار آنجاست که شما یک first-class را با یک مفهوم closure ادغام نمودید . تصور کنید که ما درخواستی را که در واقع یک عمل طولانی مانند کوئری دیتابیس می باشد را ارسال نمودیم .
بدلیل استفاده از closure ، ما به درخواست کاربر صحیح بعد از اجرای عملیات طولانی دسترسی خواهیم داشت . تنها باید دو درخواست را در یک thread منفرد بررسی کنیم بدون اینکه سختی بکشیم . حال شما باید معنای این جمله را که گفتیم : ( Node.js عملکرد و کارایی بالایی دارد و از جاوا اسکریپت استفاده می نماید زیرا جاوا اسکریپت از توابع first-class و closure ها پشتیبانی می نماید) را فهمیده باشید .
سوالی که فورا ذهن شما را با خودش مشغول خواهد کرد این است که زمانی که شخصی به شما می گوید شما تنها یک thread منفرد برای بررسی درخواست دارید ، اما CPU من دارای چند هسته می باشد ! و استفاده از تنها یک thread باعث به هدر رفتن منابع می شود . و جواب این سوال ذهن شما این می شود که بله به هدر میرود !
اما در آینده در مورد توسعه و مقیاس پذیری سخن خواهیم گفت . در واقع بسیار ساده خواهد بود که ما از تمام هسته های CPU با استفاده از پردازش های جاوا اسکریپتی مجزا برای هر کدام از هسته ها توسط Node.js استفاده کنیم . این مهم است که بدانید مدیریت Thread ها در Node.js در سطح C وجود دارد اما تمامی اجراهای جاوا اسکریپت در یک thread منفرد خواهد بود .
شاد و سلامت باشید
محمد جعفری فوتمی