[Prijevod] Poslanički model navoja

Prijevod članka: Envoy threading model - https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310

Ovaj članak mi je bio prilično zanimljiv, a budući da se Envoy najčešće koristi kao dio “istio” ili jednostavno kao “ingress controller” kubernetes-a, većina ljudi nema istu direktnu interakciju s njim kao, na primjer, sa tipičnim Nginx ili Haproxy instalacije. Međutim, ako se nešto pokvari, bilo bi dobro razumjeti kako to funkcionira iznutra. Pokušao sam da prevedem što više teksta na ruski, uključujući i posebne reči, a onima kojima je to bolno da gledaju, originale sam ostavio u zagradi. Dobrodošli u mačku.

Tehnička dokumentacija niskog nivoa za bazu kodova Envoy trenutno je prilično rijetka. Da bih ovo ispravio, planiram da napravim seriju postova na blogu o različitim podsistemima Envoy-a. Pošto je ovo prvi članak, javite mi što mislite i što bi vas moglo zanimati u budućim člancima.

Jedno od najčešćih tehničkih pitanja koje dobijam o Envoy-u je traženje niskog nivoa opisa threading modela koji koristi. U ovom postu ću opisati kako Envoy mapira veze sa nitima, kao i Thread Local Storage sistem koji koristi interno da učini kod paralelnijim i visokim performansama.

Pregled navoja

[Prijevod] Poslanički model navoja

Envoy koristi tri različite vrste tokova:

  • Glavni: Ova nit kontrolira pokretanje i završetak procesa, svu obradu XDS (xDiscovery Service) API-ja, uključujući DNS, provjeru zdravlja, opće upravljanje klasterima i runtime-om, resetiranje statistike, administraciju i općenito upravljanje procesima - Linux signale. vruće ponovno pokretanje, itd. događa u ovoj niti je asinhrona i "neblokirajuća". Općenito, glavna nit koordinira sve procese kritične funkcionalnosti koji ne zahtijevaju veliku količinu CPU-a za pokretanje. Ovo omogućava da se većina kontrolnog koda napiše kao da je jednostruka.
  • radnik: Podrazumevano, Envoy kreira radnu nit za svaku hardversku nit u sistemu, ovo se može kontrolisati pomoću opcije --concurrency. Svaka radnička nit pokreće “neblokirajuću” petlju događaja, koja je odgovorna za slušanje svakog slušaoca; u vrijeme pisanja (29. jula 2017.) nema dijeljenja slušatelja, prihvatanja novih veza, instanciranja steka filtera za vezu i obradu svih ulazno/izlaznih (IO) operacija tokom životnog veka veze. Opet, ovo omogućava da većina koda za rukovanje vezom bude napisana kao da je jednonitna.
  • Ispiranje fajlova: Svaka datoteka koju Envoy piše, uglavnom evidencije pristupa, trenutno ima nezavisnu nit za blokiranje. To je zbog činjenice da se pisanje u datoteke kešira od strane sistema datoteka čak i kada se koristi O_NONBLOCK ponekad može biti blokiran (uzdah). Kada radne niti treba da zapišu u datoteku, podaci se zapravo premještaju u međuspremnik u memoriji gdje se na kraju ispuštaju kroz nit ispiranje datoteke. Ovo je jedno područje koda gdje tehnički sve radne niti mogu blokirati istu bravu dok pokušavaju da popune memorijski bafer.

Rukovanje vezom

Kao što je gore ukratko objašnjeno, sve radne niti slušaju sve slušaoce bez ikakvog dijeljenja. Dakle, kernel se koristi za elegantno slanje prihvaćenih soketa radničkim nitima. Moderni kerneli su generalno vrlo dobri u ovome, koriste funkcije kao što je pojačavanje prioriteta ulaza/izlaza (IO) kako bi pokušali ispuniti nit radom prije nego što počnu koristiti druge niti koje također slušaju na istom soketu, a također ne koriste kružni rad. zaključavanje (Spinlock) za obradu svakog zahtjeva.
Jednom kada je veza prihvaćena na radnoj niti, ona nikada ne napušta tu nit. Sva daljnja obrada veze se u potpunosti obrađuje u radnoj niti, uključujući svako ponašanje prosljeđivanja.

Ovo ima nekoliko važnih posljedica:

  • Sva spremišta veza u Envoyu su dodijeljena radnoj niti. Dakle, iako spremišta HTTP/2 veza uspostavljaju samo jednu vezu sa svakim uzvodnim hostom u isto vrijeme, ako postoje četiri radne niti, bit će četiri HTTP/2 veze po uzvodnom hostu u stabilnom stanju.
  • Razlog zašto Envoy radi na ovaj način je taj što zadržavanjem svega na jednoj radnoj niti, gotovo sav kod može biti napisan bez blokiranja i kao da je jednonit. Ovaj dizajn olakšava pisanje velikog broja koda i nevjerovatno dobro se prilagođava gotovo neograničenom broju radnih niti.
  • Međutim, jedan od glavnih zaključaka je da je sa stanovišta memorijskog spremišta i efikasnosti veze, zapravo vrlo važno konfigurirati --concurrency. Ako imate više radnih niti nego što je potrebno, potrošit ćete memoriju, stvoriti više neaktivnih veza i smanjiti stopu okupljanja veza. U Lyftu, naši kontejneri za bočne prikolice za izaslanike rade s vrlo malom istovremenošću, tako da performanse otprilike odgovaraju uslugama uz koje se nalaze. Envoy pokrećemo kao edge proxy samo uz maksimalnu konkurentnost.

Šta znači neblokiranje?

Termin "neblokiranje" korišten je nekoliko puta do sada kada se raspravlja o tome kako funkcionišu glavna i radna niti. Sav kod je napisan pod pretpostavkom da nikada ništa nije blokirano. Međutim, to nije sasvim tačno (što nije sasvim tačno?).

Envoy koristi nekoliko dugih zaključavanja procesa:

  • Kao što je diskutovano, prilikom pisanja dnevnika pristupa, sve radne niti stiču isto zaključavanje prije nego što se popuni međuspremnik dnevnika u memoriji. Vrijeme zadržavanja zaključavanja bi trebalo biti vrlo kratko, ali je moguće da se zaključavanje ospori uz visoku konkurentnost i visoku propusnost.
  • Envoy koristi vrlo složen sistem za rukovanje statistikama koje su lokalne za nit. Ovo će biti tema posebnog posta. Međutim, ukratko ću spomenuti da je u sklopu lokalne obrade statistike niti ponekad potrebno dobiti zaključavanje na centralnom "skladištu statistike". Ovo zaključavanje nikada ne bi trebalo biti potrebno.
  • Glavna nit se povremeno mora koordinirati sa svim radničkim nitima. Ovo se radi "objavljivanjem" iz glavne niti u radne niti, a ponekad i iz radnih niti natrag u glavnu nit. Za slanje je potrebno zaključavanje kako bi se objavljena poruka mogla staviti u red čekanja za kasniju isporuku. Ove brave nikada ne bi trebalo ozbiljno osporavati, ali se ipak tehnički mogu blokirati.
  • Kada Envoy upiše zapisnik u tok sistemskih grešaka (standardna greška), zaključava cijeli proces. Općenito, Envoy-jevo lokalno evidentiranje smatra se užasnim sa stanovišta performansi, tako da nije posvećeno puno pažnje njegovom poboljšanju.
  • Postoji nekoliko drugih nasumičnih zaključavanja, ali nijedno od njih nije kritično za performanse i nikada se ne treba osporavati.

Lokalna pohrana niti

Zbog načina na koji Envoy odvaja odgovornosti glavne niti od odgovornosti radne niti, postoji zahtjev da se složena obrada može obaviti na glavnoj niti, a zatim dostaviti svakoj radnoj niti na vrlo konkurentan način. Ovaj odjeljak opisuje lokalnu pohranu slanja niti (TLS) na visokom nivou. U sljedećem odjeljku ću opisati kako se koristi za upravljanje klasterom.
[Prijevod] Poslanički model navoja

Kao što je već opisano, glavna nit upravlja gotovo svim funkcijama upravljačke i kontrolne ravni u procesu Poslanika. Kontrolna ravan je ovdje malo preopterećena, ali kada je pogledate unutar samog procesa Envoy i uporedite je sa prosljeđivanjem koje rade radne niti, ima smisla. Općenito pravilo je da proces glavne niti obavlja neki posao, a zatim treba ažurirati svaku radnu nit prema rezultatu tog rada. u ovom slučaju, radna nit ne mora steći zaključavanje na svakom pristupu.

Envoyjev TLS (Thread local storage) sistem radi na sljedeći način:

  • Kod koji se izvodi na glavnoj niti može dodijeliti TLS slot za cijeli proces. Iako je ovo apstrahirano, u praksi je to indeks u vektor, koji pruža O(1) pristup.
  • Glavna nit može instalirati proizvoljne podatke u svoj slot. Kada se to učini, podaci se objavljuju svakoj radnoj niti kao normalni događaj petlje događaja.
  • Radničke niti mogu čitati iz svog TLS slota i dohvatiti sve lokalne podatke koji su tamo dostupni.

Iako je to vrlo jednostavna i nevjerojatno moćna paradigma, vrlo je slična konceptu RCU (Read-Copy-Update) blokiranja. U suštini, radničke niti nikada ne vide promjene podataka u TLS slotovima dok rad radi. Promjena se dešava samo tokom perioda odmora između radnih događaja.

Envoy ovo koristi na dva različita načina:

  • Pohranjivanjem različitih podataka na svakoj radnoj niti, podacima se može pristupiti bez ikakvog blokiranja.
  • Održavanjem dijeljenog pokazivača na globalne podatke u načinu samo za čitanje na svakoj radnoj niti. Dakle, svaka radna nit ima broj referenci podataka koji se ne može smanjiti dok se rad izvodi. Tek kada se svi radnici smire i uploaduju nove zajedničke podatke, stari podaci će biti uništeni. Ovo je identično sa RCU.

Niti ažuriranja klastera

U ovom odjeljku ću opisati kako se TLS (Thread local storage) koristi za upravljanje klasterom. Upravljanje klasterom uključuje xDS API i/ili DNS obradu, kao i provjeru zdravlja.
[Prijevod] Poslanički model navoja

Upravljanje protokom klastera uključuje sljedeće komponente i korake:

  1. Cluster Manager je komponenta unutar Envoy-a koja upravlja svim poznatim uzvodnim klasterima, API-jem Cluster Discovery Service (CDS), API-jima usluge Secret Discovery Service (SDS) i Endpoint Discovery Service (EDS), DNS-om i aktivnim vanjskim provjerama. Odgovoran je za stvaranje "eventualno konzistentnog" prikaza svakog uzvodnog klastera, koji uključuje otkrivene hostove kao i zdravstveni status.
  2. Provjera zdravlja obavlja aktivnu provjeru zdravlja i prijavljuje promjene zdravstvenog statusa upravitelju klastera.
  3. CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Endpoint Discovery Service) / DNS se izvode kako bi se utvrdilo članstvo u klasteru. Promjena stanja se vraća upravitelju klastera.
  4. Svaka radna nit kontinuirano izvršava petlju događaja.
  5. Kada upravitelj klastera utvrdi da se stanje za klaster promijenilo, kreira novi snimak stanja klastera samo za čitanje i šalje ga svakoj radnoj niti.
  6. Tokom sljedećeg mirnog perioda, radna nit će ažurirati snimak u dodijeljenom TLS slotu.
  7. Tokom I/O događaja koji bi trebao odrediti ravnotežu opterećenja između hosta, balansator opterećenja će zatražiti TLS (Thread local storage) slot da dobije informacije o hostu. Ovo ne zahtijeva brave. Imajte na umu da TLS također može pokrenuti događaje ažuriranja tako da balanseri opterećenja i druge komponente mogu ponovo izračunati keš memorije, strukture podataka itd. Ovo je izvan okvira ovog posta, ali se koristi na različitim mjestima u kodu.

Koristeći gornju proceduru, Envoy može obraditi svaki zahtjev bez ikakvog blokiranja (osim kako je prethodno opisano). Osim složenosti samog TLS koda, većina koda ne mora razumjeti kako funkcionira višenitnost i može se napisati jednonitno. Ovo čini veći dio koda lakšim za pisanje uz vrhunske performanse.

Drugi podsistemi koji koriste TLS

TLS (Thread local storage) i RCU (Read Copy Update) se široko koriste u Envoyu.

Primjeri upotrebe:

  • Mehanizam za promjenu funkcionalnosti tokom izvođenja: Trenutna lista omogućenih funkcionalnosti izračunava se u glavnoj niti. Svakoj radnoj niti se tada daje snimak samo za čitanje koristeći RCU semantiku.
  • Zamjena tabela ruta: Za tabele ruta koje obezbeđuje RDS (Usluga otkrivanja rute), tabele ruta se kreiraju na glavnoj niti. Snimak samo za čitanje će naknadno biti dostavljen svakoj radnoj niti koristeći RCU (Read Copy Update) semantiku. Ovo čini promjenu tabela ruta atomski efikasnim.
  • Keširanje HTTP zaglavlja: Kako se ispostavilo, izračunavanje HTTP zaglavlja za svaki zahtjev (dok radi ~25K+ RPS po jezgri) je prilično skupo. Envoy centralno izračunava zaglavlje otprilike svake pola sekunde i dostavlja ga svakom radniku putem TLS-a i RCU-a.

Postoje i drugi slučajevi, ali prethodni primjeri bi trebali pružiti dobro razumijevanje za šta se koristi TLS.

Poznate zamke performansi

Iako Envoy općenito radi prilično dobro, postoji nekoliko značajnih područja koja zahtijevaju pažnju kada se koristi s vrlo velikom konkurentnošću i propusnošću:

  • Kao što je opisano u ovom članku, trenutno sve radničke niti dobijaju zaključavanje prilikom pisanja u memorijski bafer dnevnika pristupa. Uz visoku konkurentnost i visoku propusnost, morat ćete skupiti pristupne dnevnike za svaku radničku nit nauštrb isporuke van reda prilikom pisanja u konačnu datoteku. Alternativno, možete kreirati poseban dnevnik pristupa za svaku radničku nit.
  • Iako je statistika visoko optimizirana, pri vrlo visokoj konkurentnosti i propusnosti vjerovatno će doći do atomskog sukoba u pojedinačnim statistikama. Rješenje ovog problema su brojači po radnoj niti sa periodičnim resetiranjem centralnih brojača. O tome će biti riječi u sljedećem postu.
  • Trenutna arhitektura neće dobro funkcionirati ako je Envoy raspoređen u scenariju u kojem postoji vrlo malo veza koje zahtijevaju značajne resurse za obradu. Ne postoji garancija da će veze biti ravnomjerno raspoređene među radničkim nitima. Ovo se može riješiti implementacijom ravnoteže radnih veza, što će omogućiti razmjenu veza između radnih niti.

Zaključak

Envoyjev model navoja je dizajniran da omogući lakoću programiranja i masivni paralelizam na račun potencijalno trošne memorije i veza ako nisu ispravno konfigurisani. Ovaj model mu omogućava da radi vrlo dobro pri vrlo velikom broju niti i propusnosti.
Kao što sam ukratko spomenuo na Twitteru, dizajn se može pokrenuti i na punom mrežnom steku u korisničkom modu, kao što je DPDK (Kit za razvoj plana podataka), što može rezultirati konvencionalnim serverima koji rukuju milionima zahtjeva u sekundi uz potpunu L7 obradu. Biće veoma zanimljivo videti šta će se graditi u narednih nekoliko godina.
Još jedan kratki komentar: Pitali su me mnogo puta zašto smo izabrali C++ za Envoy. Razlog ostaje taj što je to još uvijek jedini široko korišteni industrijski jezik na kojem se može izgraditi arhitektura opisana u ovom postu. C++ definitivno nije pogodan za sve ili čak mnoge projekte, ali je za određene slučajeve upotrebe i dalje jedini alat za obavljanje posla.

Linkovi do koda

Veze do fajlova sa interfejsima i implementacijama zaglavlja o kojima se raspravlja u ovom postu:

izvor: www.habr.com

Dodajte komentar