Përkthimi i artikullit:
Ky artikull më duket mjaft interesant, dhe për shkak se Envoy përdoret më së shpeshti si pjesë e "istio" ose thjesht si "ingress controller" i kubernetes, shumica e njerëzve nuk e kanë një ndërlidhje të drejtpërdrejtë me të siç kanë me instalime tipike si Nginx ose Haproxy. Megjithatë, kur diçka dësht merr, do të ishte mirë të kuptosh se si funksionon nga brenda. Kam përballuar të përkthej sa më shumë tekst në rusisht, duke përfshirë edhe fjalët speciale; për ata që u duket e dhimbshme të shohin, kam lënë origjinalet në kllapa. Mirë se vini nën!
Dokumentacioni teknik i nivelit të ulët për bazën e kodit të Envoy aktualisht është mjaft i varfër. Për ta rregulluar këtë, planifikoj të bëj një seri artikujsh në blog për nën-sistemet e ndryshme të Envoy. Duke qenë se ky është artikulli i parë, ju lutem, më tregoni çfarë mendoni dhe çfarë mund të ishte interesante në artikujt e ardhshëm.
Një nga pyetjet më të zakonshme teknike që marr për Envoy është kërkesa për një përshkrim të nivelit të ulët të modelit të rrjedhjes së përdorur (threading model). Në këtë post, do të përshkruaj se si Envoy lidhet lidhjet me rrjedhat, si dhe përshkrimin e sistemit të ruajtjes lokale të rrjedhave (Thread Local Storage), i cili përdoret brenda për ta bërë kodin më paralel dhe me performancë të lartë.
Përshkrimi i Rrjedhave (Threading overview)
![[Перевод] Modeli e rrjedhjes së Envoy (Envoy threading model)](/wp-content/uploads/2019/04/c5028463db1eb5b07ccb1a3af1f64a03.jpeg)
Envoy përdor tri lloje të ndryshme rrjedhash:
- Kryesore (Main): Ky rrjedh menaxhon nisjen dhe përfundimin e procesit, të gjitha përpunimet e XDS (xDiscovery Service) API, duke përfshirë DNS, verifikimin e shëndetit (health checking), menaxhimin e përgjithshëm të klasterit dhe procesin e funksionimit të shërbimit (runtime), ri-nxisjen e statistikave, administrimin dhe menaxhimin e përgjithshëm të proceseve — sinjalet Linux, rinstalimin e nxehtë (hot restart) etj. Çdo gjë që ndodh në këtë rrjedh është asinkrone dhe "jo bllokuese". Në përgjithësi, rrjedha kryesore koordinon të gjithë proceset kritike të funksionalitetit, për kryerjen e të cilave nuk kërkohet një sasi e madhe CPU. Kjo lejon që një pjesë e madhe e kodit të menaxhimit të shkruhet ashtu siç do të ishte një një-rrjedhësh.
- Përgjegjës (Worker): Po ashtu, Envoy krijon një rrjedhë punuese (worker thread) për çdo rrjedhë harduerike në sistem, kjo mund të kontrollohet me anë të opsionit
--konkurenca. Çdo rrjedhë punuese nis një cikël ngjarjesh «jo bllokues» (event loop), i cili është përgjegjës për për të dëgjuar (listening) çdo dëgjues (listener). Në momentin e shkruarjes së këtij artikulli (29 korrik 2017), nuk ka segmentim (sharding) të dëgjuesit (listener), pranimin e lidhjeve të reja, krijimin e një shembulli të grumbullit të filtrave për lidhjen dhe përpunimin e të gjitha operacioneve të hyrjes-daljes (IO) gjatë ekzistencës së lidhjes. Përsëri, kjo lejon që shumica e kodit për përpunimin e lidhjeve të shkruhet ashtu sikur të ishte një rrjedhë punuese e vetme. - Shkarkimi i skedarëve (File flusher): Çdo skedar që shkruan Envoy, kryesisht regjistrat e aksesit (access logs), aktualisht ka një rrjedhë bllokuese të pavarur. Kjo është për shkak se shkruajtja në skedarë të cachuar nga sistemi i skedarëve, madje edhe në përdorimin e
O_NONBLOCKndonjëherë mund të bllokohet (suspenso). Kur rrjedhat e punës duhet të shkruajnë në skedar, të dhënat në të vërtetë lëvizin në një buffer në memorie, ku ato në fund shpërndahen përmes rrjedhës file flush. Kjo është një nga fushat e kodit, ku teknisht të gjitha rrjedhat e punës (worker threads) mund të bllokojnë (block) të njëjtin bllokim (lock), duke përpiqun të mbushin buffer-in e memories.
Menaxhimi i lidhjeve (Connection handling)
Siç u diskutua shkurtimisht më sipër, të gjitha rrjedhat e punës dëgjojnë çdo dëgjues (listeners) pa ndonjë segmentim. Kështu, bërthama përdoret për dërgimin e duhur të soketeve të pranuara në rrjedhat e punës. Bërthamqat moderne në përgjithësi janë shumë të mira në këtë, ato përdorin funksione të tilla si rritja e përparësisë së hyrjes-daljes (IO), për të përpiqun të mbushin rrjedhën me punë, para se të fillojnë të përdorin rrjedha të tjera që gjithashtu dëgjojnë të njëjtin soket, si dhe për të mos përdorur bllokimin rrethues (Spinlock) për të trajtuar çdo kërkesë.
Sapo lidhja të pranohet në një rrjedhë pune (worker thread), ajo kurrë nuk largohet nga kjo rrjedhë (thread). Të gjitha përpunimet e mëtejshme të lidhjes trajtohen plotësisht në rrjedhën e punës (worker thread), duke përfshirë çdo sjellje dërguese (forwarding behavior).
Kjo ka disa pasoja të rëndësishme:
- Të gjitha grupe të lidhjeve në Envoy i përkasin rrjedhës së punës. Prandaj, megjithëse grupet e lidhjeve HTTP/2 krijojnë vetëm një lidhje me çdo host të lartë në një kohë, nëse ka katër rrjedha pune, do të ketë katër lidhje HTTP/2 me hostin e lartë në një gjendje të qëndrueshme.
- Arsyeja pse Envoy funksionon në këtë mënyrë është se, duke e mbajtur gjithçka në një rrjedhë pune, pothuajse e gjithë kodi mund të shkruhet pa blloqe dhe siç duket se është një njësi punuese. Ky dizajn e thjeshton shkrimin e një sasi të madhe kodi dhe shkallëzohet jashtëzakonisht mirë për një numër pothuajse të pakufizuar rrjedhash punuese.
- Megjithatë, një nga përfundimet kryesore është se, nga pikëpamja e efikasitetit, grupi i memories dhe lidhjeve në të vërtetë është shumë e rëndësishme të konfigurohet parametri
--konkurenca. Të kesh më shumë rrjedha pune sesa është e nevojshme do të çojë në humbje të memories, krijimin e më shumë lidhjeve të papërdorura dhe uljen e shpejtësisë së hyrjes në grupin e lidhjeve. Në Lyft, konteinerit tanë të envoy sidecar punojnë me paralelizëm shumë të ulët, kështu që performanca është afërsisht në përputhje me shërbimet pranë të cilave ndodhen. Ne e ekzekutojmë Envoy si një proxy të jashtëm (edge) vetëm kur ka maksimum paralelizmi (concurrency).
Çfarë do të thotë mënyra jo bllokues
Termi "jo bllokues" është përdorur disa herë gjatë diskutimeve mbi mënyrën se si funksionojnë rrjedha kryesore dhe rrjedhat punuese. E gjithë kodi është shkruar me kushtin që asgjë kurrë nuk bllokohet. Megjithatë, këtë nuk është plotësisht e saktë (çfarë nuk është plotësisht e saktë?).
Envoy përdor disa bllokime të zgjatura të procesit:
- Siç u përmend më parë, gjatë regjistrimit të qasjes, të gjitha rrjedhat pune marrin të njëjtin bllokim para se të mbushin tamponin e regjistrit në memorje. Koha e mbajtjes së bllokimit duhet të jetë shumë e ulët, por është e mundur që ky bllokim të kontestohet me paralelizëm të lartë dhe kapacitet të lartë.
- Envoy përdor një sistem shumë të komplikuar për të përpunuar statistikën, i cili është lokal për rrjedhën. Kjo do të jetë një temë e një postimi të veçantë. Megjithatë, do ta përmend shkurtimisht se si pjesë e përpunimit lokal të statistikës së rrjedhës, disa herë kërkohet të merrni një bllokim për "depon statistikore" qendrore. Ky bllokim nuk duhet kurrë të kërkohet.
- Rrjedha kryesore ka nevojë periodikisht për koordinim me të gjitha rrjedhat e punës. Kjo bëhet përmes "publikimit" nga rrjedha kryesore në rrjedhat e punës, dhe ndonjëherë nga rrjedhat e punës prapa në rrjedhën kryesore. Për dërgimin kërkohet një bllokim, në mënyrë që mesazhi i publikuar të mund të vihet në radhë për dorëzim të mëvonshëm. Këto bllokime kurrë nuk duhet të përballen me konkurrencë serioze, por ato prapë mund të bllokohen teknikisht.
- Kur Envoy shkruan në ditarin e rrjedhës së gabimeve (standard error), ai merr një bllokim të gjithë procesit. Për përgjithësi, regjistrimi lokal i Envoy konsiderohet i tmerrshëm për performancën, prandaj nuk i kushtohet shumë vëmendje përmirësimit të tij.
- Ka disa bllokime të tjera rastësore, por asnjëra prej tyre nuk është kritike për performancën dhe nuk duhet kurrë të kontestohet.
Depo lokale e rrjedhave (Thread local storage)
Në shkak të mënyrës se si Envoy ndan detyrat e rrjedhës kryesore nga detyrat e rrjedhës së punës, ekziston një kërkesë që përpunimi i komplikuar mund të bëhet në rrjedhën kryesore dhe pastaj t'i jepet secilës rrjedhë të punës me një shkallë të lartë paralelizmi. Në këtë seksion, përshkruhet sistemi i Envoy Thread Local Storage (TLS) në një nivel të lartë. Në seksionin tjetër, do ta përshkruaj se si përdoret për të menaxhuar klasterin.
![[Перевод] Modeli e rrjedhjes së Envoy (Envoy threading model)](/wp-content/uploads/2019/04/a47af1e8eb1f55a4d7609b3ff3ee9ce1.jpeg)
Siç është përshkruar më parë, rrjedha kryesore përpunon praktikisht të gjitha funksionet e menaxhimit dhe funksionalitetin e planit të kontrollit në procesin e Envoy. Plani i kontrollit këtu është paksa i ngarkuar, por nëse e shikoni atë brenda vetë procesit të Envoy dhe e krahasoni me përcjelljen që e bëjnë rrjedhat e punës, kjo duket e arsyeshme. Rregulli i përgjithshëm është që procesi i rrjedhës kryesore kryen një punë të caktuar dhe pastaj duhet të azhurnojë çdo rrjedhë pune sipas rezultatit të kësaj pune. në këtë mënyrë, rrjedha e punës nuk ka nevojë të vendosë bllokimin për çdo qasje.
Sistemi TLS (Storage i lokalizuar për thënit) Envoy punon si më poshtë:
- Kodi që ekzekutohet në rrjedhën kryesore mund të ndajnë një slot TLS për të gjithë procesin. Edhe pse kjo është e abstraguar, në praktikë, kjo është një indeks në një vektor që siguron qasje O(1).
- Rrjedha kryesore mund të vendosë të dhëna të arbitrave në slotin e saj. Kur bëhet kjo, të dhënat publikohen në çdo rrjedhë pune si një ngjarje e zakonshme e ciklit të ngjarjeve.
- Rrjedhat e punës mund të lexojnë nga sloti i tyre TLS dhe të nxjerrin çdo të dhënë lokale të rrjedhës që është e disponueshme aty.
Edhe pse kjo është një paradigmë shumë e thjeshtë dhe jashtëzakonisht e fuqishme, që është shumë e ngjashme me konceptin e bllokimit RCU (Read-Copy-Update). Në thelb, rrjedhat e punës kurrë nuk shohin ndonjë ndryshim të të dhënave në slotet TLS gjatë ekzekutimit të punës. Ndryshimi ndodh vetëm në periudhën e pushimit midis ngjarjeve të punës.
Envoy e përdor këtë në dy mënyra të ndryshme:
- Duke ruajtur të dhëna të ndryshme në çdo rrjedhë pune, qasja në këto të dhëna bëhet pa asnjë bllokim.
- Duke ruajtur një tregues të përbashkët për të dhënat globale në modalitetin "vetëm për lexim" në çdo rrjedhë pune. Kështu, çdo rrjedhë pune ka një counter referencash për të dhënat që nuk mund të reduktohet gjatë ekzekutimit të punës. Vetëm kur të gjitha punëtorët të qetësohen dhe të ngarkojnë të dhëna të reja të përbashkëta, të dhënat e vjetra do të shkatërrohen. Kjo është identike me RCU.
Rrjedha e azhurnimit të klasterit (Cluster update threading)
Në këtë seksion, do të përshkruaj se si TLS (Storage i lokalizuar për thënit) përdoret për menaxhimin e klasterit. Menaxhimi i klasterit përfshin trajtimin e API xDS dhe / ose DNS, si dhe kontrollet e shëndetit (health checking).
![[Перевод] Modeli e rrjedhjes së Envoy (Envoy threading model)](/wp-content/uploads/2019/04/238f5718729e1f12f8f0d6507f16abe8.jpeg)
Menaxhimi i rrjedhave të klasterit përfshin komponentë dhe etapa si më poshtë:
- Menaxheri i klasterit është një komponent brenda Envoy, i cili menaxhon të gjitha upstream të njohura të klasterit, API-të CDS (Shërbimi i Zbulimit të Klasterit), API-të SDS (Shërbimi i Zbulimit të Sekreteve) dhe EDS (Shërbimi i Zbulimit të Pikave), DNS dhe kontrollet aktive të jashtme të shëndetit. Ai është përgjegjës për krijimin e një pamjeje "përfundimisht të qëndrueshme" (eventually consistent) për çdo upstream të klasterit, e cila përfshin hostet e zbuluara, si dhe statusin e shëndetit.
- Mjeti i kontrollit të gjendjes (health checker) kryen një verifikim aktiv të gjendjes dhe raporton ndryshimet e gjendjes së funksionimit te menaxheri i klasterit.
- CDS (Shërbimi i Zbulimit të Klasterit) / SDS (Shërbimi i Zbulimit të Sekreteve) / EDS (Shërbimi i Zbulimit të Pikave të Ndeshjes) / DNS realizohen për të përcaktuar përkatësinë në klaster. Ndryshimi i gjendjes kthehet te menaxheri i klasterit.
- Çdo thread pune kryen vazhdimisht një cikël të përpunimit të ngjarjeve.
- Kur menaxheri i klasterit përcakton se gjendja për klasterin ka ndryshuar, krijon një imazh të ri të gjendjes së klasterit, të disponueshëm vetëm për lexim, dhe e dërgon atë në çdo thread pune.
- Gjatë periudhës së ardhshme të pushimit, thread-i i punës do të përditësojë imazhin në slot-in e dedikuar TLS.
- Gjatë një ngjarjeje input-output, e cila duhet të përcaktojë host-in për balancimin e ngarkesës, balancuesi i ngarkesës do të kërkojë slot-in TLS (Storage lokale e thread-it) për të marrë informacionin mbi host-in. Për këtë nuk nevojiten bllokime. Vini re gjithashtu se TLS mund të iniciojë ngjarje gjatë përditësimit, kështu që nën-sistemet e balancimit të ngarkesës dhe komponentët e tjerë mund të ripërkdhejnë cache-t, struktura të dhënash etj. Ky aspekt kalon përtej këtij postimi, por përdoret në vende të ndryshme në kod.
Duke përdorur procedurën e përshkruar më sipër, Envoy mund të përpunojë çdo kërkesë pa asnjë bllokim (përveç atyre të përmendura më parë). Përveç kompleksitetit të kodit TLS, pjesa më e madhe e kodit nuk ka nevojë të kuptojë se si funksionon multithreading dhe mund të shkruhet në një mod që një thread. Kjo lehtëson shkrimin e pjesës më të madhe të kodit përveç performancës së shkëlqyer.
Nën-sistemet e tjera që përdorin TLS
TLS (Storage lokale e thread-it) dhe RCU (Përditësimi i Leximit të Kopjes) përdoren gjerësisht në Envoy.
Shembuj të përdorimeve:
- Mekanizmi i ndryshimit të funksionalitetit në kohën e ekzekutimit: Lista aktuale e funksionaliteteve të përfshira llogaritet në thread-in kryesor. Pastaj, çdo thread pune merr një imazh vetëm për lexim duke përdorur semantikën RCU.
- Zëvendësimi i tabelave të ruteve: për tabelat e rrugëve, të ofruara nga RDS (Shërbimi i Zbulimit të Rrugëve), tabelat e rrugëve krijohen në rrjedhën kryesore. Një snapshot që lexon vetëm do t'i ofrohet më vonë çdo rrjedhe të punës duke përdorur semantikën RCU (Lexo Kopshtin). Kjo e bën ndryshimin e tabelave të rrugëve efektiv në mënyrë atomike.
- Keshimi i titujve HTTP: Siç del, llogaritja e titullit HTTP për çdo kërkesë (me ~25K+ RPS për bërthamë) është mjaft e shtrenjtë. Envoy llogarit në mënyrë qendrore titullin çdo gjysmë sekonde dhe e ofron atë për çdo punëtor përmes TLS dhe RCU.
Ka edhe raste të tjera, por shembujt e mëparshëm duhet të ofrojnë një kuptim të mirë për përdorimin e TLS.
Kapricio të njohura të performancës
Megjithëse në përgjithësi Envoy funksionon mjaft mirë, ka disa zona të njohura që kërkojnë vëmendje kur përdoret me një paralelizmat të shumëfishta dhe kapacitet të lartë:
- Siç është përshkruar në këtë artikull, aktualisht të gjitha rrjedhat e punës marrin një bllokim kur shkruajnë në memorien e caches të qasjes në log. Me një paralelizmat të lartë dhe kapacitet të lartë, do të jetë e nevojshme të bëhet paketimi i logeve të qasjes për secilën rrjedhë të punës në krah të dërgimit të papërcaktuar kur shkruani në skedarin përfundimtar. Si një alternativë, mund të krijoni një log qasje për secilën rrjedhë të punës.
- Megjithëse statistikat janë optimizuar shumë, me një paralelizmat dhe kapacitet të lartë, është e mundur që do të ketë konkurrencë atomike mbi statistikën individuale. Zgjidhja e kësaj problemi janë numëruesit për një rrjedhë punë me një rinisje periodike të numëruesve qendrorë. Kjo do të diskutohet në postimin e mëpasëm.
- Arkitektura ekzistuese nuk do të funksionojë mirë nëse Envoy është vendosur në një skenar, në të cilin ka shumë pak lidhje, duke kërkuar të konsiderueshme për burime për procesim. Nuk ka garanci që lidhjet do të shpërndahen në mënyrë të barabartë midis rrjedhave të punës. Kjo mund të zgjidhet duke implementuar balancimin e lidhjeve të punës, ku do të implemetohet mundësia e ndarjes së lidhjeve midis rrjedhave të punës.
Përfundim
Modeli i horizonteve Envoy është zhvilluar për të siguruar thjeshtësi në programim dhe paralelizëm masiv përmes përdorimit potencialisht të tepruar të memories dhe lidhjeve, nëse ato nuk konfigurohen siç duhet. Ky model i lejon të funksionojë shumë mirë me një numër të lartë lidhjesh dhe gjerësish bandore.
Siç përmenda shkurtimisht në Twitter, dizajni gjithashtu mund të punojë mbi një pil të plotë rrjeti në mënyrën e përdoruesit, si DPDK (Data Plane Development Kit), që mund të rezultojë në servera të zakonshëm që trajtojnë miliona kërkesa në sekondë me përpunim të plotë të L7. Do të jetë shumë interesante të shohim se çfarë do të ndërtohet në vitet e ardhshme.
Një koment i fundit i shpejtë: Më shumë herë më është bërë pyetje përse zgjodhëm C++ për Envoy. Arsyetimi mbetet se është akoma gjuha e vetme e përhapur gjerësisht në nivel industrial, mbi të cilën mund të ngrihet arkitektura e përshkruar në këtë postim. C++ sigurisht që nuk është i përshtatshëm për të gjithë ose madje as për shumë projekte, por për raste të caktuara përdorimi është akoma mjeti i vetëm për të përfunduar punën.
Lidhjet me kodin
Lidhjet me skedarët e ndërfaqeve dhe implementimeve të titujve, të diskutuar në këtë postim:
Burimi: habr.com
