[Prijevod] Envoy model navoja

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

Smatram da je ovaj članak prilično zanimljiv, a budući da se Envoy najčešće koristi kao dio "istia" ili jednostavno kao "ulazni kontroler" kubernetesa, većina ljudi nema istu izravnu interakciju s njim kao, na primjer, s tipičnim Nginx ili Haproxy instalacije. Međutim, ako se nešto pokvari, bilo bi dobro razumjeti kako to funkcionira iznutra. Pokušao sam prevesti što je više moguće teksta na ruski, uključujući posebne riječi; za one kojima je to bolno gledati, ostavio sam izvornike u zagradama. Dobrodošli u mačku.

Tehnička dokumentacija niske razine za Envoy kodnu bazu trenutno je prilično oskudna. Kako bih to ispravio, planiram napisati niz postova na blogu o raznim podsustavima Envoya. Budući da je ovo prvi članak, recite mi što mislite i što bi vas moglo zanimati u budućim člancima.

Jedno od najčešćih tehničkih pitanja koje primam o Envoyu je traženje niskog opisa modela niti koji koristi. U ovom ću postu opisati kako Envoy preslikava veze u niti, kao i Thread Local Storage sustav koji interno koristi kako bi kod bio paralelniji i učinkovitiji.

Pregled niti

[Prijevod] Envoy 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 klasterom i vrijeme izvođenja, resetiranje statistike, administraciju i opće upravljanje procesom - Linux signali. vruće ponovno pokretanje, itd. Sve što događaji u ovoj niti su asinkroni i "neblokirajući". Općenito, glavna nit koordinira sve kritične funkcionalne procese koji ne zahtijevaju veliku količinu procesora za rad. Ovo omogućuje pisanje većine kontrolnog koda kao da je jednonitni.
  • Radnik: Prema zadanim postavkama Envoy stvara radnu nit za svaku hardversku nit u sustavu, a to se može kontrolirati pomoću opcije --concurrency. Svaka radna nit pokreće "neblokirajuću" petlju događaja, koja je odgovorna za slušanje svakog slušatelja; u vrijeme pisanja (29. srpnja 2017.) nije bilo dijeljenja slušatelja, prihvaćanja novih veza, instanciranja hrpe filtera za vezu i obradu svih ulazno/izlaznih (IO) operacija tijekom vijeka trajanja veze. Opet, ovo omogućuje da većina koda za rukovanje vezom bude napisana kao da je jednonit.
  • Ispirač datoteka: Svaka datoteka koju Envoy piše, uglavnom zapisnici pristupa, trenutno ima nezavisnu nit blokiranja. To je zbog činjenice da pisanje u datoteke koje je datotečni sustav predmemorirao čak i kada koristite O_NONBLOCK ponekad se može blokirati (uzdah). Kada radničke niti trebaju pisati u datoteku, podaci se zapravo premještaju u međuspremnik u memoriji gdje se na kraju ispiraju kroz nit ispiranje datoteke. Ovo je jedno područje koda gdje tehnički sve radne niti mogu blokirati isto zaključavanje dok pokušavaju ispuniti međuspremnik memorije.

Rukovanje vezom

Kao što je gore ukratko objašnjeno, sve radne niti slušaju sve slušatelje bez ikakvog dijeljenja. Dakle, kernel se koristi za elegantno slanje prihvaćenih utičnica radnim nitima. Moderni kerneli općenito su vrlo dobri u tome, koriste značajke kao što je povećanje prioriteta ulaza/izlaza (IO) kako bi pokušali ispuniti nit radom prije nego počnu koristiti druge niti koje također slušaju na istoj utičnici, 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 u potpunosti se obrađuje u radnoj niti, uključujući svako prosljeđivanje.

To ima nekoliko važnih posljedica:

  • Svi skupovi veza u Envoyu dodijeljeni su radnoj niti. Dakle, iako skupovi 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 se držanjem svega na jednoj radnoj niti gotovo sav kod može napisati bez blokiranja i kao da je jednonit. Ovaj dizajn olakšava pisanje velikog broja koda i nevjerojatno se dobro prilagođava gotovo neograničenom broju radnih niti.
  • Međutim, jedan od glavnih zaključaka je da je sa stajališta memorijskog skladišta i učinkovitosti veze zapravo vrlo važno konfigurirati --concurrency. Imati više radnih niti nego što je potrebno trošit će memoriju, stvarati više neaktivnih veza i smanjiti stopu skupljanja veza. U Lyftu, naši spremnici s bočnom prikolicom Envoy rade s vrlo niskom paralelnošću tako da performanse otprilike odgovaraju uslugama uz koje se nalaze. Envoy pokrećemo kao rubni proxy samo pri maksimalnoj konkurentnosti.

Što znači neblokiranje?

Izraz "neblokiranje" do sada je korišten nekoliko puta kada se raspravljalo o tome kako glavne i radne niti rade. Sav kod je napisan pod pretpostavkom da nikada ništa nije blokirano. Međutim, to nije posve točno (što nije posve točno?).

Envoy koristi nekoliko dugih procesa zaključavanja:

  • Kao što je spomenuto, prilikom pisanja dnevnika pristupa, sve radne niti dobivaju isto zaključavanje prije nego što se popuni međuspremnik dnevnika u memoriji. Vrijeme zadržavanja zaključavanja trebalo bi biti vrlo malo, ali je moguće da se zaključavanje ospori pri visokoj konkurentnosti i visokoj propusnosti.
  • Envoy koristi vrlo složen sustav za obradu statistike koja je lokalna za nit. Ovo će biti tema posebnog posta. Međutim, ukratko ću spomenuti da je u sklopu lokalne obrade statistike niti ponekad potrebno nabaviti zaključavanje središnje "stats store". Ovo zaključavanje nikada ne bi trebalo biti potrebno.
  • Glavna nit se povremeno treba koordinirati sa svim radničkim nitima. To se radi "objavljivanjem" iz glavne niti u radničke niti, a ponekad iz radničkih 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 se nikada ne bi trebale ozbiljno osporavati, ali se ipak tehnički mogu blokirati.
  • Kada Envoy zapiše zapisnik u tok grešaka sustava (standardna pogreška), zaključava cijeli proces. Općenito, lokalno bilježenje Envoya smatra se užasnim sa stajališ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 izvedbu i ne bi se smjelo dovoditi u pitanje.

Lokalna pohrana niti

Zbog načina na koji Envoy odvaja odgovornosti glavne niti od odgovornosti radničke niti, postoji zahtjev da se složena obrada može izvršiti na glavnoj niti i zatim dostaviti svakoj radničkoj niti na visoko konkurentan način. Ovaj odjeljak opisuje Envoy Thread Local Storage (TLS) na visokoj razini. U sljedećem odjeljku opisat ću kako se koristi za upravljanje klasterom.
[Prijevod] Envoy model navoja

Kao što je već opisano, glavna nit upravlja gotovo svim funkcijama ravni upravljanja i kontrole u procesu Envoy. Kontrolna ravnina je ovdje malo preopterećena, ali kada je pogledate unutar samog Envoy procesa i usporedite s prosljeđivanjem koje rade radne niti, ima smisla. Općenito je pravilo da proces glavne niti obavlja određeni 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 pri svakom pristupu.

Envoy TLS (Thread local storage) sustav radi na sljedeći način:

  • Kod koji se izvodi na glavnoj niti može dodijeliti TLS utor za cijeli proces. Iako je ovo apstraktno, u praksi je to indeks u vektoru, osiguravajući O(1) pristup.
  • Glavna nit može instalirati proizvoljne podatke u svoj utor. Kada se to učini, podaci se objavljuju u svakoj radnoj niti kao uobičajeni događaj petlje događaja.
  • Radničke niti mogu čitati iz svog TLS utora i dohvatiti sve podatke lokalne niti 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 biti, radne niti nikada ne vide promjene podataka u TLS utorima dok se rad izvodi. Promjena se događa samo tijekom razdoblja 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 zajedničkog pokazivača na globalne podatke u načinu rada samo za čitanje na svakoj radnoj niti. Stoga svaka radna nit ima broj referenci podataka koji se ne može smanjiti dok se rad izvodi. Tek kada se svi radnici smire i učitaju nove dijeljene podatke, stari će podaci biti uništeni. Ovo je identično RCU.

Niti ažuriranja klastera

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

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

  1. Upravitelj klastera je komponenta unutar Envoya koja upravlja svim poznatim uzvodnim kanalima klastera, API-jima usluge otkrivanja klastera (CDS), API-jima usluge tajnog otkrivanja (SDS) i usluge otkrivanja krajnjih točaka (EDS), DNS-om i aktivnim vanjskim provjerama. Odgovoran je za stvaranje "eventualno dosljednog" prikaza svakog uzvodnog klastera, koji uključuje otkrivene domaćine kao i zdravstveni status.
  2. Provjera zdravlja provodi aktivnu provjeru zdravlja i izvješćuje o promjenama statusa zdravlja upravitelju klastera.
  3. CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Endpoint Discovery Service) / DNS izvode se za određivanje članstva u klasteru. Promjena stanja vraća se upravitelju klastera.
  4. Svaka radna nit kontinuirano izvršava petlju događaja.
  5. Kada upravitelj klastera utvrdi da se stanje klastera promijenilo, stvara novu snimku stanja klastera samo za čitanje i šalje je svakoj radničkoj niti.
  6. Tijekom sljedećeg mirnog razdoblja, radna nit će ažurirati snimku u dodijeljenom TLS utoru.
  7. Tijekom I/O događaja koji bi trebao odrediti host za ravnotežu opterećenja, balanser opterećenja će zatražiti TLS (Thread local storage) utor za dobivanje informacija o hostu. Ovo ne zahtijeva brave. Također imajte na umu da TLS također može pokrenuti događaje ažuriranja tako da balanseri opterećenja i druge komponente mogu ponovno izračunati predmemorije, strukture podataka itd. Ovo je izvan opsega ovog posta, ali se koristi na raznim mjestima u kodu.

Koristeći gornji postupak, 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 radi multithreading i može se pisati jednonitno. To čini većinu koda lakšim za pisanje uz vrhunsku izvedbu.

Ostali podsustavi koji koriste TLS

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

Primjeri upotrebe:

  • Mehanizam za promjenu funkcionalnosti tijekom izvođenja: Trenutačni popis omogućenih funkcija izračunava se u glavnoj niti. Svakoj radnoj niti tada se daje snimka samo za čitanje pomoću RCU semantike.
  • Zamjena tablica ruta: Za tablice ruta koje pruža RDS (Route Discovery Service), tablice ruta stvaraju se u glavnoj niti. Snimka samo za čitanje naknadno će biti dostavljena svakoj radnoj niti pomoću RCU (Read Copy Update) semantike. To čini promjenu tablica ruta atomski učinkovitom.
  • Predmemoriranje HTTP zaglavlja: Kako se pokazalo, izračunavanje HTTP zaglavlja za svaki zahtjev (dok radi ~25K+ RPS po jezgri) prilično je skupo. Envoy centralno izračunava zaglavlje otprilike svakih pola sekunde i daje ga svakom radniku putem TLS-a i RCU-a.

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

Poznate zamke izvedbe

Dok Envoy sveukupno ima dosta dobre rezultate, postoji nekoliko značajnih područja koja zahtijevaju pozornost kada se koristi s vrlo visokom konkurentnošću i propusnošću:

  • Kao što je opisano u ovom članku, trenutno sve radne niti dobivaju zaključavanje prilikom pisanja u međuspremnik memorije dnevnika pristupa. Pri visokoj istodobnosti i visokoj propusnosti, morat ćete grupirati zapisnike pristupa za svaku radnu nit nauštrb isporuke izvan reda prilikom pisanja u konačnu datoteku. Alternativno, možete stvoriti zasebnu evidenciju pristupa za svaku radnu nit.
  • Iako su statistike visoko optimizirane, pri vrlo visokoj konkurentnosti i propusnosti vjerojatno će doći do atomskog sukoba na pojedinačnim statistikama. Rješenje ovog problema su brojači po radnoj niti s periodičnim resetiranjem središnjih brojača. O tome će biti riječi u sljedećem postu.
  • Trenutna arhitektura neće dobro funkcionirati ako se Envoy implementira u scenariju u kojem postoji vrlo malo veza koje zahtijevaju značajne resurse obrade. Ne postoji jamstvo da će veze biti ravnomjerno raspoređene među radnim nitima. Ovo se može riješiti implementacijom balansiranja radnih veza, što će omogućiti razmjenu veza između radničkih niti.

Zaključak

Envoyjev model threadinga osmišljen je kako bi omogućio jednostavno programiranje i masivni paralelizam na račun potencijalno rasipne memorije i veza ako nisu ispravno konfigurirani. Ovaj model mu omogućuje vrlo dobre performanse pri vrlo velikom broju niti i propusnosti.
Kao što sam ukratko spomenuo na Twitteru, dizajn se također može izvoditi na vrhu punog mrežnog skupa korisničkog načina kao što je DPDK (Data Plane Development Kit), što može rezultirati u tome da konvencionalni poslužitelji obrađuju milijune zahtjeva u sekundi s punom L7 obradom. Bit će vrlo zanimljivo vidjeti što će se izgraditi u sljedećih nekoliko godina.
Zadnji kratki komentar: mnogo su me puta pitali zašto smo odabrali 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 prikladan za sve ili čak za mnoge projekte, ali za određene slučajeve upotrebe i dalje je jedini alat za obavljanje posla.

Veze na kod

Veze na datoteke sa sučeljima i implementacijama zaglavlja o kojima se govori u ovom postu:

Izvor: www.habr.com

Dodajte komentar