توزیع‌کننده بار CDN ستون (ابر کافه‌بازار)  چگونه کار می‌کند؟

در این پست می‌خواهیم در رابطه با نحوه‌ی عملکرد CDN و به طور ویژه روش‌های توزیع ترافیک بین سرورهای CDN در ابر کافه‌بازار توضیحاتی بدهیم که شامل تاریخچه‌ای از روش‌های متفاوتی که برای این امر استفاده کردیم و همینطور توضیحات فنی در رابطه با آخرین روشی که پیاده‌سازی کردیم، می‌شود.در ابتدای کار اپلیکیشن بازار، فایل‌های static مثل تصاویر و ‌apkها بر روی یک سرور قرار می‌گرفت و همه‌ی کاربران موقع دانلود کردن به این سرور متصل شده و فایل مورد نظرشان را دانلود می‌کردند. کم‌کم با رشد تعداد کاربران فعال بازار و به تبع آن رشد تعداد دانلودها، یک سرور کافی نبود. تصمیم اولیه این بود که تعداد سرورها را بیشتر کنیم و یک کپی از تمام فایل ها روی همه سرورها داشته باشیم. بنابراین چند سرور با کانفیگ یکسان در دیتاسنترهای مختلف نصب کردیم. با رشد تعداد فایل‌ها امکان ذخیره همه فایل‌ها روی همه سرورهای CDN نبود و باید با در نظر گرفتن Locality زمانی در دسترسی به فایل‌ها از طرف کاربرها از HTTP Cache استفاده می‌کردیم.چالش دیگر شبکه CDN، نحوه توزیع بار بین سرورهای متعددی است که در اختیار داریم. روش‌های مختلفی برای این کار وجود دارد که ما به ترتیب زیر سراغشان رفتیم و پیاده‌سازی کردیم.توزیع بار در کلاینت، استفاده از DNS record :قدیمی ترین روشی که برای توزیع بار استفاده کرده بودیم، توزیع‌ درخواست‌ها از سمت کلاینت بود. اولین مشکل این روش به‌روز نگه‌ داشتن لیست سرورهای CDN سمت کلاینت بود. برای حل این مشکل تصمیم گرفتیم که از DNS record ها استفاده کنیم. به این صورت که بیست رکورد تعریف کردیم و هر رکورد IP چند سرور را برمی‌گرداند در این روش با اضافه و کم‌ شدن سرور جدید مشکلی بوجود نمی‌آمد و همچنین با اضافه کردن متعدد IP یک سرور در رکورد های مختلف تا حدی وزن توزیع بار را کنترل کردیم. اما این روش هم بعد از مدتی دیگر قابل استفاده نبود، چون در صورت بروز مشکل برای یک سرور باید IP آن سرور را از تمامی رکوردها حذف می‌کردیم و در صورتی که میخواستیم وزن‌ها را تغییر دهیم، به علت وجود کش در Resolver‌های DNS تغییرات دیرتر اعمال می‌شدند.توزیع بار با استفاده از DNS نیمه‌هوشمند GSLB :روش بعدی برای توزیع بار، استفاده از DNS load balancer بود.بدین منظور ما از روش Global Server Load Balancer (GSLB) استفاده کردیم. وظیفه‌ی اصلی این توزیع‌کننده بار این بود که از سلامت سرورها اطمینان حاصل کند و بعد از آن با وزنی که از پیش تعیین شده، IP سرورها را در جواب کوئری‌های DNS به کاربران بدهد. با این روش ما می‌توانستیم به جای استفاده از چندین دامنه، فقط یک دامنه در اختیار کلاینت قرار دهیم و کلاینت در پاسخ استعلام از DNS مربوط به آن دامنه، ‌IPهای متنوعی دریافت کند و به سرور وصل شود.این روش برای مدت‌ها در CDN ما استفاده می‌شد، ولی مشکلات زیادی را ایجاد می‌کرد که از موارد مهم آن در ادامه یاد خواهم کرد:ریکرسرهای DNS با توجه به TTL تعیین شده کوئری‌های DNS را کش می‌کنند و این موضوع هم باعث می‌شد در صورت ناسالم بودن یک سرور مدت طولانی‌تری کاربران اذیت شوند و همینطور کنترل ما بر روی میزان ترافیک ارسالی سمت سرورها را هم کم می‌کرد.چون IP سرورها به صورت کاملا تصادفی برای کاربرها ارسال می‌شد، ممکن بود کاربری مثلا در شبکه مبین‌نت به جای اتصال به سرور نزدیک خودش که در مبین‌نت قرار دارد به یک سرور در افرانت وصل شود که این موضوع باعث تاخیر در دانلود می‌شد.امنیت پایین‌تری داشتیم چون حمله کننده می‌توانست IP سرورها را داشته باشد و بدین ترتیب به آن‌ها حمله کرده و یک سرور را با حمله DoS (حمله‌ی منع سرویس) از کار بیندازد.توزیع بار توسط DNS Server هوشمند برای کم کردن اثر مشکل ۱، سعی کردیم که TTL جواب‌های GSLB را تا حد امکان عدد کم‌تری باشد ولی این مسأله کاملا بستگی به DNS Resolver داشت که چقدر جواب را cache کند و ممکن بود DNS Resolver هایی باشند که از‌ TTL ما سرپیچی کنند. مساله دیگر این بود که با کم کردن TTL به مقدار قابل‌توجهی درخواست‌های GSLB زیاد می‌شد و عملا cache لایه‌ای DNS مورد استفاده قرار نمی‌گرفت. برای کم کردن اثر مشکل ۲، می‌توانستیم GSLB را هوشمندتر کنیم و بر اساس ISP کاربر IP سروری که احتمال می‌دادیم به او نزدیک‌تر است را برگردانیم؛ اما لازمه این کار داشتن یک پایگاه‌ داده‌ی خوب از IP های مربوط به ISP های متفاوت بود و همینطور به روز نگه‌داشتن این پایگاه داده.توزیع بار با استفاده از خود شبکه، Anycast (ساختار جدید):برای حل مشکلات از یک مفهوم شبکه به نام Anycast استفاده کردیم. روش کار در این ساختار بدین صورت است که ما یک IP Range را روی همه سرورهایمان قرار می‌دهیم و این IP Range را از روی تمام سرورها با استفاده از پروتکل BGP در شبکه‌ی اینترنت، منتشر می‌کنیم. این امر باعث می‌شود که هر کاربر فقط یک IP را از DNS بگیرد و داخل شبکه اینترنت به نزدیک‌ترین سرور (از نظر تعداد ‌ASهای میانی) متصل شود. در این روش وقتی یک سرور به مشکل می‌خورد به سادگی می‌توانیم انتشار IP توسط BGP را متوقف کنیم تا دیگر ترافیکی به سمت آن سرور ارسال نشود. برای پیاده‌سازی درست Anycast شما باید ISP‌هایی که کاربران بیشتری در آن‌ها دارید را پیدا کرده و سرورها را در نزدیک‌ترین نقطه نسبت به آن ISP قرار دهید. به عنوان مثال کاربران اپراتورهای تلفن‌ همراه عمده کاربران بازار را تشکیل می‌دهند، پس منطقی است که بازار در این دو ISP سرور بگذارد و ‌IP Range خودش را در داخل این شبکه‌ها منتشر کند و ترافیک را از داخل شبکه این ISP‌ها جواب دهد. در زمان اتخاذ این تصمیم حضور در ایرانسل و همراه اول به خاطر نوپا بودن دیتاسنترهای آن‌ها امکان‌پذیر نبود و ما نیز از قبل سرورهای متعددی در دیتاسنترهایی مثل افرانت، های‌وب، مبین‌نت، شاتل و … داشتیم. با این نگاه که در بلندمدت باید در ISP‌ها و در نزدیک‌ترین نقطه به کاربر حضور داشته باشیم، سعی کردیم راه‌حل Anycast را با توزیع فعلی سرورها به پیش ببریم.توزیع بار با توجه به موقعیت جغرافیایی کاربران وقتی IP Range خودمان را با BGP از همه این دیتاسنترها منتشر می‌کنیم، برای کنترل اینکه کاربران کدام ISP به کدام دیتاسنتر بروند می‌شود از BGP Communities استفاده کرد. در واقع BGP Communities به کلاینت BGP امکان Traffic Engineering را می‌دهد. با پرس‌و‌جو متوجه شدیم که BGP Communities در ایران تقریبا به جز چند مورد well-known پشتیبانی نمی‌شود و باید از دیتاسنترها می‌خواستیم که به صورت استاتیک تنظیماتی انجام دهند. مثلا تنظیم می‌کردیم که ترافیک ایرانسل به دیتاسنتر «الف» و ترافیک همراه اول به دیتاسنتر «ب» برود. چون این روش استاتیک بود، در صورت بروز مشکل برای دیتاسنتر «ب» همه کاربران همراه اول تا زمان تغییر دوباره مسیرها به صورت دستی با مشکل مواجه می‌شدند. می‌شود ادعا کرد که مثلا می‌توانستیم این announcement را از چند دیتاسنتر انجام دهیم ولی مشکل این بود که همه‌چیز ماهیتی استاتیک داشت و باید با تلفن و تیکت از بخش فنی این دیتاسنترها می‌خواستیم که Route‌ها ما را تغییر دهند. حتی با فرض اینکه این دیتاسنترها، خیلی سریع به درخواست‌های ما جواب بدهند، ما باید در هر دیتاسنتر به اندازه اوج ترافیک یک ISP بزرگ مثل ایرانسل و همراه اول ریسورس سرور و پهنای باند می‌داشتیم که متاسفانه به خاطر اینکه این زمان‌های اوج مکررا اتفاق نمی‌افتد، دیتاسنترها این مساله را بدون دریافت هزینه بالا برای اضافه ریسورس بلامصرف، نمی‌پذیرفتند.ما راه‌حلی می‌خواستیم که در عین استفاده از Anycast در مواقعی که یک سرور ظرفیتش پرمی‌شود بتواند مقداری از ترافیکش را به سمت یک سرور دیگر بفرستد و آن سرور اضافه بار را تحمل کند.بار بیش از انتظار در برخی نقاط جغرافیایی ساختار خیلی جدید Nemesis:برای حل این مشکل چندین راه حل را بررسی کردیم، راه حل‌های شرکت‌های بزرگ دنیا را مطالعه کردیم و در نهایت نتیجه صحبت‌ها به پیاده‌سازی پروژه Nemesis منتهی شد. هسته اصلی توزیع‌کنندگی بار در این پروژه، Katran فیسبوک است.پروژه‌ی Katran یک توزیع‌کننده بار هوشمند لایه چهار است که با استفاده از XDP Infrastructure موجود در کرنل، پکت‌ها را قبل از رسیدن به TCP/IP Stack هسته لینوکس، پردازش میکند و بازدهی خیلی بالاتری نسبت به انواع دیگر توزیع‌کننده‌های بار لایه ۴ مثل Nginx و IPVS دارد.در صورت پشتیبانی کارت شبکه می‌توان کد مربوط به توزیع‌بار که یک کد eBPF است را بر روی پردازنده کارت شبکه Offload کرد و به بازدهی‌های بسیار بالاتری نیز دست یافت. (از‌XDP همچنین می‌توان برای ساخت firewall، router و … استفاده کرد.) توضیحات بیشتر در رابطه‌ با ساختار XDP و کد eBPF را می‌توانید از این لینک مطالعه کنید.پروژه Katran مزایا و معایب زیر را دارد که البته در ادامه به طور مفصل توضیح داده شده است:مزایا:۱- سرعت بسیار بالا به نسبت باقی توزیع‌کننده‌های بار۲- افزایش خطی بازدهی توزیع‌کننده بار با افزایش تعداد صف‌های دریافت داده روی کارت شبکه ها۳- کپسوله‌سازی (Encapsulation) متناسب با RSS.معایب:۱- ورژن کرنل بالایی که می‌خواست (حداقل 4.13)، زمانی که ما تصمیم گرفتیم از Katran استفاده کنیم روی سرورهای CDN کرنل 4.4 نصب بود.۲- عدم وجود سلامت سنج، این یعنی زمانی که سرورهای اصلی به هر دلیلی از دسترس خارج می‌شدند همچنان درخواست‌ها را به سمتشون ارسال می‌شد و کاربرها نمیتوانستن پاسخ مد نظرشون را دریافت کنن.۳- عدم وجود کنترل‌کننده هوشمند تا در صورتی که تعداد درخواست‌های یکی از سرورها بیش‌تر از توان پاسخگویی آن سرور شد درخواست‌ها به صورت اتوماتیک به سمت سرور دیگری ارسال شود.۴- توزیع‌کننده بار Katran برای اینکه پکت‌ها، سمت سرور اصلی به طور متوازن بر روی صف‌های ورودی توزیع شود، ip مبدا پکت‌های خروجی را از رنج 172.16.0.0/16 انتخاب می‌کند که این رنج، جزو ip های Private محسوب می‌شود و در isp هایی که مقررات را به درستی رعایت میکنند و اجازه‌ی خروج پکت با ip مبدا ناشناس یا Private را نمی‌دهند، Drop می‌شود.(دیتاسنتر برای جلوگیری از IP Spoofing چنین سیاستی را اعمال می‌کنند)۵- روی سرورهایی که بیش از یک interface ورودی دارند قابل اجرا نیست.۶- توزیع‌کننده بار Katran تمامی پکت‌ها را Encapsulate کرده و برای Default Gateway ارسال می‌کند، این عمل زمانی‌که Katran و CDN منتخب، هر دو روی یک سرور باشند، Katran را دچار مشکل افت بازدهی می‌کند.بعد از بررسی موارد بالا و همینطور مقایسه با راهکارهای دیگر در نهایت تصمیم گرفتیم که تغییراتی در کد پروژه Katran دهیم که مشکلات ۴، ۵ و ۶ را حل کرد که در پستی دیگر جزئیات این تغییرات را خواهیم نوشت. در حال حاضر این تغییرات را در قالب مرج‌ریکوئست با پروژه‌ی اصلی جلو می‌بریم تا به repository اصلی اضافه شوند.همین‌طور برای حل مشکل ۲و۳ یک سلامت‌سنج و کنترل‌کننده هوشمند توسعه دادیم که وزن‌های مدنظر را با بررسی سلامت سرورهای مقصد بر روی Katran تنظیم می‌کند.در مجموع این پروژه شامل چند بخش مهم است:۱- هسته ‌توزیع‌کنندگی Katran که به زبان‌های C وCPP‌ نوشته شده است.۲- برنامه BPF و ساختار XDP.۳- سلامت سنج و کنترل‌کننده هوشمند Nemesis، که با زبان Go نوشته شده است.ارسال ترافیک مازاد به سمت سرورهای کم‌بار توسط Katran پروژه‌های Nemesis و Katran روی تمام سرورهای CDN نصب شده‌اند و نحوه عملکردشان به ترتیب مراحل زیر است:۱‍- اول ازهمه کنترل‌کننده چک می‌کند که سرور مقصد از سلامت لازم برخوردار باشد و در صورت برخورداری از سلامت کافی آن را به‌عنوان سرور سالم در نظر می‌گیرد، سرور مقصد در بیشتر مواقع برای بالا بودن بهره‌وری، همان سرور توزیع‌کننده بار یا سروری داخل همان دیتاسنتر است.۲- با در نظر گرفتن چندین متغیر مختلف، وزن‌های متفاوتی را برای سرورهای مقصد در نظر می‌گیرد و به صورت آهسته آهسته وزن‌ها را اعمال می‌کند.۳- سپس پکت‌ها توسط توزیع‌کننده بار دریافت می‌شود.۴- بررسی می‌شود که IP مقصد در تنظیمات مرتبط قرار گرفته باشد، در غیر اینصورت پکت را بدون تغییر به لایه‌ی بالاتر ارسال می‌کند.۵ - بررسی می‌شود که آیا از همین session قبلا پکتی آمده بوده؟! که در این صورت همان سرور مقصد قبلی، به‌عنوان سرور مقصد انتخاب می‌شود.۶- اگر session جدید باشد از ۵تایی مرتب (tcp/udp-sip-sport-dip-dport) موجود در پکت برای محاسبه مقدار hash استفاده می‌شود.۷- با استفاده از hash یک سرور مقصد انتخاب می‌شود.۸- جدول session را با استفاده از داده‌های پکت به‌روزرسانی می‌کند تا برای پکت‌های بعدی احتیاج به محاسبه دوباره hash نباشد.۹- در‌صورتی که سرور مقصد با سرور توزیع‌کننده بار یکسان باشد، پکت را به لایه‌ی بالاتر ارسال می‌کند، در غیراین‌صورت پکت، داخل یک پکت IP دیگر encapsulate می‌شود و به سمت سرور مقصد ارسال می‌شود.این توزیع‌کننده بار تنها در حالت DSR اجرا می‌شود، که متفاوت از حالت Proxy است و عملکرد آن به شکل زیر است:حالت Direct Server Return حالت Proxyاز قابلیت‌های منحصربه‌ فرد Katran این است که در صورت بروز مشکل برای یکی از توزیع‌کننده‌های بار، توزیع‌کننده بار دیگری وظیفه‌اش را برعهده می‌گیرد، بدون اینکه مشکلی برای session به‌وجود بیاید؛ اهمیت قضیه در این است که بدون احتیاج به اشتراک‌گذاری حالت توزیع‌کننده‌های بار و کاملا stateless، توزیع‌کننده بار جدید، پکت‌های مربوط به session را بعد از محاسبه hash به همان سرور قبلی ارسال می‌کند.این یعنی اگر کاربری در حال استفاده از ویدیو کافه‌بازار باشد و یا در حال دانلود اپلیکیشن باشد و توزیع‌کننده باری دچار مشکل بشود یا احتیاج به ریستارتش باشد، کاربر متوجه این تغییر نمی‌شود و ترافیک از طریق یک توزیع‌کننده بار دیگر به دست سرور مقصد می‌رسد.توضیحات مفصل‌تر از ویژگی‌های کلیدی Katran:۱ -سرعت: Katran برای فوروارد کردن پکت ها از XDP استفاده می‌کند، که اجازه استفاده از روتین‌های packet handling را بلافاصله بعد از دریافت پکت از کارت شبکه (NIC) و پیش از اینکه کرنل فرصت اجرا داشته باشد، را می‌دهد.۲- بازدهی،‌ با تعداد صف‌های RX کارت شبکه به صورت خطی افزایش پیدا می‌کند: نحوه‌ی عملکرد XDP بدین‌صورت است که برنامه‌ی BPF را برای هر پکت دریافت شده فراخوانی می‌کند، و چون کارت شبکه‌های ما چندین صف برای دریافت داده دارند، در نتیجه برای هر‌کدام از صف‌ها برنامه BPF به صورت جداگانه اجرا می‌شود. در Katran این قابلیت وجود دارد که هرکدام از هسته‌های پردازنده را به یک برنامه BPF نگاشت کنیم، که باعث می‌شود هر پردازنده به صورت مستقل برنامه را اجرا کند و هیچ‌گونه وابستگی به بقیه هسته‌ها نداشته باشد؛ این توانمندی‌های Katran در مجموع باعث می‌شود که بازدهی توزیع‌کننده بار رابطه‌ی خطی با تعداد هسته‌های پردازنده و همینطور صف‌های کارت شبکه داشته باشد.۳- کپسوله‌سازی (Encapsulation) متناسب با RSS: در Katran برای فوروارد کردن پکت‌ها از توزیع‌کننده بار تا سرورهای مقصد، از ipip encapsulation استفاده می‌شود. با این حال، برای اینکه توانایی تشخیص پیوستگی پکت‌های یک flow در سمت سرور مقصد وجود داشته باشد و همین‌طور، توزیع متوازن ترافیک بر روی صف‌های کارت شبکه سرور مقصد، انجام شود،‌ به‌جای استفاده از ip سرور توزیع‌کننده بار برای source ip در پکت‌های ipip، امکانی را ایجاد می‌کند که پکت‌های flowهای مختلف، source ip های متفاوتی داشته باشند، ولی برای پکت‌های یک flow همواره ip یکسانی تنظیم شود.۴- استفاده از Maglev hashing تغییر یافته برای کانکشن‌ها: در صورت وجود خطا،‌ انعطاف‌پذیری خوب و ویژگی‌های توزیع‌کنندگی عالی از خود ارائه می‌دهد. hashing به‌گونه‌ای تغییر پیدا کرده که بتواند وزن‌های نامساوی را برای سرورهای مقصد در نظر بگیرد..۵ - احتیاجی به busy looping در مسیر دریافت داده نیست: توزیع‌کننده بار در صورتی که ترافیکی برای رسیدگی وجود نداشته باشد، تقریبا از CPU استفاده‌ای نمی‌کند.۶- توزیع‌کننده بار Katran (و به طور کلی XDP) به ما اجازه می‌دهد که بدون داشتن افت عملکرد (performance penalties) بر روی همان سرور، هر اپلیکیشنی را اجرا کنیم. (در مقایسه با بقیه‌ی تکنولوژی‌های "kernel bypass")جمع‌بندی:بعد از اضافه‌ کردن ‌Katran و Nemesis به سرورها ما توانستیم کنترل کامل توزیع با‌ر را در اختیار بگیریم و به صورت کاملا هوشمند ترافیک را بین سرورها توزیع کنیم، این درحالیست که از مزایای Anycast نیز بی‌بهره نمانده‌ایم و ترافیک هر‌کاربر از نزدیک‌ترین سرور پاسخ داده می‌شود.