[ÖversĂ€ttning] Envoy threading model (Envoy modell för trĂ„dar)

ÖversĂ€ttning av artikeln: Envoy-trĂ„dningsmodell — https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310

Den hÀr artikeln verkade mig vara ganska intressant, och eftersom Envoy oftast anvÀnds som en del av "istio" eller helt enkelt som "ingress controller" för Kubernetes, har de flesta mÀnniskor inte samma direkta interaktion med den som med typiska installationer av Nginx eller Haproxy. Men om nÄgot gÄr fel skulle det vara bra att förstÄ hur det fungerar inuti. Jag har försökt översÀtta sÄ mycket text som möjligt till ryska, inklusive specialord, för dem som har svÄrt att se pÄ sÄdant lÀmnade jag originalen inom parentes. VÀlkommen under kat.

Teknisk dokumentation av lÄg nivÄ om Envoys kodbas Àr för nÀrvarande ganska sparsam. För att ÄtgÀrda detta planerar jag att göra en serie bloggartiklar om olika delsystem i Envoy. Eftersom detta Àr den första artikeln, vÀnligen lÄt mig veta vad du tycker och vad som skulle kunna intressera dig i de kommande artiklarna.

En av de vanligaste tekniska frÄgorna jag fÄr om Envoy Àr begÀran om en lÄg nivÄ beskrivning av den anvÀnda trÄdningsmodellen. I det hÀr inlÀgget kommer jag att beskriva hur Envoy mappar anslutningar till trÄdar, samt en beskrivning av systemet för trÄdspecifik lagring (Thread Local Storage) som anvÀnds internt för att göra koden mer parallell och högpresterande.

Översikt av trĂ„dar (Threading overview)

[ÖversĂ€ttning] Envoy threading model (Envoy modell för trĂ„dar)

Envoy anvÀnder tre olika typer av trÄdar:

  • HuvudtrĂ„d (Main): Denna trĂ„d hanterar starten och stoppningen av processen, all hantering av XDS (xDiscovery Service) API:erna, inklusive DNS, hĂ€lsokontroller (health checking), övergripande klusterhantering och tjĂ€nstehantering (runtime), Ă„terstĂ€llande av statistik, administration och övergripande hantering av processer — Linux-signaler, varm omstart (hot restart) etc. Allt som hĂ€nder i denna trĂ„d Ă€r asynkront och "icke-blockerande". Generellt koordinerar huvudtrĂ„den alla kritiska funktionaliteter, vars utförande inte krĂ€ver en stor mĂ€ngd CPU. Detta gör att större delen av styrningskoden kan skrivas som om den vore enkeltrĂ„dad.
  • ArbetstrĂ„d (Worker): Som standard skapar Envoy en arbetstrĂ„d (worker thread) för varje hĂ„rdvarutrĂ„d i systemet, detta kan kontrolleras med hjĂ€lp av alternativet --concurrency. Varje arbetstrĂ„d kör en "icke-blockerande" hĂ€ndelseschlepp (event loop) som ansvarar för att övervaka (listening) varje lyssnare (listener). Vid skrivande av denna artikel (29 juli 2017) finns ingen segmentering (sharding) av lyssnaren (listener), vilket inkluderar att ta emot nya kopplingar, skapa en instans av filterstacken för anslutningen och hantera alla in-och-utdataoperationer (IO) under anslutningens livslĂ€ngd. Återigen gör detta att större delen av koden för hantering av anslutningar kan skrivas som om den vore en trĂ„dad (single-threaded) applikation.
  • Filskriver (File flusher): Varje fil som Envoy skriver, huvudsakligen Ă„tkomstloggar (access logs), har för nĂ€rvarande en oberoende blockerande trĂ„d. Detta beror pĂ„ att skrivning till filer, som Ă€r cachade av filsystemet Ă€ven nĂ€r det anvĂ€nds O_NONBLOCK ibland kan blockera (suck). NĂ€r arbetstrĂ„dar behöver skriva till en fil, flyttas data faktiskt till en buffer i minnet, dĂ€r de sĂ„ smĂ„ningom spolas via strömmen file flush. Detta Ă€r ett av de omrĂ„den i koden dĂ€r tekniskt sett kan alla arbetstrĂ„dar (worker threads) blockera (block) samma lĂ„s (lock) nĂ€r de försöker fylla bufferten i minnet.

Anslutningshantering (Connection handling)

Som kort nÀmnts ovan, övervakar alla arbetstrÄdar alla lyssnare (listeners) utan segmentering. DÀrför anvÀnds kÀrnan för att effektivt skicka de mottagna sockets till arbetstrÄdarna. Moderna kÀrnor Àr i allmÀnhet mycket bra pÄ detta, de anvÀnder funktioner som prioritering av in-och-utdata (IO) för att försöka fylla en ström med arbete innan de börjar anvÀnda andra strömmar som ocksÄ lyssnar pÄ samma socket, samt undvika cyklisk blockering (Spinlock) för att hantera varje begÀran.
SÄ snart en anslutning accepteras pÄ en arbetstrÄd (worker thread), lÀmnar den aldrig denna trÄd (thread). All vidare behandling av anslutningen hanteras helt och hÄllet i arbetstrÄden (worker thread), inklusive eventuellt vidarebefordringsbeteende (forwarding behavior).

Detta har ett antal viktiga konsekvenser:

  • Alla anslutningspooler i Envoy tillhör arbetsflödet. DĂ€rför, Ă€ven om HTTP/2 anslutningspooler endast upprĂ€ttar en enda anslutning till varje överordnad vĂ€rd Ă„t gĂ„ngen, om det finns fyra arbetsflöden, kommer det att finnas fyra HTTP/2 anslutningar till den överordnade vĂ€rden i ett stabilt tillstĂ„nd.
  • Anledningen till att Envoy fungerar pĂ„ detta sĂ€tt Ă€r att genom att hĂ„lla allt i ett enda arbetsflöde kan nĂ€stan all kod skrivas utan blockeringar och som om den Ă€r entrĂ„dad. Denna design förenklar skrivandet av en stor mĂ€ngd kod och skalas otroligt bra för nĂ€stan obegrĂ€nsat antal arbetsflöden.
  • Men en av de viktigaste slutsatserna Ă€r att det i termer av effektivitet Ă€r mycket viktigt att justera parametern --concurrency. Att ha fler arbetsflöden Ă€n nödvĂ€ndigt kommer att leda till minnesförlust, skapande av fler inaktiva anslutningar och lĂ€gre hastighet att nĂ„ anslutningspoolen. I Lyft arbetar vĂ„ra envoy sidecar-containrar med mycket lĂ„g parallellism, sĂ„ att prestandan ungefĂ€r motsvarar tjĂ€nsterna de sitter bredvid. Vi kör Envoy som en edge proxy endast vid maximal parallellism.

Vad icke-blockerande betyder

Termen "icke-blockerande" har anvÀnts flera gÄnger i diskussioner om hur huvud- och arbetsflöden fungerar. All kod Àr skriven med förutsÀttningen att ingenting nÄgonsin blockeras. Dock Àr detta inte helt sant.

Envoy anvÀnder flera lÄngvariga lÄs i processen:

  • Som tidigare nĂ€mnts, nĂ€r loggar för Ă„tkomst skrivs, fĂ„r alla arbetsflöden samma lĂ„s innan de fyller loggbufferten i minnet. HĂ„lltiden för lĂ„sen bör vara mycket lĂ„g, men det Ă€r möjligt att denna lĂ„s kan ifrĂ„gasĂ€ttas vid hög parallellism och hög genomströmning.
  • Envoy anvĂ€nder ett mycket komplext system för att hantera statistik som Ă€r lokal för strömmen. Detta kommer att vara Ă€mnet för ett separat inlĂ€gg. Jag nĂ€mner dock kort att som en del av lokal statistikbehandling av strömmen, krĂ€vs det ibland en lĂ„s för det centrala "statistiklagret". Detta lĂ„s ska aldrig behövas.
  • Huvudströmmen behöver periodiskt samordnas med alla arbetströmmar. Detta görs genom att "publicera" frĂ„n huvudströmmen till arbetströmmarna, och ibland frĂ„n arbetströmmarna tillbaka till huvudströmmen. För att skicka krĂ€vs det en lĂ„s sĂ„ att det publicerade meddelandet kan placeras i kö för senare leverans. Dessa lĂ„s bör aldrig utsĂ€ttas för allvarlig konkurrens, men de kan tekniskt sett fortfarande blockeras.
  • NĂ€r Envoy skriver loggar till systemströmmens felström (standardfel), fĂ„r den en lĂ„s pĂ„ hela processen. Generellt sett betraktas lokal loggning i Envoy som dĂ„lig för prestandan, sĂ„ det lĂ€ggs inte mycket vikt vid att förbĂ€ttra den.
  • Det finns nĂ„gra andra tillfĂ€lliga lĂ„s, men ingen av dem Ă€r kritisk för prestanda och bör aldrig ifrĂ„gasĂ€ttas.

TrÄd-lokalt lagring (Thread local storage)

PÄ grund av sÀttet som Envoy separerar huvudströmmens ansvar frÄn arbetströmmens ansvar, finns det ett krav pÄ att komplex bearbetning kan utföras i huvudströmmen och sedan ges till varje arbetström med hög grad av parallellism. I detta avsnitt beskrivs systemet för Envoy Thread Local Storage (TLS) pÄ en hög nivÄ. I nÀsta avsnitt kommer jag att beskriva hur det anvÀnds för att hantera klustret.
[ÖversĂ€ttning] Envoy threading model (Envoy modell för trĂ„dar)

Som redan nÀmnts, hanterar huvudströmmen nÀstan alla funktioner för hantering och funktionalitet för kontrollplanet i Envoy-processen. Kontrollplanet Àr hÀr nÄgot överbelastat, men om man ser pÄ det i ramen för selve Envoy-processen och jÀmför med vidarebefordran som utförs av arbetströmmarna, verkar det rimligt. Som en allmÀn regel utför huvudströmsprocessen en viss arbetsuppgift och sedan mÄste den uppdatera varje arbetström i enlighet med resultatet av detta arbete. I det hÀr fallet behöver inte arbetstrÄden sÀtta en lÄs vid varje Ätkomst.

TLS-systemet (TrÄd-lokal lagring) Envoy fungerar pÄ följande sÀtt:

  • Koden som körs i huvudtrĂ„den kan allokera en TLS-plats för hela processen. Även om detta Ă€r abstrakt, Ă€r det i praktiken ett index i en vektor som tillhandahĂ„ller O(1) Ă„tkomst.
  • HuvudtrĂ„den kan sĂ€tta godtyckliga data i sin slot. NĂ€r detta görs publiceras data i varje arbetstrĂ„d som en vanlig hĂ€ndelse i hĂ€ndelsecykeln.
  • ArbetstrĂ„darna kan lĂ€sa frĂ„n sin TLS-slot och hĂ€mta alla lokala trĂ„ddatan som finns tillgĂ€nglig dĂ€r.

Även om detta Ă€r en mycket enkel och otroligt kraftfull paradigm som liknar RCU-konceptet (Read-Copy-Update). I grunden ser arbetstrĂ„darna aldrig nĂ„gra förĂ€ndringar i data i TLS-slotarna under arbetets gĂ„ng. Ändringar sker endast under vila mellan arbetshĂ€ndelser.

Envoy anvÀnder detta pÄ tvÄ olika sÀtt:

  • Genom att spara olika data pĂ„ varje arbetstrĂ„d görs Ă„tkomsten till dessa data utan nĂ„gon lĂ„sning.
  • Genom att spara en gemensam pekare pĂ„ globala data i lĂ€slĂ€ge pĂ„ varje arbetstrĂ„d. DĂ€rmed har varje arbetstrĂ„d en referensrĂ€knare pĂ„ data som inte kan minskas under arbetets gĂ„ng. Endast nĂ€r alla arbetare har vilat och laddat nya gemensamma data, kommer de gamla data att förstöras. Detta Ă€r identiskt med RCU.

KlusteruppdateringstrÄdning (Cluster update threading)

I det hÀr avsnittet kommer jag att beskriva hur TLS (Thread local storage) anvÀnds för att hantera klustret. Hantering av klustret inkluderar behandling av API xDS och / eller DNS samt hÀlsokontroller (health checking).
[ÖversĂ€ttning] Envoy threading model (Envoy modell för trĂ„dar)

KlustertrÄdhantering omfattar följande komponenter och steg:

  1. Klusterhanteraren Àr en komponent inom Envoy som hanterar alla kÀnda upstream i klustret, API-grÀnssnittet CDS (Cluster Discovery Service), API-grÀnssnitten SDS (Secret Discovery Service) och EDS (Endpoint Discovery Service), DNS och aktiva externa hÀlsokontroller (health checking). Den ansvarar för att skapa en "eventuellt konsekvent" (eventually consistent) bild av varje upstream i klustret, vilket inkluderar upptÀckta vÀrdar samt hÀlsostatus (health status).
  2. HÀlsokontrollverktyget (health checker) utför aktiv hÀlsokontroll och rapporterar förÀndringar i tillstÄndet till klusterchefen.
  3. CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Endpoint Discovery Service) / DNS anvÀnds för att avgöra klustertillhörighet. TillstÄndsÀndringar returneras till klusterchefen.
  4. Varje arbetstrÄd utför stÀndigt en cykel av hÀndelsebehandling.
  5. NÀr klusterchefen faststÀller att tillstÄndet för klustret har förÀndrats skapas en ny tillstÄndsbild som Àr skrivskyddad och skickas till varje arbetstrÄd.
  6. Under nÀsta viloperiod kommer arbetstrÄden att uppdatera bilden i den tilldelade TLS-platsen.
  7. Under en in- och utflödeshÀndelse som ska avgöra vÀrden för belastningsbalansering kommer belastningsbalanseraren att begÀra TLS-platsen (trÄdspecifik lagring) för att hÀmta vÀrdinformation. Inga lÄs krÀvs för detta. Observera ocksÄ att TLS Àven kan initiera hÀndelser vid uppdateringar, sÄ att belastningsbalanseringens subsystem och andra komponenter kan omberÀkna cacheminnen, datakonstruktioner etc. Detta gÄr utanför ramen för detta inlÀgg, men anvÀnds pÄ olika stÀllen i koden.

Genom att anvÀnda den ovan beskrivna proceduren kan Envoy hantera varje begÀran utan nÄgra lÄs (förutom de som tidigare beskrivits). Utöver komplexiteten i sjÀlva TLS-koden behöver den största delen av koden inte förstÄ hur flertrÄdighet fungerar, och den kan skrivas i en enkeltrÄdig stil. Detta underlÀttar skrivandet av större delen av koden utöver utmÀrkt prestanda.

Andra subsystem som anvÀnder TLS

TLS (trÄdspecifik lagring) och RCU (Read Copy Update) anvÀnds allmÀnt i Envoy.

AnvÀndningsexempel:

  • Mekanism för att Ă€ndra funktionalitet under körning: Den aktuella listan över aktiverade funktioner berĂ€knas i huvudtrĂ„den. DĂ€refter tilldelas varje arbetstrĂ„d en skrivskyddad bild med hjĂ€lp av RCU-semantik.
  • ErsĂ€ttning av routingtavlor: för rutnĂ€t som tillhandahĂ„lls av RDS (Route Discovery Service) skapas rutnĂ€t i huvudtrĂ„den. En skrivskyddad ögonblicksbild kommer sedan att tillhandahĂ„llas varje arbetstrĂ„d med hjĂ€lp av RCU-semantik (Read Copy Update). Detta gör Ă€ndringar i rutnĂ€t atomĂ€rt effektiva.
  • Caching av HTTP-huvuden: Det visar sig att berĂ€kningen av HTTP-huvudet för varje förfrĂ„gan (vid körning av ~25K+ RPS per kĂ€rna) Ă€r ganska kostsam. Envoy berĂ€knar centralt huvudet ungefĂ€r varannan sekund och förser varje arbetare via TLS och RCU.

Det finns andra fall, men de tidigare exemplen bör ge en bra förstÄelse för vad TLS anvÀnds till.

KÀnda prestandafÀllor (Known performance pitfalls)

Även om Envoy överlag fungerar ganska bra finns det nĂ„gra kĂ€nda omrĂ„den som krĂ€ver uppmĂ€rksamhet nĂ€r det anvĂ€nds med mycket hög parallellism och kapacitet:

  • Som redan beskrivits i denna artikel, fĂ„r för nĂ€rvarande alla arbetstrĂ„dar en lĂ„sning vid skrivning till minnesbufferten för loggĂ„tkomst. Vid hög parallellism och hög kapacitet kommer det att krĂ€vas en samling av loggar för varje arbetstrĂ„d till priset av oordnad leverans vid skrivning till den slutliga filen. Som ett alternativ kan varje arbetstrĂ„d upprĂ€tta en separat logg för Ă„tkomst.
  • Även om statistiken Ă€r starkt optimerad, kommer det troligen att ske atomĂ€r konkurrens om individuell statistik vid mycket hög parallellism och kapacitet. Lösningen pĂ„ detta problem Ă€r att ha rĂ€knare för en arbetstrĂ„d med periodiska Ă„terstĂ€llningar av centrala rĂ€knare. Detta kommer att diskuteras i ett kommande inlĂ€gg.
  • Den befintliga arkitekturen kommer inte att fungera bra om Envoy distribueras i ett scenario dĂ€r det finns mycket fĂ„ anslutningar som krĂ€ver betydande resurser för bearbetning. Det finns ingen garanti för att anslutningarna kommer att fördelas jĂ€mnt mellan arbetstrĂ„darna. Detta kan lösas genom att implementera lastbalansering av arbetstrĂ„dar, dĂ€r en funktion för att dela anslutningar mellan arbetstrĂ„dar implementeras.

Slutsats (Conclusion)

Envoys trÄdbaserade modell Àr utformad för att underlÀtta programmering och massiv parallellism genom potentiellt kostsamma anvÀndningar av minne och anslutningar om de inte konfigureras korrekt. Denna modell gör att den fungerar mycket bra vid ett mycket stort antal trÄdar och hög genomströmning.
Som jag kort nÀmnde pÄ Twitter kan designen ocksÄ fungera ovanpÄ en fullfjÀdrad nÀtverksstack i anvÀndarlÀge, som DPDK (Data Plane Development Kit), vilket kan leda till att vanliga servrar hanterar miljoner förfrÄgningar per sekund med fullstÀndig L7-behandling. Det ska bli mycket intressant att se vad som byggs under de kommande Ären.
En sista snabb kommentar: Jag har fÄtt frÄgan flera gÄnger om varför vi valde C++ för Envoy. Anledningen Àr fortfarande att det Àr det enda allmÀnt förekommande sprÄk pÄ industristandard, som kan bygga den arkitektur som beskrivs i det hÀr inlÀgget. C++ Àr definitivt inte lÀmpligt för alla eller ens mÄnga projekt, men för vissa anvÀndningsfall Àr det fortfarande det enda verktyget som fÄr jobbet gjort.

LĂ€nkar till kod

LÀnkar till filerna med grÀnssnitt och implementationshuvuden som diskuteras i detta inlÀgg:

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster