وقتی لودبالانسر لودت رو بالانس نمی‌کنه!

همین چند وقت پیشا به یه مسئله‌ای خورده بودیم با بچه‌ها که خیلی طول کشید تا بتونیم مشکل رو پیدا کنیم و در پی اون من این پست رو می‌نویسم.

داستان از این قرار بود که یه دیپلویمنتی داشتیم که یه کد قدیمی HTTP Server به زبان گو روش بالا اومده بود و داشت کار می‌کرد. این کده هم در واقع یه وب‌هوک بالا می‌آورد. یعنی ریکوئست‌ها به این سرویس از یه سرویس دیگه می‌اومدن. این شکلی هم بود که برای اینکه HA باشه، چند تا اینستنس داشت و روی اینستنس‌های مختلفش لودبالانس می‌کردیم. حالا ما یه تغییری روی کدش داده بودیم و می‌خواستیم دیپلویمنت رو آپدیت کنیم. از اونجایی که نگران بودیم تغییراتمون مشکلاتی برامون ایجاد کنه، گفتیم یهو همه‌ی بار رو نبریم رو دیپلویمنت جدید. در نتیجه، به اصطلاح قناری (canary) کردیم. به این صورت که اول ۱۰ درصد بار رو رو جدیده انداختیم، بعدش ۲۰ درصد، بعدش ۳۰ درصد. به این صورت این کارو کردیم که اومدیم از ۱۰ تا اینستنس قدیمی‌ای که بالا بود، یکی رو پایین آوردیم و به جاش جدیده رو بالا آوردیم و به همین شکل ادامه دادیم.

اونجایی خیلی ناراحت شدیم که دیدیم هیچ ریکوئستی اون فیچر جدیدا رو نداره و دقیق‌تر که بررسی کردیم، دیدیم هیچ کدوم از جدیدا اصلا ریکوئستی نمی‌گیرن!

و خب کل مدت ما اینجوری بودیم که:

Load balancer! You had only one job!!!

حالا مسئله این هم نیست همیشه. مثلا یه سرویسی داشتیم که ۳ تا اینستنس ازش بالا آورده بودیم و گذاشته بودیم پشت IPVS. اما لودشون اصلا بالانس نبود. مثلا به یکی ۲۰ درصد بیشتر از بقیه ریکوئست HTTP می‌رسید.

تصویر متعلق به aws
تصویر متعلق به aws


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

لودبالانسرایی که ما باهاشون کار می‌کنیم عمدتا یا لایه ۴ (Transport Layer)ن یا لایه ۷ (Application Layer). توی فضای صحبت الانمون به صورت متناظر TCP Load Balancer و HTTP Load Balancer داریم. و خب اینا عملکردشون با هم فرق داره و نتایج استفاده از هر کدوم هم متفاوت می‌شه. فرقشون اینه که اولی کانکشن رو لودبالانس می‌کنه و بعدی ریکوئست HTTP رو. حالا چجوری این باعث عجایب(!) می‌شه؟ راجع به این قراره حرف بزنیم.

ولی آدمای کمی داستانای تئوری دانش‌گاه‌طوری رو دوست دارن :)). اکثریت می‌خوان ببینن در عمل چی می‌شه. منم اول می‌خوام نشون بدم که در عمل چه تفاوتی می‌تونن داشته باشن.

من ۳ تا نود دارم. روی یکی از این نودا یه HAProxy بالا می‌آرم ([email protected]) و روی دو تا نود دیگه سروری که می‌خوام روش لودبالانس کنم رو ([email protected] و [email protected]). و می‌خوام فرق‌های لودبالانس لایه ۴ و لایه ۷ رو روش ببینم.

به کانفیگ دیفالت HAProxyم روی نود ۱ این کانفیگ‌ها رو اضافه می‌کنم:

کانفیگ‌های لودبالانسینگ روی HAProxy
کانفیگ‌های لودبالانسینگ روی HAProxy

یعنی این‌شکلی می‌شه که رو پورت ۸۰ لودبالانس لایه ۴ می‌کنه و رو پورت ۸۱ لودبالانس لایه ۷.

علاوه بر این، یه کد کلاینت-سرور ساده‌ی گو هم برای تست کارهایی که می‌خوایم بکنیم زدم:

  • کد کلاینت:
تصویر قسمت اصلی کد کلاینت
تصویر قسمت اصلی کد کلاینت
  • کد سرور:
تصویر کد سرور
تصویر کد سرور

حالا روی ۲ تا نود پشتی، کد سرور رو ران می‌کنیم. اول کد کلاینت رو با یه worker برای ۱۰ ثانیه به پورت ۸۱ که HTTP Load Balancer روش بالاست، روی لوکال سیستم، ران می‌کنیم. نتیجه یه همچین چیزی می‌شه:

ترمینال بالا چپ نود ۱ - ترمینال پایین چپ لوکال - ترمینال وسط نود ۲ - ترمینال راست نود ۳
ترمینال بالا چپ نود ۱ - ترمینال پایین چپ لوکال - ترمینال وسط نود ۲ - ترمینال راست نود ۳

همونطور که واضحه، لودمون بالانس شده بین دوتا اینستنس.

حالا همین تست رو به پورت ۸۰ که TCP Load Balancer روش بالاست تکرار می‌کنیم:

وا! لود بالانس نشد! این دقیقا همون نقطه‌ایه که ما لودبالانسر گذاشتیم که لودمونو بالانس کنه ولی انگار نه انگار! ولی حالا بیایم یه تست دیگه بگیریم ببینیم چی می‌شه! به جای اینکه با یه worker بزنیم، با ۲تا worker هم‌زمان تست بگیریم:

عه! لود بالانسه این دفعه! خب بیایم با ۳ تا worker هم بگیریم دیگه حالا:

خب دوباره بالانس نیست! یکی ۲ برابر اون‌یکیه!

چه اتفاقی داره می‌افته؟ جواب تو یه مکانیزمیه به اسم Keepalive. این Keepalive چی هست حالا؟ توضیح مفصلشو که سرچ کنیم پیدا می‌کنیم ولی خب خلاصه بخوایم بگیم، اینه که برای آپتیمایز کردن HTTP این مکانیزم ایجاد شده که ما نیایم برای هر ریکوئستمون به یه اندپوینت، کانکشن TCP بزنیم. اگه یه کانکشن زدیم، بعد اینکه تموم شد ریکوئستمون، نبندیمش. که اگه خواستیم دوباره ریکوئست بفرستیم به همونجا، رو همون کانکشن رد کنیم (به اصطلاح reuse کنیم کانکشن رو).

دقیقا داستان ما هم همینه دیگه. این کلاینت ما داره از Keepalive استفاده می‌کنه (فکت: تو پکیج http زبان Go، فیچر Keepalive دیفالته) و زمانی که اولین ریکوئست به یکی از نودای ما می‌ره، دیگه بقیه‌ی ریکوئستا هم رو همون کانکشن اولی می‌رن. ولی وقتی ما دو workerه بزنیم، دوتا کانکشن داریم و چون کانکشنا داره لودبالانس می‌شه، یه worker به یه نود و worker دیگه به اون‌یکی نود می‌زنه.

با توجه به این توضیحات، اگه ما به workerهامون بگیم که از Keepalive استفاده نکنن، رو پورت ۸۰ باید لود بالانس درست ببینیم (با یک worker):

که همینطور هم می‌شه.

ولی خب در عمل، اینکه ما Keepalive رو disable کنیم کار خوبی نیست. چون به ازای هر ریکوئست داریم کانکشن می‌زنیم که هزینه داره (حالا روی SSL هزینه‌ش بیشتر هم می‌شه).

با توجه به همه‌ی این حرفا، آیا می‌تونیم بگیم که لودبالانس کردن HTTP با یه لودبالانسر لایه ۴ کار بد یا غلطیه؟ قطعا نه! بسته به ورک‌لودش کاملا می‌تونه فرق بکنه. یعنی مثلا ما اگه تعداد خیلی زیادی کلاینت داشته باشیم، اصلا این موضوع قابل صرف نظره. ولی خب اگه فقط یه کلاینت داشته باشیم که داره از این فیچر (Keepalive) استفاده می‌کنه انگاری اصلا لودبالانس نکرده‌یم. بعلاوه مثلا توی کیس‌هایی که می‌خوایم قناری کنیم و می‌خوایم یه درصدی از بار رو اینطوری پخش کنیم، این موضوع اهمیت پیدا می‌کنه. بعلاوه بهینگی لودبالانس لایه ۴ بیشتره و می‌شه آپتیمیزیشن‌های بیشتر و جدی‌تری روش اعمال کرد.

هدف اصلی از این پست این بود که بیایم رفتار این دوتا رو تو یه سناریویی که ممکنه ما رو به تعحب بندازه مشاهده کنیم و راجع به دلیلش صحبت کنیم. چون خیلی جاها مثل اورکستریتورها یا کلاودها، لودبالانسرهایی که ارائه می‌شه، لودبالانسر لایه‌ی ۴ه.

در نهایت هم این اولین بلاگ‌پست فنی‌ایه که دارم می‌نویسم و نمی‌دونم چقدر خوب تونستم چیزی که می‌خواستم رو ارائه کنم. امیدوارم که حال کرده باشید. خیلی خوش‌حال می‌شم فیدبک بگیرم که در آینده بتونم این مهارت رو توی خودم تقویت کنم. دم شما هم گرم :)).