لود بالانس | Nginx from stratch
11 روز پیش
با لود بالانس آشنا میشیم و یاد میگیریم چطور از Nginx به عنوان لود بالانسر استفاده کنیم.
لود بالانس (Load Balancing) به معنی توزیعِ ترافیکِ ورودی (برای مثال درخواستهای کاربران) بین چندین سرور است تا هیچ سروری بیش از حد زیر فشار قرار نگیرد و کارایی سیستم حفظ شود.
توزیع ترافیک بین چندین سرور باعث افزایش در دسترس بودن و همچنین مقیاس پذیری بیشتر سرویس های ما میشود.
افزایش در دسترس بودن (High Availability): اگر یکی از سرورها از کار بیفتد، درخواستها به سرورهای دیگر هدایت میشوند.
افزایش مقیاسپذیری (Scalability): میتوان با اضافه کردن سرورهای جدید به سادگی به حجم بیشتری از درخواستها پاسخ داد.
ابزارهای مختلفی برای لود بالانس (توزیع ترافیک) وجود دارد که یکی از رایجترین آنها nginx است. با استفاده از nginx میتوان ترافیک HTTP, TCP و حتی UDP را لود بالانس کرد.
لود بالانسینگ درخواست های HTTP
در قسمت های قبل گفتیم که فایل /etc/nginx/nginx.conf
جاییه که nginx از آنجا باقی تنظیمات رو لود میکنه. بیاید یه نگاهی به این فایل بندازیم:
میتونیم ببینیم که یک بلاک http
وجود دارد. بلاک http تنظیمات مرتبط با درخواست های http را انجام میدهد. در آخرین خط این بلاک با استفاده از دستور include
تمامی فایل هایی که دارای پسوند conf
باشند را به عنوان تنظیمات از مسیر /etc/nginx/conf.d/
لود میکند. این یعنی هر فایلی که با پسوند conf در مسیر /etc/nginx/conf.d/
قرار دهیم, به عنوان تنظیماتی برای درخواست های http مورد استفاده قرار میگیرد.
با استفاده از دانشی که تا اینجا کسب کردیم، میتوانیم یک لود بالانسر با nginx پیادهسازی کنیم تا درخواستها را بین چندین سرور توزیع کند. فرض کنید چند نسخه فعال از یک اپلیکیشن در حال اجرا هستند و قصد داریم ترافیک ورودی را بین آنها لود بالانس کنیم. برای این کار، میتوان سه سرور را شبیهسازی کرده و از یک nginx به عنوان لود بالانسر برای مدیریت و توزیع ترافیک استفاده کرد.
برای شبیه سازی , با استفاده از خود nginx سه تا سرور ایجاد کنیم. برای اینکار میتوانید یک فایل با نام mock-server.conf
در مسیر /etc/nginx/conf.d/
ایجاد کنید و تنظیمات زیر را درون آن قرار دهید:
با استفاده از دستورات بالا سه سرور nginx ایجاد کردیم که به ترتیب روی پورت های 8080
, 8081
, 8082
به درخواست های دریافتی پاسخ میدهند. با استفاده از هشتک یا #
میتوان توضیحات (comment) گذاشت. کامنت یا توضیحات برای توسعه دهنده ها گذاشته میشود تا بعدا با خواندن آنها, تنظیمات نوشته شده را بهتر درک کنند.
حالا یک لود بالانسر ایجاد میکنیم و آن را طوری تنظیم میکنیم که درخواست ها را بین آنها توزیع کند (من فایل تنظیماتم را load-balancer.conf
نامگذاری میکنم.):
در این مثال، با استفاده از upstream
گروهی از سرورها با نام backend01
تعریف میکنیم. آدرس سرورها را با کلمه کلیدی server
مشخص کرده و با استفاده از weight
تعیین میکنیم که بار ترافیک به چه نسبتی میان آنها توزیع شود.
در بلاک upstream در هنگام تعریف server ها میتوان از IP و یا URL برای مشخص کردن آدرس مورد نظر استفاده کرد.
در مثال بالا weight برای همه سرورهای مشخص شده برابر است بدین صورت درخواست ها به صورت یکسان بین آنها توزیع میشود. در صورتی که weight
زیاد تر شود درخواست ها به همان میزان نیز بر روی یک سرور بیشتر توزیع میشوند.
در ادامه یک بلاک server
ایجاد کردهایم که روی پورت 8015
تمامی درخواستهای ورودی را دریافت میکند. اگر آدرس درخواست با مسیر تعریفشده در بخش location
مطابقت داشته باشد، درخواست به سرورهای گروه backend01
پروکسی میشود. این انتقال با استفاده از دستور proxy_pass
انجام میشود.
برای تطبیق تمامی درخواستها از علامت /
در ابتدای بخش location
استفاده میکنیم. در این حالت، اگر یکی از آدرسهای localhost:8015
یا 127.0.0.1:8015
را در مرورگر باز کنیم، با هر بار ارسال درخواست، به یکی از سه سروری که در ابتدا تنظیم کردهایم متصل میشویم و پاسخ از همان سرور برای ما ارسال میشود.
در برخی موارد ممکن است یکی از سرورها توان پردازشی بالاتری داشته باشد و قادر باشد به درخواستهای بیشتری پاسخ دهد. در این حالت میتوان با افزایش مقدار weight
در بلاک upstream
، سهم بیشتری از ترافیک را به آن سرور اختصاص داد.
لود بالانس TCP و UDP
برای لود بالانس TCP و UDP بلاک های upstream
و server
را همانند قبل میتوان استفاده کرد با این تفاوت که این بلاک ها باید درون بلاک دیگری به نام stream
قرار بگیرند. برای اینکار به انتهای /etc/nginx/nginx.conf
میتوان دستورات زیر را اضافه کرد:
با اینکار تمامی تنظیماتی که در مسیر /etc/nginx/stream_conf.d/
تعریف کنیم برای مدیریت درخواست های TCP و UDP استفاده خواهند شد.
به عنوان مثال برای لود بالانس TCP فرض کنید دو سرور از دیتابیس MySQL داریم و درخواست ها را بین این دو سرور میخواهیم توزیع کنیم. برای اینکار میتونیم یک فایل با نام mysql-db-load-balancer.conf
در مسیر stream_conf.d
تعریف کنیم و سپس تنظیمات زیر رو درون آن قرار دهیم:
در مثال بالا از location
استفاده ای نکردیم چون در درخواست های TCP دیگر مسیر درخواست معنایی ندارد.
برای لود بالانس در حالت udp کافیست در listen
از کلمه udp
استفاده کنیم تا Nginx متوجه بشه باید درخواست های روی پروتکل UDP را دریافت کند.
در لود بالانس TCP و UDP باید در نظر داشته باشیم که Nginx به صورت پیش فرض رفتار زیر را دارد:
هر کانکشن TCP یا UDP که باز شود تا وقتی که کلاینت خودش کانکشن رو نبندد، به همان صورت به سروری که بعد از لود بالانس متصل شده است ارتباط خود را حفظ میکند.
کانکشن های TCP یا UDP به خاطر stateful بودن، بعد از باز شدن به صورت persistent میمونن. یعنی اگر برنامهی کلاینت (یا مثلا mysql client) connection pooling انجام بده یا connection رو باز نگه داره، عملا همون connection همیشه به یک backend وصل است چون Nginx تا وقتی connection بازه است لود بالانس دیگری انجام نمی شود.
الگوریتم های لود بالانس
الگوریتم هایی که nginx روی ورژن رایگان خود ساپورت میکند به صورت زیر است:
راند رابین (Round robin)
کمترین کانکشن (Least connection)
جنریک هش (Generic hash)
تصادفی (Random)
آیپی هش (IP hash)
لود بالانس با الگوریتم Round robin
در روش Round Robin، درخواستها به ترتیب و به صورت چرخشی بین سرورهای موجود توزیع میشوند؛ اینکار بدون توجه به اینکه چه مقدار بار روی هر سرور توزیع شده یا چقدر سریع به درخواست ها پاسخ میدهد انجام میشود.
در nginx به صورت پیش فرض (default) از روش راند رابین استفاده میشود. بنابراین اگر روش دیگری را انتخاب نکرده باشید لود به صورت round robin روی سرورهای شما توزیع میشود.
لود بالانس با الگوریتم Least connection
در این روش، هر درخواست جدید به سروری فرستاده میشود که در حال حاضر کمترین تعداد اتصال فعال (Active Connections) را دارد. یعنی Nginx ابتدا تعداد درخواست فعال روی هر سرور را بررسی میکند و سپس درخواست جدید را به سروری که کمترین بار فعلی روی اون هست ارسال میکند.
این روش زمانی مناسب است که زمان پاسخ درخواستها متفاوت باشد؛ به این معنا که برخی درخواستها طولانی و برخی کوتاه هستند. معمولاً سرورهایی با توان پردازشی بالاتر میتوانند درخواستها را سریعتر پردازش کنند و به همین دلیل تعداد اتصالهای فعال آنها کمتر باقی میماند. در این شرایط، Nginx با ارسال تعداد بیشتری از درخواستها به این سرورها، سعی میکند بار را به صورت متعادل میان تمام سرورها تقسیم کند و تعداد اتصالهای فعال را در یک سطح متناسب نگه دارد.
لود بالانس با الگوریتم Generic hash
روش Generic Hash Load Balancing (یا hash-based load balancing) بر اساس یک الگوی هش (Hash Function) کار میکند. این روش بیشتر زمانی کاربرد دارد که بخواهیم درخواستهای مشابه همیشه به یک سرور مشخص هدایت شوند.
در این روش، Nginx یک مقدار مشخص از هر درخواست (مثل IP کلاینت، URL، یا هر پارامتر دلخواه) را دریافت می کند و نتیجهی حاصل از هش شدن آن مقدار, تعیین میکند که درخواست به کدام سرور ارسال شود.
وقتی از الگوریتم Hash برای لود بالانس استفاده میکنیم، یه نکته مهم اینه که اگر یکی از سرورها به هر دلیلی به جمع سرورها اضافه یا حذف بشه، ساختار هش کاملاً به هم میریزه و مسیر بیشتر درخواستها تغییر میکنه. این یعنی کاربرهایی که قبلاً به یک سرور مشخص هدایت میشدن، حالا ممکنه به سرورهای دیگه منتقل بشن. برای اینکه این اتفاق کمتر پیش بیاد، میتونیم از پارامتر consistent استفاده کنیم. این پارامتر باعث میشه توزیع درخواستها در زمان اضافه یا حذف شدن سرورها حداقل تغییر ممکن رو داشته باشه.
لود بالانس با الگوریتم Random
در این روش، Nginx بهطور تصادفی یک سرور از لیست سرورهای موجود در upstream انتخاب میکند و درخواست را به آن ارسال میکند. این الگوریتم بدون در نظر گرفتن تعداد اتصالات فعال یا سرعت پاسخگویی صرفاً به صورت کاملاً اتفاقی عمل میکند. در این روش میتوان برای سرورهای تعریف شده weight مشخص کرد.
در روش رندم میتوان در جلوی random از یکسری متد برای مشخص کردن رندم سرورها استفاده کرد. تنها متدی که در ورژن رایگان nginx وجود دارد two
است. در صورتی که به صورت random two;
نحوه توزیع شدن درخواست ها را مشخص کنیم, nginx از بین لیست سرورهایی که در upstream
مشخص کرده ایم ابتدا دو سرور را به صورت تصادفی انتخاب میکند و سپس ترافیک را به صورت least_conn
بین این دو سرور پخش میکند. بدین صورت همیشه دو سرور انتخاب میشوند و ترافیک به سروری که کانکشن فعال کمتری دارد ارسال میشود.
لود بالانس با الگوریتم IP hash
در هنگام استفاده از روش آیپی هش, nginx بر اساس آدرسِ IP کلاینت, یک مقدار هش تولید میکند و همیشه درخواستهای کلاینت با همان IP را به یک سرور ثابت هدایت میکند.
این روش تضمین میکند که کلاینتها تا زمانی که سرور بالادستی یکسانی در دسترس باشد، به آن سرور پروکسی میشوند.
این روش معمولا زمانی کاربرد دارد که نیاز داریم یک کلاینت همیشه با یک سرور خاص در ارتباط باشد. برای مثال زمانی که کش سمت سرور وجود دارد یا session های (معمولا برای اهراز هویت استفاده میشود) ذخیره شده به صورت محلی در هر سرور نگهداری میشوند.
همچنین باید در نظر داشته باشیم که اگر بیشتر کلاینتها از یک بازه IP خاص باشند، ممکن است ترافیک ورودی به صورت ناعادلانه روی سرورها توزیع شود.
در Nginx روش IP Hash فقط برای HTTP قابل استفاده است. همچنین میتوان برای سرورهای تعریف شده weight مشخص کرد.
پایش سلامت (Health check)
در nginx دو روش passive و active برای بررسی سلامت سرورها وجود دارد. روش passive در ورژن رایگان nginx موجود است.
در روش Active، لود بالانسر به صورت دورهای و مستقیم درخواستهایی برای بررسی وضعیت سرورها ارسال میکند. یعنی nginx بهطور فعال سرورها را بررسی میکند تا ببیند آیا هنوز در دسترس و سالم هستند یا نه.
در روش Passive، لود بالانسر بهصورت مستقیم وضعیت سرورها را بررسی نمیکند، بلکه فقط زمانی متوجه بروز مشکل میشود که در حین پردازش درخواستهای واقعی کاربران، خطایی رخ دهد یا به بیان دیگر nginx فقط زمانی متوجه خرابی سرور میشود که پاسخهای خطا از سمت سرور در جریان درخواستهای واقعی کاربران دریافت شود.
ورژن رایگان Nginx تنها روش Passive را پشتیبانی میکند. برای تنظیم health check به صورت passive میتونیم تنظیمات زیر رو انجام بدیم:
با استفاده از تنظیمات بالا, سلامت سرور بالادستی به صورت غیر فعال و از طریق نظارت بر پاسخ های دریافتی به درخواست های کلایند بررسی می شود. در صورتی که درخواست های ارسال شده به سرور بالادستی (در اینجا backend نامگذاری شده) با خطا مواجهه شوند یا با timeout مواجهه شوند, nginx متوجه میشود که سرور بالادستی تعریف شده در وضعیت سلامت قرار ندارد و درخواستی به آن ارسال نمیکند.
پارامترهای max_fails
و fail_timeout
مربوط به تشخیص خطا در (Passive Health Check) سرورهای بالادستی (upstream
) هستند. این دو کمک میکنند که Nginx بفهمد کِی یک سرور backend موقتاً از دسترس خارج شده.
max_fails=3
یعنی اگر Nginx طی بازه زمانی مشخص شده (که همونfail_timeout
هست) سه بار پشت سر هم از این سرور پاسخی نگیره یا خطا دریافت کنه (مثل timeouts یا خطای 5xx)، اون سرور رو "failed" یا موقتاً غیرفعال فرض میکنه.fail_timeout=3s
یعنی بازه زمانی که توش Nginx این خطاها رو میشماره. همچنین زمانی هست که بعد از شناسایی failure، Nginx اون سرور رو از مدار خارج میکنه و سعی میکنه در درخواستهای بعدی به سرورهای سالم وصل بشه. بعد از گذشت این زمان، Nginx دوباره تلاش میکنه به اون سرور وصل بشه.
فرض کنید Nginx طی ۳ ثانیه سه بار تلاش میکنه به backend1 وصل بشه و هر سه بار خطا میگیره. Nginx نتیجه میگیره که backend1 فعلاً خرابه و دیگه به اون سرور درخواست نمیفرسته. بعد از ۳ ثانیه دوباره بهش یه درخواست تستی میفرسته. اگه موفق بشه، دوباره اون سرور رو فعال میکنه.
میتوانید به نمونه کدِ مثال هایی که در این قسمت وجود داشت از طریق گیت هاب دسترسی داشته باشید کنید.
قسمت قبل: وب سرور برای فایل های استاتیک | Nginx from scratch
قسمت بعد: مدیریت ترافیک | Nginx from scratch