برنده جدید کافکا مصرف کنندگان: اسکالا به سفر 🚀

350 میلیون پیام در روز
نویسندگان مشترک: عبدالصامت ایلری، نیهات علیم
در این مقاله، داستانی را خواهید خواند که چگونه با مهاجرت از Scala به Go، استفاده از حافظه را تا 50٪ کاهش دادیم، بازده CPU را تا 80٪ و TP را تا ~20 افزایش دادیم.
معرفی
ترندیول در حال حاضر بیش از 170 میلیون محصول قابل فروش و 200 میلیون محصول آماده فروش دارد. در هر زمان، ممکن است تغییر یا بی اعتباری، به قول ما، در این محصولات از طریق رویدادهای مختلف مانند تبلیغات، سهام و قیمت رخ دهد. منظور ما از تغییرات، میلیونها مورد از آنها است، اگر دقیق باشیم، تقریباً 350 میلیون در روز. بهعنوان تیم نمایهسازی Trendyol، باید این تغییرات را تقریباً در زمان واقعی اعمال کنیم، زیرا هر تأخیر ممکن است باعث شود دادههای محصول نادرست نمایش داده شوند. ما حتی نمی خواهیم به اشتباه نوشتن قیمت یک محصول فکر کنیم.
در وسط همه این تغییرات اپلیکیشن ما Perses قرار دارد. در اساطیر یونان، پرس، خدای نابودی تایتان یونانی بود. مانند Destruction God، اپلیکیشن ما Perses داده های محصول قدیمی را از بین می برد و نسخه جدید آن را جایگزین می کند.
همانطور که از نمودار خروجی روزانه Perses در زیر می بینیم، Perses روزانه میلیون ها عملیات I/O را انجام می دهد. بسیار مهم است که آنها را به درستی و بدون تاخیر انجام دهید تا بتوانیم داده های محصول صحیح را به کاربران نشان دهیم. برای دستیابی به هدف ما از داشتن یک برنامه کاربردی با کارایی بالا، Perses به عنوان یک برنامه کاربردی چند منظوره طراحی شده است تا هر استقرار بتواند به طور مستقل مقیاس شود و روند عدم اعتبار یکدیگر را مسدود نکند.
از مجموع این ها به راحتی می توان گفت که پرس نقش مهمی در روند باطل شدن ترندیول دارد.
دلایل مختلفی وجود داشت که چرا این تصمیم مهاجرت را گرفتیم.
- قبلاً، با مهاجرت سایر پروژههای مصرفکننده کوچکتر، نتایج بهتری از نظر استفاده از منابع و عملکرد دریافت میکردیم.
- یادگیری و نگهداری Go آسانتر از حفظ پروژههای قدیمی Scala برای تیم ما بود.
مراحل اجرای ما
ما سفر پلتفرم مجدد خود را در 5 مرحله توضیح خواهیم داد.
1. بدون نظارت نمی توانید قتل ها را حل کنید.
در Scala Perses، عملیات مصرف به صورت دستهای با استفاده از Akka Stream (v2.12) انجام شد. می توانید پیاده سازی را در بلوک کد زیر مشاهده کنید.
ما این دو را به محض اجرای مصرف دسته ای در Go Perses آزمایش و مقایسه کردیم (از طریق کافکا-گو، اما نتایج ما را شوکه کرد. در حالی که Scala Perses در حال پردازش 13k پیام در یک دقیقه در هر pod بود، Go فقط می توانست 4k را پردازش کند. 😢
پس از مشاهده نتایج، برنامه را زیر نظر گرفتیم و گلوگاه را در قسمت تولیدکننده کافکا دیدیم. هنگامی که به پایگاه کد تهیه کننده در کتابخانه کافکا فرو رفتیم، متوجه شدیم که عملیات تولید به صورت همزمان انجام شده است. یعنی برای هر پیامی به دلال می رفتیم که برای عملکرد مضر بود.
2. قاتل را گرفتار کنید! صبر کنید، بیشتر هستند!؟
ما تصمیم گرفتیم پیاده سازی تولید کننده را از همزمان به ناهمزمان و به صورت دسته ای تغییر دهیم. صف پیام حاوی پیام هایی بود که در آن زمان به دو موضوع مختلف ارسال می شد.
پس از اتمام پیادهسازی، بلافاصله آزمایش بارگذاری را انجام دادیم، اما نتایج همچنان ناامیدکننده بود. Go Perses 9k پیام را در یک دقیقه در هر pod پردازش کرد، اما هنوز به Scala Perses 13k نرسیدیم. 😞
3. دنبال آرد سوخاری
کانال پیام را برای موضوعات خود جدا کردیم. ما فقط دو موضوع داشتیم که در آن زمان باید تولید میکردیم، بنابراین دو کانال پیام و یک گوروتین در هر کانال ایجاد کردیم.
پس از این تغییر، پارامترهای مختلف «اندازه دسته» و «مدت زمان» را برای دستیابی به بهترین عملکرد امتحان کردیم. پس از چندین بار تلاش، دیدیم که مقادیر بهینه برای اندازه دسته 500 و برای مدت زمان دسته 500 میلی ثانیه بود. هنگامی که دوباره تست بارگذاری را با پارامترهای نهایی با 36 پارتیشن و 12 پاد انجام دادیم، 1.094.800 پیام را در 10 دقیقه با توان عملیاتی 136k پردازش کردیم. Scala با توان عملیاتی 156k خود همچنان عملکرد بیشتری داشت و می توانست همان تعداد پیام را در 8 دقیقه پردازش کند.🤔
4. بن بست نیست
ما از کتابخانه automaxprocs Uber برای یکی از پروژههای دیگر Go خود استفاده میکنیم و شاهد بهبود عملکرد بودیم، بنابراین میخواستیم آن را برای Go Perses نیز امتحان کنیم. متأسفانه، توان عملیاتی تغییر نکرده است، زیرا Perses محدودتر از i/o محدودتر از CPU است. 😢
5. بوی شیرین پیروزی
از آنجایی که هنوز نتوانستیم نتایج مطلوب را به دست آوریم، تصمیم گرفتیم روی طراحی معماری خود تمرکز کنیم تا بفهمیم چه کاری باید انجام دهیم.
در بخش اول معماری، یک گوروتین به طور مداوم به یک موضوع گوش می دهد و با استفاده از یک صف پیام داخلی پیام های جدیدی را واکشی می کند. ما فکر می کنیم می توانیم اندازه صف را در اینجا تنظیم کنیم، بنابراین تصمیم گرفتیم آن را پیکربندی کنیم.
در قسمت دوم معماری، پس از اینکه پیام های واکشی شده به کانال می آیند، گوروتین ها آنها را پردازش می کنند. در اینجا، ما فکر کردیم که بتوانیم اندازه بافر کانال و تعداد گوروتین های کارگر را تنظیم کنیم.
پس از تغییر تنظیمات در فرآیند تنظیم از نتایج شگفت زده شدیم 💃💃. می توانید آزمایشات ما و نتایج آنها را در جدول زیر مشاهده کنید.
مقایسه عملکردی ترین نتایج Scala Perses و Go Perses را در زیر مشاهده می کنید.
نتیجه
در نتیجه فرآیند مهاجرت، منابع زیر را بهینه کردیم
- میزان استفاده از حافظه: 50% کاهش (از 1.127 گیگابایت به 0.622 گیگابایت)
- CPU: 80٪ کارآمد (از 1.72 تا 0.379)
- TP: ~20٪ افزایش (از 156k به 189k)
ما بسیار خرسندیم که این بهینه سازی را بر روی یک پایگاه کد چندگانه استقرار که تحت بار زیاد کار می کند، انجام دهیم.
چیزی که ما یاد گرفتیم!
- ما متوجه شدیم که نظارت وقتی به ما کمک می کند تا مشکل تولیدکننده کافکا را حل کنیم چقدر مهم است.
- با فرو رفتن در طراحی پروژه خود، متوجه شدیم که برخی از پارامترهای حیاتی هنوز باید تنظیم شوند تا عملکرد بهبود یابد.
ممنون که خواندید 🎈🎈🎈
ما بخشی از تجربیات خود را با یک مدیر سعی مجدد داخلی در kafka-konsumer منبع باز کردیم. ما مشتاق دریافت بازخورد شما هستیم.
با تشکر از کوتلو آراسلی، امره اوداباس و مرت بولوت برای حمایتشان 💚
اگر مایل به پیوستن به تیم ما هستید، می توانید برای نقش توسعه دهنده باطن یا هر یک از موقعیت های باز فعلی ما درخواست دهید.