من این مقاله را بسیار جالب دیدم، و از آنجایی که Envoy اغلب به عنوان بخشی از "istio" یا صرفاً به عنوان "کنترل کننده ورود" از kubernetes استفاده می شود، بیشتر مردم تعامل مستقیمی با آن ندارند، به عنوان مثال، با معمولی. نصب Nginx یا Haproxy. با این حال، اگر چیزی شکسته شود، خوب است بفهمیم که چگونه از درون کار می کند. من سعی کردم تا حد امکان متن را به روسی ترجمه کنم، از جمله کلمات خاص؛ برای کسانی که دیدن این مطلب برایشان دردناک است، اصل را داخل پرانتز گذاشتم. به گربه خوش آمدید
اسناد فنی سطح پایین برای پایگاه کد Envoy در حال حاضر بسیار پراکنده است. برای رفع این مشکل، قصد دارم یک سری پست وبلاگ در مورد زیرسیستم های مختلف Envoy انجام دهم. از آنجایی که این اولین مقاله است، لطفاً نظرات خود را به من بگویید و در مقالات بعدی به چه چیزهایی علاقه مند خواهید بود.
یکی از متداولترین سؤالات فنی که در مورد Envoy دریافت میکنم، درخواست توضیح سطح پایین از مدل رشتهای است که استفاده میکند. در این پست، نحوه نقشهبرداری Envoy اتصالات به رشتهها و همچنین سیستم Thread Local Storage را که به صورت داخلی برای موازیتر کردن کدها و کارایی بالا استفاده میکند، شرح خواهم داد.
نمای کلی رشته
Envoy از سه نوع مختلف جریان استفاده می کند:
اصلی: این رشته راهاندازی و خاتمه فرآیند، تمامی پردازشهای API XDS (XDiscovery Service) از جمله DNS، بررسی سلامت، مدیریت کلستر و زمان اجرا، تنظیم مجدد آمار، مدیریت و مدیریت فرآیند کلی - سیگنالهای لینوکس را کنترل میکند. راهاندازی مجدد داغ و غیره. اتفاق می افتد در این موضوع ناهمزمان و "غیر مسدود کننده" است. به طور کلی، رشته اصلی تمام فرآیندهای عملکردی حیاتی را که برای اجرا به مقدار زیادی CPU نیاز ندارند، هماهنگ می کند. این اجازه می دهد تا بیشتر کدهای کنترلی به گونه ای نوشته شوند که گویی تک رشته ای هستند.
کارگر: به طور پیش فرض، Envoy برای هر رشته سخت افزاری در سیستم یک thread کارگر ایجاد می کند، این می تواند با استفاده از گزینه کنترل شود. --concurrency. هر نخ کارگر یک حلقه رویداد «غیر مسدودکننده» را اجرا میکند، که وظیفه گوش دادن به هر شنونده را بر عهده دارد؛ در زمان نگارش (29 ژوئیه 2017) هیچ اشتراکی از شنونده، پذیرش اتصالات جدید، ایجاد یک پشته فیلتر برای اتصال، و پردازش تمام عملیات ورودی/خروجی (IO) در طول عمر اتصال. باز هم، این اجازه می دهد تا اکثر کدهای مدیریت اتصال به گونه ای نوشته شوند که گویی تک رشته ای هستند.
پاک کننده فایل: هر فایلی که Envoy می نویسد، عمدتاً گزارش های دسترسی، در حال حاضر دارای یک رشته مسدود کننده مستقل است. این به خاطر این واقعیت است که نوشتن روی فایلهایی که توسط سیستم فایل ذخیره میشوند حتی در هنگام استفاده O_NONBLOCK گاهی اوقات ممکن است مسدود شود (آه). زمانی که thread های کارگر نیاز به نوشتن در یک فایل دارند، داده ها در واقع به یک بافر در حافظه منتقل می شوند و در نهایت از طریق thread پاک می شوند. فلاش فایل. این یکی از حوزههای کد است که از نظر فنی، تمام رشتههای کارگر میتوانند قفل یکسانی را در حین پر کردن بافر حافظه مسدود کنند.
مدیریت اتصال
همانطور که در بالا به اختصار توضیح داده شد، همه رشتههای کارگر به همه شنوندگان بدون هیچ گونه تقسیمبندی گوش میدهند. بنابراین، از کرنل برای ارسال برازنده سوکت های پذیرفته شده به نخ های کارگر استفاده می شود. هستههای مدرن معمولاً در این کار بسیار خوب هستند، آنها از ویژگیهایی مانند تقویت اولویت ورودی/خروجی (IO) استفاده میکنند تا قبل از شروع به استفاده از رشتههای دیگر که در همان سوکت گوش میدهند، یک رشته را با کار پر کنند، و همچنین از دور رابین استفاده نمیکنند. قفل کردن (Spinlock) برای پردازش هر درخواست.
هنگامی که یک اتصال در یک موضوع کارگر پذیرفته می شود، هرگز آن رشته را ترک نمی کند. تمام پردازش های بعدی اتصال به طور کامل در نخ کارگر انجام می شود، از جمله هر گونه رفتار ارسال.
این چند پیامد مهم دارد:
تمام اتصالات در Envoy به یک موضوع کارگر اختصاص داده شده است. بنابراین، اگرچه استخرهای اتصال HTTP/2 فقط یک اتصال به هر میزبان بالادستی در یک زمان برقرار میکنند، اگر چهار رشته کاری وجود داشته باشد، چهار اتصال HTTP/2 در هر میزبان بالادستی در حالت ثابت وجود خواهد داشت.
دلیل اینکه Envoy به این روش کار می کند این است که با نگه داشتن همه چیز در یک رشته کارگر، تقریباً همه کدها را می توان بدون مسدود کردن و به گونه ای که گویی تک رشته ای است نوشت. این طراحی نوشتن کدهای زیادی را آسان می کند و به طور باورنکردنی به تعداد تقریباً نامحدودی از موضوعات کارگر مقیاس می شود.
با این حال، یکی از نکات اصلی این است که از نظر حافظه و بازده اتصال، پیکربندی در واقع بسیار مهم است. --concurrency. داشتن thread های کارگر بیشتر از حد لازم باعث هدر رفتن حافظه، ایجاد اتصالات بیکار بیشتر و کاهش نرخ ادغام اتصال می شود. در Lyft، کانتینرهای کناری فرستاده ما با همزمانی بسیار کم کار می کنند، به طوری که عملکرد تقریباً با خدماتی که در کنار آن قرار دارند مطابقت دارد. ما Envoy را به عنوان یک پروکسی لبه فقط در حداکثر همزمانی اجرا می کنیم.
عدم انسداد یعنی چه؟
اصطلاح "non-blocking" تاکنون چندین بار هنگام بحث در مورد نحوه کار نخ های اصلی و کارگر استفاده شده است. همه کدها با این فرض نوشته می شوند که هیچ چیز هرگز مسدود نشده است. با این حال، این کاملاً درست نیست (چیزی که کاملاً درست نیست؟).
Envoy از چندین قفل فرآیند طولانی استفاده می کند:
همانطور که بحث شد، هنگام نوشتن گزارش های دسترسی، تمام نخ های کارگر قبل از پر شدن بافر گزارش داخل حافظه، قفل یکسانی را بدست می آورند. زمان نگه داشتن قفل باید بسیار کم باشد، اما ممکن است که قفل در همزمانی بالا و توان عملیاتی بالا به چالش کشیده شود.
Envoy از یک سیستم بسیار پیچیده برای مدیریت آمارهای محلی برای موضوع استفاده می کند. این موضوع یک پست جداگانه خواهد بود. با این حال، من به طور خلاصه اشاره می کنم که به عنوان بخشی از پردازش آمار رشته به صورت محلی، گاهی اوقات لازم است یک قفل در یک "فروشگاه آمار" مرکزی به دست آوریم. این قفل هرگز نباید مورد نیاز باشد.
نخ اصلی به طور دوره ای نیاز به هماهنگی با تمام موضوعات کارگر دارد. این کار با "انتشار" از نخ اصلی به رشته های کارگر و گاهی اوقات از موضوعات کارگر به نخ اصلی انجام می شود. ارسال نیاز به قفل دارد تا پیام منتشر شده برای تحویل بعدی در صف قرار گیرد. این قفل ها هرگز نباید به طور جدی مورد مناقشه قرار گیرند، اما همچنان می توانند از نظر فنی مسدود شوند.
هنگامی که Envoy یک گزارش در جریان خطای سیستم می نویسد (خطای استاندارد)، کل فرآیند را قفل می کند. به طور کلی، ورود به سیستم محلی Envoy از نقطه نظر عملکرد وحشتناک در نظر گرفته می شود، بنابراین توجه زیادی به بهبود آن نشده است.
چند قفل تصادفی دیگر وجود دارد، اما هیچ کدام از آنها عملکرد مهمی ندارند و هرگز نباید به چالش کشیده شوند.
موضوع ذخیره سازی محلی
از آنجایی که Envoy مسئولیتهای نخ اصلی را از مسئولیتهای نخ کارگر جدا میکند، این الزام وجود دارد که بتوان پردازش پیچیدهای را روی نخ اصلی انجام داد و سپس به هر نخ کارگری بهصورت کاملاً همزمان ارائه کرد. این بخش ذخیرهسازی محلی Envoy Thread (TLS) را در سطح بالایی توصیف میکند. در بخش بعدی نحوه استفاده از آن برای مدیریت یک خوشه را شرح خواهم داد.
همانطور که قبلاً توضیح داده شد، رشته اصلی تقریباً تمام عملکردهای صفحه مدیریت و کنترل در فرآیند Envoy را کنترل می کند. صفحه کنترل در اینجا کمی بیش از حد بارگذاری شده است، اما وقتی به آن در خود فرآیند Envoy نگاه می کنید و آن را با فورواردینگی که نخ های کارگر انجام می دهند مقایسه می کنید، منطقی است. قاعده کلی این است که فرآیند thread اصلی مقداری کار را انجام می دهد و سپس باید هر نخ worker را با توجه به نتیجه آن کار به روز کند. در این حالت، نخ کارگر نیازی به قفل در هر دسترسی ندارد.
سیستم TLS (ذخیره سازی محلی Thread) Envoy به شرح زیر عمل می کند:
کدهای در حال اجرا بر روی رشته اصلی می توانند یک اسلات TLS برای کل فرآیند اختصاص دهند. اگرچه این انتزاع است، اما در عمل یک شاخص در یک برداری است که دسترسی O(1) را فراهم می کند.
رشته اصلی می تواند داده های دلخواه را در اسلات خود نصب کند. هنگامی که این کار انجام شد، داده ها به عنوان یک رویداد معمولی حلقه رویداد در هر نخ کارگر منتشر می شود.
رشتههای کارگر میتوانند از اسلات TLS خود بخوانند و دادههای محلی موجود در آن را بازیابی کنند.
اگرچه این یک پارادایم بسیار ساده و فوق العاده قدرتمند است، اما بسیار شبیه به مفهوم مسدود کردن RCU (Read-Copy-Update) است. اساساً، تارهای کارگر هرگز هیچ تغییر داده ای را در اسلات های TLS در حین اجرای کار مشاهده نمی کنند. تغییر فقط در طول دوره استراحت بین رویدادهای کاری رخ می دهد.
Envoy از این به دو روش مختلف استفاده می کند:
با ذخیره داده های مختلف در هر رشته کارگر، می توان بدون هیچ گونه مسدودی به داده ها دسترسی داشت.
با حفظ یک اشاره گر مشترک به داده های جهانی در حالت فقط خواندنی در هر رشته کارگر. بنابراین، هر نخ کارگر دارای یک تعداد مرجع داده است که در حین اجرای کار نمی توان آن را کاهش داد. تنها زمانی که همه کارگران آرام شوند و داده های مشترک جدید را آپلود کنند، داده های قدیمی از بین می روند. این مشابه RCU است.
رشته به روز رسانی خوشه
در این بخش، نحوه استفاده از TLS (حافظه محلی Thread) برای مدیریت یک خوشه را توضیح خواهم داد. مدیریت خوشه شامل xDS API و/یا پردازش DNS و همچنین بررسی سلامت است.
مدیریت جریان خوشه شامل اجزا و مراحل زیر است:
Cluster Manager مؤلفهای در Envoy است که تمام بالادستیهای شناخته شده خوشه، APIهای Cluster Discovery Service (CDS)، APIهای سرویس کشف مخفی (SDS) و Endpoint Discovery Service (EDS)، DNS و بررسیهای خارجی فعال را مدیریت میکند. مسئول ایجاد یک نمای "در نهایت سازگار" از هر خوشه بالادستی است که شامل میزبان های کشف شده و همچنین وضعیت سلامتی می شود.
چک کننده سلامت یک بررسی فعال سلامت انجام می دهد و تغییرات وضعیت سلامت را به مدیر خوشه گزارش می دهد.
CDS (سرویس کشف خوشه) / SDS (سرویس کشف مخفی) / EDS (سرویس کشف نقطه پایانی) / DNS برای تعیین عضویت در خوشه انجام می شود. تغییر حالت به مدیر خوشه برگردانده می شود.
هر نخ کارگر به طور پیوسته یک حلقه رویداد را اجرا می کند.
هنگامی که مدیر خوشه تشخیص می دهد که وضعیت یک خوشه تغییر کرده است، یک عکس فوری فقط خواندنی از وضعیت خوشه ایجاد می کند و آن را به هر رشته کارگر ارسال می کند.
در طول دوره آرام بعدی، موضوع worker عکس فوری را در شکاف TLS اختصاص داده شده به روز می کند.
در طول یک رویداد I/O که قرار است تعادل میزبان تا بارگذاری را تعیین کند، متعادل کننده بار یک اسلات TLS (Thread local storage) را برای به دست آوردن اطلاعات در مورد میزبان درخواست می کند. این کار نیازی به قفل ندارد. همچنین توجه داشته باشید که TLS همچنین میتواند رویدادهای بهروزرسانی را راهاندازی کند تا متعادلکنندههای بار و سایر مؤلفهها بتوانند حافظه پنهان، ساختارهای داده و غیره را دوباره محاسبه کنند. این از حوصله این پست خارج است، اما در جاهای مختلف کد استفاده می شود.
با استفاده از روش فوق، Envoy می تواند هر درخواستی را بدون هیچ گونه مسدودی پردازش کند (به جز مواردی که قبلا توضیح داده شد). جدا از پیچیدگی خود کد TLS، بیشتر کدها نیازی به درک نحوه عملکرد چند رشته ای ندارند و می توان آنها را به صورت تک رشته ای نوشت. این کار علاوه بر عملکرد عالی، نوشتن بیشتر کدها را آسانتر می کند.
زیرسیستم های دیگری که از TLS استفاده می کنند
TLS (حافظه محلی Thread) و RCU (Read Copy Update) به طور گسترده در Envoy استفاده می شود.
نمونه هایی از استفاده:
مکانیسم تغییر عملکرد در حین اجرا: لیست فعلی عملکردهای فعال در رشته اصلی محاسبه می شود. سپس به هر نخ کارگر یک عکس فوری فقط خواندنی با استفاده از معنای RCU داده می شود.
تعویض جداول مسیر: برای جداول مسیر ارائه شده توسط RDS (Route Discovery Service)، جداول مسیر بر روی رشته اصلی ایجاد می شود. عکس فوری فقط خواندنی متعاقباً با استفاده از معنای RCU (Read Copy Update) برای هر رشته کارگر ارائه می شود. این باعث می شود تغییر جدول مسیرها از نظر اتمی کارآمد باشد.
ذخیره هدر HTTP: همانطور که مشخص است، محاسبه هدر HTTP برای هر درخواست (در حالی که ~25K+ RPS در هر هسته اجرا می شود) بسیار گران است. Envoy به طور متمرکز هدر را تقریباً هر نیم ثانیه محاسبه می کند و آن را از طریق TLS و RCU در اختیار هر کارگر قرار می دهد.
موارد دیگری نیز وجود دارد، اما مثالهای قبلی باید درک خوبی از آنچه TLS برای آن استفاده میشود، ارائه دهد.
مشکلات عملکرد شناخته شده
در حالی که Envoy به طور کلی بسیار خوب عمل می کند، چند زمینه قابل توجه وجود دارد که زمانی که با همزمانی و توان عملیاتی بسیار بالا استفاده می شود نیاز به توجه دارد:
همانطور که در این مقاله توضیح داده شد، در حال حاضر تمام رشته های کارگر هنگام نوشتن در بافر حافظه log دسترسی قفل می شوند. در زمان همزمانی بالا و توان عملیاتی بالا، باید لاگ های دسترسی را برای هر نخ کارگر به هزینه تحویل خارج از دستور هنگام نوشتن در فایل نهایی دسته بندی کنید. از طرف دیگر، میتوانید یک گزارش دسترسی جداگانه برای هر رشته کارگر ایجاد کنید.
اگرچه آمار بسیار بهینه شده است، اما در همزمانی و توان عملیاتی بسیار بالا احتمالاً اختلافات اتمی بر روی آمارهای فردی وجود خواهد داشت. راه حل این مشکل شمارنده های هر نخ کارگر با تنظیم مجدد دوره ای شمارنده های مرکزی است. در پست بعدی به این موضوع پرداخته خواهد شد.
اگر Envoy در سناریویی مستقر شود که در آن اتصالات بسیار کمی وجود دارد که به منابع پردازشی قابل توجهی نیاز دارند، معماری فعلی به خوبی کار نخواهد کرد. هیچ تضمینی وجود ندارد که اتصالات به طور مساوی بین رشته های کارگر توزیع شود. این را می توان با اجرای تعادل اتصال کارگر حل کرد، که امکان تبادل اتصالات بین رزوه های کارگر را فراهم می کند.
نتیجه
مدل threading Envoy به گونهای طراحی شده است که در صورت عدم پیکربندی صحیح، برنامهنویسی آسان و موازیسازی عظیم را به هزینه حافظه و اتصالات بالقوه هدر میدهد. این مدل به آن اجازه می دهد تا در تعداد نخ ها و توان عملیاتی بسیار بالا عملکرد بسیار خوبی داشته باشد.
همانطور که در توییتر به اختصار اشاره کردم، این طرح همچنین میتواند در بالای یک پشته شبکه کامل حالت کاربر مانند DPDK (کیت توسعه هواپیمای داده) اجرا شود، که میتواند منجر به مدیریت میلیونها درخواست در ثانیه توسط سرورهای معمولی با پردازش کامل L7 شود. بسیار جالب خواهد بود که ببینیم در چند سال آینده چه چیزی ساخته خواهد شد.
آخرین نظر سریع: بارها از من پرسیده شد که چرا C++ را برای Envoy انتخاب کردیم. دلیل این است که هنوز هم تنها زبان صنعتی پرکاربردی است که معماری توصیف شده در این پست را می توان با آن ساخت. C++ قطعا برای همه یا حتی بسیاری از پروژه ها مناسب نیست، اما برای موارد استفاده خاص هنوز تنها ابزار برای انجام کار است.
لینک به کد
پیوندهایی به فایلها با رابطها و پیادهسازی هدر که در این پست مورد بحث قرار گرفتهاند: