Prevod članka:
Ovaj članak mi se učinio prilično zanimljivim, a pošto se Envoy najčešće koristi kao deo „istio“ ili kao „ingress controller“ za Kubernetes, većina ljudi nema direktnu interakciju s njim kao sa standardnim instalacijama Nginx ili Haproxy. Međutim, ako nešto pođe po zlu, bilo bi lepo razumeti kako je to unutra. Pokušao sam da prevedem što više teksta na srpski, uključujući i specijalne reči; za one kojim je mučno gledati takve stvari, ostavio sam originalne termine u zagradi. Dobrodošli pod kat.
Tehnička dokumentacija niskog nivoa o kodnoj bazi Envoy trenutno je prilično skromna. Da to popravim, planiram da napravim seriju članaka na blogu o različitim podsystemima Envoy. Pošto je ovo prvi članak, molim vas javite mi šta mislite i šta bi vas moglo zanimati u sledećim člancima.
Jedno od najčešćih tehničkih pitanja koja dobijam o Envoy-u je zahtev za niskonivovnim opisom korišćenog modela niti (threading model). U ovom postu ću opisati kako Envoy usklađuje veze sa nitima, kao i opis sistema lokalnog skladišta niti (Thread Local Storage), koji se koristi unutra da bi se kod učinio paralelnijim i visokoprofitabilnim.
Opis niti (Threading overview)
![[Prevajanje] Model tokov Envoy (Envoy threading model)](/wp-content/uploads/2019/04/c5028463db1eb5b07ccb1a3af1f64a03.jpeg)
Envoy koristi tri različita tipa niti:
- Glavna (Main): Ova nit upravlja pokretanjem i završetkom procesa, obradom svih XDS (xDiscovery Service) API-ja, uključujući DNS, zdravstvene provere (health checking), opštim upravljanjem klasterom i radnog procesa (runtime), resetovanjem statistike, administracijom i opštim vođenjem procesa — Linux signali, topli restart (hot restart) itd. Sve što se dešava u ovoj niti je asinhrono i „neblokirajuće“. U suštini, glavna nit koordinira sve kritične procese funkcionalnosti za čije izvršenje nije potrebno veliko obim CPU-a. To omogućava većinu upravljačkog koda da se piše kao da je jednonitni.
- Radna (Worker): Podrazumevano Envoy kreira radnu nit (worker thread) za svaku hardversku nit u sistemu, to se može kontrolisati pomoću opcije
--concurrencyVsak delovni tok zažene „neblokirajočo“ zanko dogodkov (event loop), ki je odgovorna za spremljanje (listening) vsakega poslušalca (listener). Ob pisanju tega članka (29. julij 2017) segmentiranja (sharding) poslušalca (listener) še ni, sprejemanje novih povezav, ustvarjanje primerka sklada filtrov za povezavo in obdelava vseh operacij vhod/izhod (IO) traja vse od obstoja povezave. To omogoča, da večino kode za obdelavo povezav napišemo, kot da deluje enotno. - Datotečni (File flusher): Vsaka datoteka, ki jo piše Envoy, predvsem dnevniki dostopa (access logs), trenutno ima neodvisen blokirajoč tok. To je posledica dejstva, da zapisovanje v datoteke, ki jih predpomni datotečni sistem, lahko blokira tudi pri uporabi
O_NONBLOCKkadar delovni tokovi potrebujejo pisanje v datoteko, se podatki dejansko premaknejo v pomnilniški predpomnilnik, kjer se na koncu izpraznijo preko toka file flush. To je eno področje kode, kjer lahko vsi delovni tokovi (worker threads) blokirajo (block) isto zaklepanje (lock), ko poskušajo napolniti pomnilniški predpomnilnik.
Obdelava povezav (Connection handling)
Kot je bilo omenjeno že zgoraj, vsi delovni tokovi spremljajo vse poslušalce (listeners) brez kakršnega koli segmentiranja. Tako se jedro učinkovito uporablja za pošiljanje sprejetih soketov v delovne tokove. Sodobna jedra so na splošno zelo dobra pri tem; uporabljajo funkcije, kot je dvig prednosti vhod/izhod (IO), da poskušajo napolniti tok z delom, preden začnejo uporabljati druge tokove, ki prav tako spremljajo isti soket, ter da se izognejo uporabi cikel blokade (Spinlock) za obdelavo vsakega zahtevka.
Ko je povezava sprejeta v delovnem toku (worker thread), nikoli ne zapusti tega toka (thread). Vsa nadaljnja obdelava povezave se popolnoma izvaja v delovnem toku (worker thread), vključno z vsako obnašanjem preusmerjanja (forwarding behavior).
To ima več pomembnih posledic:
- Vsi povezovalni bazeni v Envoy so povezani z delovnim tokom. Zato, čeprav HTTP/2 povezovalni bazeni naenkrat vzpostavijo le eno povezavo s vsakim nadrejenim gostiteljem, bo ob štirih delovnih tokov štiri povezave HTTP/2 do nadrejenega gostitelja v stabilnem stanju.
- Razlog, zakaj Envoy deluje na ta način, je, da lahko skoraj celotno kodo napišemo brez zaklepanja in tako, da deluje kot enotni delovni tok. Ta zasnova poenostavi pisanje velike količine kode in izjemno dobro deluje na skoraj neomejeno število delovnih tokov.
- Eden od glavnih izsledkov pa je, da je za učinkovitost bazena pomnilnika in povezav v resnici zelo pomembno, da pravilno nastavimo parameter
--concurrency. Imeti več delovnih tokov, kot je potrebno, bo povzročilo izgubo pomnilnika, ustvarjanje več neaktivnih povezav in zmanjšanje hitrosti dostopa do povezovalnega bazena. V Lyftovih envoy sidecar kontejnerjih delamo z zelo nizkim paralelizmom, tako da se zmogljivost približno ujema z storitvami, ob katerih se nahajajo. Envoy zažene kot robni proxy (edge) le pri maksimalnem paralelizmu.
Kaj pomeni neblokirni način (What non-blocking means)
Termin "neblokirni" se je doslej večkrat uporabil pri razpravi o tem, kako delujejo osnovni in delovni tokovi. Vsa koda je napisana ob predpostavki, da nikoli ne pride do zaklepanja. Vendar to ni povsem res (kaj ni povsem res?).
Envoy uporablja več dolgih zaklepanj procesa:
- Kot že omenjeno, pri pisanju dnevnikov dostopa vsi delovni tokovi dobijo enotno zaklepanje pred izpolnjevanjem dnevniškega buffra v pomnilniku. Čas zadrževanja zaklepa mora biti zelo nizek, vendar je mogoče, da bo ta zaklep izzvan pri visoki paralelizaciji in visoki prepustnosti.
- Envoy uporablja zelo zapleten sistem za obdelavo statistike, ki je lokaliziran za tok. To bo tema ločenega prispevka. Kljub temu bom na kratko omenil, da je kot del lokalne obdelave statistike včasih potrebno pridobiti zaklep za centralni 'shranjevanje statistike'. Ta zaklep nikoli ne bi smel biti potreben.
- Glavni tok potrebuje občasno usklajevanje z vsemi delovnimi tokovi. To se doseže z 'objavo' iz glavnega toka v delovne tokove, včasih pa tudi iz delovnih tokov nazaj v glavni tok. Za pošiljanje je potreben zaklep, da se lahko objavljeno sporočilo postavi v čakalno vrsto za kasnejšo dostavo. Ti zaklepi nikoli ne bi smeli biti predmet resne konkurence, a vseeno lahko tehnično pride do zaklepanja.
- Ko Envoy piše dnevnik v sistemski tok napak (standard error), pridobi zaklep celotnega procesa. Na splošno se lokalno beleženje Envoy šteje za slabo z vidika zmogljivosti, zato mu ni posvečene veliko pozornosti.
- Obstaja še nekaj drugih občasnih zaklepov, vendar nobeden od njih ni kritičen za zmogljivost in nikoli ne bi smel biti izpodbijan.
Lokalno shranjevanje niti (Thread local storage)
Zaradi načina, kako Envoy ločuje naloge glavnega toka od nalog delovnega toka, obstaja zahteva, da se zapletena obdelava lahko izvede v glavnem toku in nato zagotovi vsakemu delovnemu toku z visokim nivojem paralelizma. V tem razdelku je opisana sistem Envoy Thread Local Storage (TLS) na visoki ravni. V naslednjem razdelku bom opisal, kako se uporablja za upravljanje gruče.
![[Prevajanje] Model tokov Envoy (Envoy threading model)](/wp-content/uploads/2019/04/a47af1e8eb1f55a4d7609b3ff3ee9ce1.jpeg)
Kot že omenjeno, glavni tok obdeluje skoraj vse funkcije upravljanja (management) in funkcionalnost nadzorne ravni (control plane) v procesu Envoy. Nadzorna raven je tukaj nekoliko preobremenjena, a če jo obravnavamo v okviru samega procesa Envoy in jo primerjamo s preusmerjanjem, ki ga izvajajo delovni tokovi, se to zdi smiselno. Na splošno glavni proces opravi določeno delo in nato mora posodobiti vsak delovni tok v skladu z rezultatom tega dela. pri tem delovnem tok ne potrebuje nastavljati zaklepa pri vsakem dostopu.
Sistem TLS (Thread local storage) Envoy deluje na naslednji način:
- Koda, ki se izvaja v glavni nit, lahko dodeli TLS prostor za celoten proces. Čeprav je to abstraktno, v praksi to predstavlja indeks v vektorju, ki omogoča dostop O(1).
- Glavna nit lahko nastavi poljubne podatke v svoj prostor. Ko je to storjeno, se podatki objavijo v vsaki delovni niti kot običajni dogodek cikla dogodkov.
- Delovne niti lahko berejo iz svojega TLS prostora in pridobijo vse lokalne podatke niti, ki so tam na voljo.
Čeprav je to zelo preprosta in izjemno močna paradigma, ki je zelo podobna konceptu zaklepanja RCU (Read-Copy-Update). V bistvu delovne niti nikoli ne vidijo nobenih sprememb podatkov v TLS prostorih med izvajanjem dela. Sprememba se zgodi le v obdobju mirovanja med delovnimi dogodki.
Envoy to uporablja na dva različna načina:
- Shranjene različne podatke v vsaki delovni niti, dostop do teh podatkov pa se izvede brez kakršnega koli zaklepa.
- Shranjuje skupni kazalec na globalne podatke v načinu ''samo za branje'' na vsaki delovni niti. Tako ima vsaka delovna nit števec referenc na podatke, ki ga med izvajanjem dela ni mogoče zmanjšati. Le ko se vsi delavci umirijo in naložijo nove skupne podatke, se stari podatki uničijo. To je enako RCU.
Nit posodobitve klasterja (Cluster update threading)
V tem razdelku bom opisal, kako se TLS (Thread local storage) uporablja za upravljanje klasterja. Upravljanje klasterja vključuje obdelavo API xDS in / ali DNS ter preverjanje delovanja (health checking).
![[Prevajanje] Model tokov Envoy (Envoy threading model)](/wp-content/uploads/2019/04/238f5718729e1f12f8f0d6507f16abe8.jpeg)
Upravljanje tokov klasterja vključuje naslednje komponente in faze:
- Manager klasterja je komponenta znotraj Envoy, ki upravlja vse znane upstream-e klasterja, API vmesnik CDS (Cluster Discovery Service), API vmesnike SDS (Secret Discovery Service) in EDS (Endpoint Discovery Service), DNS in aktivne zunanje preglede delovanja (health checking). Odgovoren je za ustvarjanje ''končno usklajenega'' (eventually consistent) pregleda vsakega upstream-a klasterja, ki vključuje odkrite gostitelje in stanje delovanja (health status).
- Sredstvo provery rabotosposobnosti (health checker) izvira aktivno proveryaet rabotosposobnost i soobshchaet ob izmeneniyakh sostoyaniya rabotosposobnosti dispetcheru klassika.
- CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Endpoint Discovery Service) / DNS ispolnyayutsya dlya opredeleniya prinadlezhnosti k klasteru. Izmenenie sostoyaniya vozvrashchaetsya menedzheru klastera.
- Kazhdyy rabochyy potok postoyanno vypolnyayet tsikl obrabotki sobytiy.
- Kogda menedzher klastera opredelyaet, chto sostoyanie dlya klastera izmenilos', on sozdavaet novyy snimok sostoyaniya klastera, dostupnyy tol'ko dlya chteniyu, i otpravlyayet ego v kazhdyy rabochyy potok.
- V tekushchyy period pokoya rabochyy potok obnovit snimok v vydelennom slotye TLS.
- Vo vremya sobytiya vvoda-vyvod, kotoroye dolzhno opredelit' khost dlya balancerovki nagruzki, balancer nagruzki budet zaprashivat' slot TLS (Thread local storage) dlya polucheniya informatsii o khoste. Dlya etogo ne trebuetsya blokirovok. Obratite vnimanie takzhe, chto TLS mozhet takzhe initsiiruvat' sobytiya pri obnovlenii, chtoby podsystemy balancerovki nagruzki i drugiye komponenty mogli пересчитывать keshi, struktury dannykh i t.d. Eto vykhodit za ramki etogo posta, no ispol'zuyetsya v razlichnykh mestakh koda.
Ispol'zuyya virosposoby protseduru, Envoy mozhet obradot' kazhdyy zapros bez kakikh-libo blokirovok (krome opisannykh ranee). Pomimo slozhnosti samogo koda TLS, bol'shoy chasti koda ne nuzhno ponimat', kak rabotaet mnozhestvennost', i on mozhet byt' napisany v odnopotochnom rezhime. Eto oblegchaet napisanie bol'shoy chasti koda v dopolnenie k prevoshodnoy proizvoditel'nosti.
Drugie podsystemy, ispol'zuyushchie TLS (Other subsystems that make use of TLS)
TLS (Thread local storage) i RCU (Read Copy Update) shiroko ispol'zuyutsya v Envoy.
Primeri uporabe:
- Mekhanizm izmeneniya funktsional'nosti v protsesse vypolneniya: Tekushchiy spisok vklyuchennogo funktsionala vychislayetsya v osnovnom potoke. Zatem kazhdomu rabochему potoku predostavlyayetsya snimok tol'ko dlya chteniya s ispol'zovaniem semantiki RCU.
- Zamena tabletov marshrutov: za tabele usmerjenja, ki jih zagotavlja RDS (Route Discovery Service), se tabele usmerjenja ustvarjajo v glavnem toku. Snemek v samo branje bo nato zagotovljen vsakemu delovnemu toku z uporabo semantike RCU (Read Copy Update). To omogoča, da je sprememba tabel usmerjenja atomsko učinkovita.
- Predpomnjenje HTTP glava: Kot se izkaže, je izračunavanje HTTP glave za vsak zahtevek (pri ~25K+ RPS na jedro) dokaj drago. Envoy centralizirano izračunava glavo približno vsake pol sekunde in jo zagotavlja vsakemu delavcu prek TLS in RCU.
Obstajajo tudi drugi primeri, vendar bi morali prejšnji primeri zagotoviti dobro razumevanje, za kaj se uporablja TLS.
Znane pasti zmogljivosti (Known performance pitfalls)
Čeprav na splošno Envoy deluje dovolj dobro, obstaja nekaj znanih področij, ki zahtevajo pozornost, ko se uporablja z zelo visokim paralelizmom in propustnostjo:
- Kot je bilo opisano v tem članku, trenutno vsi delovni tokovi prejemajo zaklep pri pisanju v pomnilniški dnevnik dostopa. Pri visoki paralelizmu in visoki propustnosti bo potrebno združevanje dnevnikov dostopa za vsak delovni tok na račun neurejenega dostavljanja pri pisanju v končno datoteko. Kot alternativo je možno ustvariti ločen dnevnik dostopa za vsak delovni tok.
- Čeprav so statistični podatki zelo optimizirani, bo pri zelo visoki paralelizmu in propustnosti verjetno atomska konkurenca glede posameznih statistik. Rešitev tega problema so števec za en delovni tok s periodičnim ponovnim nastavljanjem centralnih števcev. To bo obravnavano v naslednjem prispevku.
- Obstoječa arhitektura ne bo dobro delovala, če bo Envoy uveden v scenarij, v katerem je zelo malo povezav, ki zahtevajo znatne vire za obdelavo. Ni zagotovila, da bodo povezave enakomerno razporejene med delovne tokove. To se lahko reši z implementacijo uravnoteženja delovnih povezav, pri čemer bo omogočeno izmenjavanje povezav med delovnimi tokovi.
Zaključek (Conclusion)
Modela pretokov Envoy je zasnovana za zagotavljanje preprostosti programiranja in masovnega paralelizma, kar lahko vodi do potencialno neučinkovite rabe pomnilnika in povezav, če niso pravilno nastavljene. Ta model omogoča zelo dobro delovanje pri zelo velikem številu pretokov in propustu.
Kot sem na kratko omenil na Twitterju, lahko dizajn deluje tudi nad polno funkcionalno omrežno zloženko v uporabniškem načinu, kot je DPDK (Data Plane Development Kit), kar lahko povzroči, da običajni strežniki obdelujejo milijone zahtevkov na sekundo pri polni obdelavi L7. Zelo zanimivo bo videti, kaj bo zgrajeno v naslednjih nekaj letih.
Ena zadnja hitra opomba: večkrat so me vprašali, zakaj smo izbrali C ++ za Envoy. Razlog ostaja, da je to še vedno edini široko uporabljeni jezik industrijske ravni, s katerim lahko zgradimo arhitekturo, opisano v tem prispevku. C ++ nikakor ni primeren za vse ali celo za mnoge projekte, vendar je za nekatere primere uporabe še vedno edino orodje za opravljanje dela.
Povezave do kode
Povezave do datotek z vmesniki in implementacijo glav, obravnavanih v tem prispevku:
Izvor: habr.com
