[Пераклад] Envoy мадэль патокаў (Envoy threading model)

Пераклад артыкула: Envoy threading model - https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310

Дадзены артыкул здаўся мне досыць цікавай, а бо Envoy часцей за ўсё выкарыстоўваецца як частка "istio" ці проста як "ingress controller" kubernetes, такім чынам большасць людзей не маюць з ім такога ж прамога ўзаемадзеяння як напрыклад з тыпавымі ўстаноўкамі Nginx або Haproxy. Аднак калі нешта ламаецца, было б добра разумець як яно ўладкована знутры. Я пастараўся перакласці як мага больш тэксту на рускую мову ў тым ліку і спецыяльныя словы, для тых каму балюча на такое глядзець я пакінуў арыгіналы ў дужках. Сардэчна запрашаем пад кат.

Тэхнічная дакументацыя нізкага ўзроўню па кодавай базе Envoy у цяперашні час даволі бедная. Каб выправіць гэта, я планую зрабіць серыю артыкулаў у блогу аб розных падсістэмах Envoy. Так як гэта першы артыкул, калі ласка, дайце мне ведаць, што вы думаеце, і што вам магло быць цікава ў наступных артыкулах.

Адно з найболей распаўсюджаных тэхнічных пытанняў, якія я атрымліваю аб Envoy, гэта запыт на нізкаўзроўневае апісанне выкарыстоўванай мадэлі струменяў (threading model). У гэтым пасце я апішу як Envoy супастаўляе злучэнні са струменямі, а таксама апісанне сістэмы лакальнага сховішчы струменяў (Thread Local Storage), якая выкарыстоўваецца ўсярэдзіне, каб зрабіць код больш раўналежным і высокапрадукцыйным.

Апісанне патокаў (Threading overview)

[Пераклад] Envoy мадэль патокаў (Envoy threading model)

Envoy выкарыстоўвае тры розных тыпу патокаў:

  • Асноўны (Main): Гэты струмень кіруе запускам і завяршэннем працэсу, усёй апрацоўкай XDS (xDiscovery Service) API, уключаючы DNS, праверку працаздольнасці (health checking), агульнае кіраванне кластарам і працэсам працы сэрвісу (runtime), скідам статыстыкі, адміністраванне і агульнае кіраванне працэсамі – Linux сігналы, гарачы перазапуск (hot restart) і т. д. Усё, што адбываецца ў гэтым струмені, з'яўляецца асінхронным і «неблакавальным». У цэлым асноўны паток каардынуе ўсе крытычныя працэсы функцыянальнасці, для выканання якіх не патрабуецца вялікай колькасці ЦПУ. Гэта дазваляе большую частку кода кіравання пісаць так, як калі б ён быў аднаструменным.
  • Рабочы (Worker): Па змаўчанні Envoy стварае працоўны струмень(worker thread) для кожнага апаратнага струменя ў сістэме, гэта можна кантраляваць з дапамогай опцыі --concurrency. Кожны працоўны струмень запускае «неблакіруючы» цыкл падзей (event loop), які адказвае за праслухоўванне (listening) кожнага праслухоўніка (listener), на момант напісання артыкула (29 ліпеня 2017 г.) няма сегментавання (sharding) праслухоўніка (listener), прыём новых злучэнняў, стварэнне асобніка стэка фільтраў для падлучэння і апрацоўку ўсіх аперацый уводу-высновы (IO) за час існавання злучэння. Зноў жа, гэта дазваляе большую частку кода апрацоўкі злучэнняў пісаць так, як калі б ён быў аднаструменным.
  • Файлавы (File flusher): Кожны файл, які піша Envoy, у асноўным часопісы доступу (access logs), у цяперашні час мае незалежны блакавальны струмень. Гэта злучана з тым, што запіс у файлы кэшаваныя файлавай сістэмай нават пры выкарыстанні O_NONBLOCK часам можа блакавацца (уздых). Калі працоўным струменям неабходна запісаць у файл, дадзеныя фактычна перамяшчаюцца ў буфер у памяці, дзе яны ў канчатковым выніку скідаюцца праз струмень file flush. Гэта адна з абласцей кода, у якой тэхнічна ўсе працоўныя струмені (worker threads) могуць блакаваць (block) адну і тую ж блакіроўку (lock), спрабуючы запоўніць буфер памяці.

Апрацоўка злучэнняў (Connection handling)

Як абмяркоўвалася сцісла вышэй, усе працоўныя струмені праслухоўваюць усіх слухачоў (listeners) без якога-небудзь сегментавання. Такім чынам, ядро ​​выкарыстоўваецца для пісьменнай адпраўкі прынятых сокетаў у працоўныя струмені. Сучасныя ядра ў цэлым вельмі добрыя ў гэтым, яны выкарыстоўваюць такія функцыі, як павышэнне прыярытэту ўводу-вываду (IO), каб паспрабаваць запоўніць паток працай, перш чым пачаць выкарыстоўваць іншыя патокі, якія таксама праслухоўваюць той жа сокет, а таксама не выкарыстоўваць цыклічную блакіроўку (Spinlock) для апрацоўкі кожнага запыту.
Як толькі злучэнне прынята на працоўным струмені (worker thread), яно ніколі не пакідае гэты струмень (thread). Уся далейшая апрацоўка злучэння цалкам апрацоўваецца ў працоўным струмені (worker thread), уключаючы любыя паводзіны перасылання (forwarding behavior).

Гэта мае некалькі важных наступстваў:

  • Усе пулы злучэнняў у Envoy ставяцца да працоўнага струменя. Такім чынам, хоць пулы злучэнняў HTTP/2 робяць толькі адно злучэнне з кожным вышэйстаячым хастом за раз, калі ёсць чатыры працоўных струменя, будзе чатыры злучэння HTTP/2 на вышэйстаячы хост ва ўстойлівым стане.
  • Чыннік, па якой Envoy працуе такім чынам, складаецца ў тым, што, захоўваючы ўсё ў адным працоўным струмені, амаль увесь код можа быць напісаны без блакаванняў і як быццам ён аднаструменны. Гэты дызайн спрашчае напісанне вялікай колькасці кода і неверагодна добра маштабуецца для амаль неабмежаванага ліку працоўных струменяў.
  • Аднак, адной з асноўных высноваў з'яўляецца тое, што з пункту гледжання эфектыўнасці пула памяці і злучэнняў насамрэч вельмі важна наладзіць параметр --concurrency. Наяўнасць большай колькасці працоўных струменяў, чым неабходна, прывядзе да страты памяці, стварэнню большай колькасці бяздзейных злучэнняў і зніжэнню хуткасці траплення ў пул злучэнняў. У Lyft нашы envoy sidecar кантэйнеры працуюць з вельмі нізкім паралелізмам, так што прадукцыйнасць прыкладна адпавядае службам, побач з якімі яны сядзяць. Мы запускаем Envoy у якасці памежнага проксі-сервера (edge) толькі пры максімальным паралелізме (concurrency).

Што азначае не блакуючы рэжым (What non-blocking means)

Тэрмін «неблакіруючы» да гэтага часу выкарыстоўваўся некалькі разоў пры абмеркаванні таго, як працуюць асноўны і працоўны патокі. Увесь код напісаны пры ўмове, што нішто ніколі не блакіруецца. Аднак, гэта не зусім дакладна (што не зусім дакладна?).

Envoy выкарыстоўвае некалькі працяглых блакіровак працэсу:

  • Як ужо гаварылася, пры запісе часопісаў доступу ўсе працоўныя струмені атрымліваюць аднолькавую блакіроўку перад запаўненнем буфера часопіса ў памяці. Час утрымання блакіроўкі павінна быць вельмі нізкім, але магчыма, што гэтая блакіроўка будзе аспрэчвацца пры высокім раўналізме і высокай прапускной здольнасці.
  • Envoy выкарыстоўвае вельмі складаную сістэму для апрацоўкі статыстыкі, якая з'яўляецца лакальнай для патоку. Гэта будзе тэма асобнага паста. Тым не менш, я коратка згадаю, што як частка лакальнай апрацоўкі статыстыкі патоку часам патрабуецца атрымаць блакіроўку для цэнтральнага "сховішча статыстыкі". Гэтая блакіроўка не павінна калі-небудзь патрабавацца.
  • Асноўны струмень перыядычна мае патрэбу ў каардынацыі са ўсімі працоўнымі струменямі. Гэта робіцца шляхам "публікацыі" з асноўнага струменя ў працоўныя струмені, а часам і з працоўных струменяў зваротна ў асноўны струмень. Для адпраўкі патрабуецца блакіроўка, каб апублікаванае паведамленне можна было змясціць у чаргу для наступнай дастаўкі. Гэтыя блакіроўкі ніколі не павінны падвяргацца сур'ёзнаму суперніцтву, але яны ўсё роўна могуць тэхнічна блакавацца.
  • Калі Envoy піша часопіс у сістэмны струмень памылак (standard error), ён атрымлівае блакаванне ўсяго працэсу. У цэлым, лакальнае вядзенне часопіса Envoy лічыцца жудасным з пункту гледжання прадукцыйнасці, таму яго паляпшэнню не надаецца шмат увагі.
  • Ёсць некалькі іншых выпадковых блакіровак, але ні адна з іх не з'яўляецца крытычнай для прадукцыйнасці і ніколі не павінна аспрэчвацца.

Лакальнае сховішча патоку (Thread local storage)

З-за спосабу, якім Envoy адлучае абавязкі асноўнага струменя ад абавязкаў працоўнага струменя, існуе патрабаванне, што складаная апрацоўка можа быць выканана ў галоўным струмені, а затым прадстаўлена кожнаму працоўнаму струменю з высокай ступенню паралелізму. У гэтай частцы апісана сістэма Envoy Thread Local Storage (TLS) на высокім узроўні. У наступным раздзеле я апішу, як ён выкарыстоўваецца для кіравання кластарам.
[Пераклад] Envoy мадэль патокаў (Envoy threading model)

Як ужо было апісана, асноўны струмень апрацоўвае практычна ўсе функцыі кіравання (management) і функцыянальнасць плоскасці кіравання (control plane) падчас Envoy. Плоскасць кіравання тут крыху перагружана, але калі разглядаць яе ў рамках самога працэсу Envoy і параўноўваць з перасыланнем, якую выконваюць працоўныя струмені, гэта ўяўляецца мэтазгодным. Па агульным правіле працэс асноўнага струменя выконвае некаторую працу, а затым яму неабходна абнаўляць кожны працоўны струмень у адпаведнасці з вынікам гэтай працы, пры гэтым працоўнай плыні не трэба ўсталёўваць блакіроўку пры кожным доступе..

Сістэма TLS (Thread local storage) Envoy працуе наступным чынам:

  • Код, які выконваецца ў асноўным струмені, можа вылучыць слот TLS для ўсяго працэсу. Хоць гэта абстрагавана, на практыцы гэта азначнік у вектары, які забяспечвае доступ O(1).
  • Асноўная плынь можа ўсталёўваць адвольныя дадзеныя ў свой слот. Калі гэта зроблена, дадзеныя публікуюцца ў кожным працоўным струмені як звычайная падзея цыклу падзей.
  • Рабочыя патокі могуць чытаць са свайго слота TLS і здабываць любыя лакальныя дадзеныя патокаў, даступныя там.

Хоць гэта вельмі простая і неверагодна магутная парадыгма, якая вельмі падобная да канцэпцыі блакавання RCU (Read-Copy-Update). У сутнасці, працоўныя струмені ніколі не бачаць якія-небудзь змен дадзеных у слотах TLS падчас выканання працы. Змена адбываецца толькі ў перыяд спакою паміж працоўнымі падзеямі.

Envoy выкарыстоўвае гэта двума рознымі спосабамі:

  • Захоўваючы розныя дадзеныя на кожным працоўным струмені, доступ да гэтых дадзеных ажыццяўляецца без які-небудзь блакаванні.
  • Захоўваючы агульны паказальнік на глабальныя дадзеныя ў рэжыме "толькі для чытання" на кожным працоўным струмені. Такім чынам, кожны працоўны струмень мае лічыльнік спасылак на дадзеныя, які не можа быць паменшаны падчас выканання працы. Толькі калі ўсе работнікі супакояцца і загрузяць новыя агульныя даныя, старыя даныя будуць знішчаны. Гэта ідэнтычна RCU.

Струмень абнаўлення кластара (Cluster update threading)

У гэтай частцы я апішу, як TLS (Thread local storage) выкарыстоўваецца для кіравання кластарам. Упраўленне кластарам ўключае апрацоўку API xDS і / або DNS, а таксама праверку працаздольнасці (health checking).
[Пераклад] Envoy мадэль патокаў (Envoy threading model)

Кіраванне патокамі кластара ўключае ў сябе наступныя кампаненты і этапы:

  1. Мэнэджар кластара – гэта кампанент усярэдзіне Envoy, які кіруе ўсімі вядомымі апстрымамі (upstream) кластара, API-інтэрфейсам CDS (Cluster Discovery Service), API-інтэрфейсамі SDS (Secret Discovery Service) і EDS (Endpoint Discovery Service), DNS і актыўнымі вонкавымі праверкамі працаздольнасці (health checking). Ён адказвае за стварэнне ў "канчатковым выніку ўзгодненага" (eventually consistent) прадстаўлення кожнага апстрыму (upstream) кластара, які ўключае выяўленыя хасты, а таксама стан працаздольнасці (health status).
  2. Сродак праверкі працаздольнасці (health checker) выконвае актыўную праверку працаздольнасці і паведамляе аб зменах стану працаздольнасці дыспетчару кластара.
  3. CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Endpoint Discovery Service) / DNS выконваюцца для вызначэння прыналежнасці да кластара. Змяненне стану вяртаецца мэнэджэра кластара.
  4. Кожны працоўны струмень стала выконвае цыкл апрацоўкі падзей.
  5. Калі менеджэр кластара вызначае, што стан для кластара змянілася, ён стварае новы здымак стану кластара, даступны толькі для чытання, і адпраўляе яго ў кожны працоўны струмень.
  6. На працягу наступнага перыяду супакою працоўны струмень абновіць здымак у вылучаным слоце TLS.
  7. Падчас падзеі ўводу-высновы, якое павінна вызначыць хост для балансавання нагрузкі, балансавальнік нагрузкі будзе запытваць слот TLS (Thread local storage) для атрымання інфармацыі аб хасце. Для гэтага не патрабуецца блакіровак. Звярніце ўвагу таксама, што TLS можа таксама ініцыяваць падзеі пры абнаўленні, так што падсістэмы балансавання нагрузкі і іншыя кампаненты могуць пералічваць кэшы, структуры дадзеных і т.д. Гэта выходзіць за рамкі гэтага паста, але выкарыстоўваецца ў розных месцах кода.

Выкарыстоўваючы вышэйапісаную працэдуру, Envoy можа апрацоўваць кожны запыт без якіх-небудзь блакіровак (акрамя апісаных раней). Акрамя складанасці самага кода TLS, большай частцы кода не трэба разумець, як працуе шматструменнасць, і ён можа быць напісаны ў аднаструменным рэжыме. Гэта палягчае напісанне большай частцы кода ў дадатак да найвышэйшай прадукцыйнасці.

Іншыя падсістэмы, якія выкарыстоўваюць TLS (Other subsystems that make use of TLS)

TLS (Thread local storage) і RCU (Read Copy Update) шырока выкарыстоўваюцца ў Envoy.

Прыклады выкарыстання:

  • Механізм змены функцыянальнасці падчас выкананні: Бягучы спіс уключанага функцыяналу вылічаецца ў асноўным патоку. Затым кожнаму працоўнаму струменю падаецца здымак толькі для чытання з выкарыстаннем семантыкі RCU.
  • Замена табліц маршрутаў: для табліц маршрутаў, якія прадстаўляюцца RDS (Route Discovery Service), табліцы маршрутаў ствараюцца ў асноўным патоку. Здымак толькі для чытання ў далейшым будзе прадстаўлены кожнаму працоўнаму струменю з выкарыстаннем семантыкі RCU (Read Copy Update). Гэта робіць змяненне табліц маршрутаў атамарна эфектыўным.
  • Кэшаванне загалоўкаў HTTP: Як высвятляецца, вылічэнне загалоўка HTTP для кожнага запыту (пры выкананні ~25K+ RPS на ядро) даволі дорага. Envoy цэнтралізавана вылічае загаловак прыкладна кожныя паўсекунды і дае яго кожнаму работніку праз TLS і RCU.

Ёсць і іншыя выпадкі, але папярэднія прыклады павінны забяспечыць добрае разуменне таго, навошта выкарыстоўваецца TLS.

Вядомыя падводныя камяні прадукцыйнасці (Known performance pitfalls)

Хоць у цэлым Envoy працуе дастаткова добра, ёсць некалькі вядомых абласцей, якія патрабуюць увагі, калі ён выкарыстоўваецца з вельмі высокім паралелізмам і прапускной здольнасцю:

  • Як ужо апісана ў гэтым артыкуле, у наш час усе працоўныя струмені атрымліваюць блакаванне пры запісе ў буфер памяці часопіса доступу. Пры высокім паралелізме і высокай прапускной здольнасці запатрабуецца выканаць пакетаванне часопісаў доступу для кожнага працоўнага струменя за рахунак неўпарадкаванай дастаўкі пры запісе ў канчатковы файл. Як альтэрнатыву, можна ствараць асобны часопіс доступу для кожнага працоўнага струменя.
  • Хаця статыстыка вельмі моцна аптымізавана, пры вельмі высокім паралелізме і прапускной здольнасці, верагодна, будзе атамарная канкурэнцыя на індывідуальнай статыстыцы. Рашэнне гэтай праблемы - лічыльнікі на адзін працоўны струмень з перыядычным скідам цэнтральных лічыльнікаў. Гэта будзе абмяркоўвацца ў наступным пасце.
  • Існуючая архітэктура не будзе працаваць добра, калі Envoy разгорнуты ў сцэнары, у якім вельмі мала злучэнняў, якія патрабуюць значных рэсурсаў для апрацоўкі. Няма гарантыі, што сувязі будуць раўнамерна размеркаваны паміж працоўнымі патокамі. Гэта можа быць вырашана шляхам рэалізацыі балансавання працоўных злучэнняў, пры якой будзе рэалізаваная магчымасць абмену злучэннямі паміж працоўнымі струменямі.

Заключэнне (Conclusion)

Мадэль патокаў Envoy распрацавана для забеспячэння прастаты праграмавання і масавага паралелізму за кошт патэнцыйна марнатраўнага выкарыстання памяці і злучэнняў, калі яны не настроены правільна. Гэтая мадэль дазваляе яму вельмі добрае працаваць пры вельмі высокай колькасці струменяў і прапускной здольнасці.
Як я коратка згадаў у Твітары, дызайн таксама можа працаваць па-над поўнафункцыянальным сеткавым стэкам у рэжыме карыстача, такога як DPDK (Data Plane Development Kit), што можа прывесці да таго, што звычайныя серверы будуць апрацоўваць мільёны запытаў у секунду пры поўнай апрацоўцы L7. Будзе вельмі цікава паглядзець, што будзе пабудавана ў найбліжэйшыя некалькі гадоў.
Адзін апошні хуткі каментар: мяне шмат разоў пыталіся, чаму мы выбралі C++ для Envoy. Чыннік па-ранейшаму складаецца ў тым, што гэта ўсё яшчэ адзіная шырока распаўсюджаная мова прамысловага ўзроўня, на якім можна пабудаваць архітэктуру, апісаную ў гэтым пасце. C++ вызначана не падыходзіць для ўсіх ці нават для шматлікіх праектаў, але для вызначаных выпадкаў выкарыстання гэта ўсё яшчэ адзіная прылада для выканання працы (to get the job done).

Спасылкі на код (Links to code)

Спасылкі на файлы з інтэрфейсамі і рэалізацыяй загалоўкаў, якія абмяркоўваюцца ў гэтым пасце:

Крыніца: habr.com

Дадаць каментар