[Translation] Envoy thread model

Oversættelse af artiklen: Envoy threading model — https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310

Denne artikel virkede ganske interessant for mig, og da Envoy oftest bruges som en del af "istio" eller bare som "ingress controller" for Kubernetes, har de fleste mennesker ikke samme direkte interaktion med det som for eksempel med typiske installationer af Nginx eller Haproxy. Men hvis noget går galt, ville det være godt at forstå, hvordan det fungerer indeni. Jeg har forsøgt at oversætte så meget tekst til russisk som muligt, inklusive specialord, og for dem, der har svært ved det, har jeg efterladt originalerne i parentes. Velkommen under katten.

Den tekniske dokumentation på lavt niveau for Envoy-kodebasen er i øjeblikket ret sparsom. For at rette op på dette planlægger jeg at lave en serie blogartikler om forskellige undersystemer af Envoy. Da dette er den første artikel, vil jeg gerne høre, hvad du synes, og hvad der kunne have været interessant i de følgende artikler.

Et af de mest almindelige tekniske spørgsmål, jeg får om Envoy, er forespørgslen om en lavniveau-beskrivelse af den anvendte threading model. I dette indlæg vil jeg beskrive, hvordan Envoy matcher forbindelser til tråde samt en beskrivelse af systemet for lokal trådlagring (Thread Local Storage), der bruges internt for at gøre koden mere parallel og højtydende.

Oversigt over tråde (Threading overview)

[Translation] Envoy thread model

Envoy bruger tre forskellige typer tråde:

  • Hovedtråd (Main): Denne tråd styrer opstart og afslutning af processen, al behandling af XDS (xDiscovery Service) API, herunder DNS, sundhedstjek (health checking), generel clusterstyring og processtyring (runtime), nulstilling af statistikker, administration og generel processtyring — Linux-signaler, hot restart osv. Alt, hvad der sker i denne tråd, er asynkront og "non-blocking". Generelt koordinerer hovedtråden alle kritiske funktionalitetsprocesser, for at udføre hvilke der ikke er behov for mange CPU-ressourcer. Dette gør det muligt at skrive størstedelen af kontrolkoden, som om den var en-trådet.
  • Arbejds-tråd (Worker): Som standard opretter Envoy en arbejds-tråd (worker thread) for hver hardware-tråd i systemet; dette kan kontrolleres med indstillingen --concurrencyHver arbejdstråd kører en "ikke-blokerende" begivenhedscyklus (event loop), som er ansvarlig for at lytte til hver lytter (listener). På tidspunktet for skrivningen af denne artikel (29. juli 2017) er der ingen segmentering (sharding) af lytteren, modtagelse af nye forbindelser, oprettelse af et filterstak til forbindelsen og behandling af alle input-output-operationer (IO) i løbet af forbindelsens eksistens. Dette giver også mulighed for, at det meste af koden til behandling af forbindelser kan skrives, som om det var enkelttrådet.
  • Filafslutter (File flusher): Hver fil, som Envoy skriver, i hovedsageligt adgangslogfiler (access logs), har i øjeblikket en uafhængig blokerende tråd. Dette skyldes, at skrivning til filer, der er cachede af filsystemet, selv når der bruges O_NONBLOCK nogle gange kan blokere (suk). Når arbejdstrådene skal skrive til en fil, bliver dataene faktisk flyttet til en buffer i hukommelsen, hvor de til sidst skyderes gennem strømmen file flush. Dette er et af de områder i koden, hvor teknisk set kan alle arbejdstråde (worker threads) blokere den samme lås (lock), mens de forsøger at fylde hukommelsesbufferten.

Håndtering af forbindelser (Connection handling)

Som kort nævnt ovenfor, lytter alle arbejdstråde til alle lyttere (listeners) uden nogen form for segmentering. På denne måde anvendes kernen til effektivt at dirigere de modtagne sockets til arbejdstrådene. Moderne kerner er generelt meget gode til dette; de bruger funktioner som IO-prioritetsforøgelse for at forsøge at fylde en tråd med arbejde, før de begynder at bruge andre tråde, der også lytter til den samme socket, og undgår at bruge spinlock til at håndtere hver anmodning.
Så snart forbindelsen accepteres af en arbejdstråd (worker thread), forlader den aldrig denne tråd (thread). Al yderligere behandling af forbindelsen håndteres fuldstændigt i arbejdstråden (worker thread), inklusive enhver videresendelsesadfærd (forwarding behavior).

Dette har flere vigtige konsekvenser:

  • Alle forbindelsespools i Envoy tilhører arbejdstrømmen. Så selvom HTTP/2-forbindelsespools kun laver én forbindelse til hver overliggende vært ad gangen, vil der være fire HTTP/2-forbindelser til den overliggende vært i en stabil tilstand, hvis der er fire arbejdstrømme.
  • Årsagen til, at Envoy fungerer på denne måde, er, at ved at holde alt i én arbejdstrøm, kan næsten hele koden skrives uden blokeringer og som om den er entrådet. Dette design gør det lettere at skrive en stor mængde kode og skalerer utrolig godt til næsten ubegrænsede antal arbejdstrømme.
  • Men en af de vigtigste konklusioner er, at hvad angår effektivitet, er det faktisk meget vigtigt at justere parameteren --concurrency. At have flere arbejdstrømme, end der er nødvendigt, vil føre til hukommelsestab, skabe flere inaktive forbindelser og reducere hastigheden for at nå forbindelsespoolen. I Lyft kører vores Envoy sidecar-containere med meget lav parallellisme, så ydeevnen svarer omtrent til de tjenester, de sidder ved siden af. Vi kører Envoy som en kantproxy (edge) kun ved maksimal parallellisme (concurrency).

Hvad ikke-blokerende betyder (What non-blocking means)

Begrebet "ikke-blokerende" er blevet brugt flere gange i diskussionen om, hvordan hoved- og arbejdstrømme fungerer. Al koden er skrevet under forudsætning af, at ingenting nogensinde blokeres. Dette er dog ikke helt korrekt (what is not quite correct?).

Envoy bruger flere langvarige processer til at blokere:

  • Som nævnt, når der skrives adgangsloger, får alle arbejdstrømme den samme lås, før de fylder logbufferen i hukommelsen. Tiden for at holde låsen skal være meget lav, men det er muligt, at denne lås bliver udfordret ved høj parallellisme og høj båndbredde.
  • Envoy bruger et meget komplekst system til behandling af statistik, der er lokalt for strømmen. Dette vil blive emnet for et separat indlæg. Men jeg vil kort nævne, at som en del af den lokale behandling af strømstatistik er det nogle gange nødvendigt at få en lås for det centrale "statslager". Denne lås skal aldrig være nødvendig.
  • Hovedstrømmen har periodevis brug for koordination med alle arbejdsstrømme. Dette gøres ved at "publicere" fra hovedstrømmen til arbejdsstrømmene, og nogle gange også fra arbejdsstrømmene tilbage til hovedstrømmen. For at sende kræves der en lås, så den publicerede besked kan placeres i køen til senere levering. Disse låse bør aldrig være udsat for alvorlig konkurrence, men de kan stadig teknisk blive blokeret.
  • Når Envoy logger til systemets fejlstream (standardfejl), får den lås på hele processen. Generelt betragtes lokal logging i Envoy som frygtelig med hensyn til ydeevne, så der er ikke meget opmærksomhed på at forbedre det.
  • Der er flere andre sporadiske låse, men ingen af dem er kritiske for ydeevnen og bør aldrig udfordres.

Lokal trådlagring (Thread local storage)

På grund af måden, hvorpå Envoy adskiller ansvaret for hovedstrømmen fra ansvaret for arbejdsstrømmen, er der et krav om, at kompleks behandling kan udføres i hovedstrømmen og derefter gives til hver arbejdsstrøm med høj grad af parallelisme. I dette afsnit beskrives Envoy Thread Local Storage (TLS) systemet på et højt niveau. I det næste afsnit vil jeg beskrive, hvordan det bruges til at styre klyngen.
[Translation] Envoy thread model

Som allerede beskrevet, håndterer hovedstrømmen stort set alle funktioner relateret til ledelse (management) og funktionalitet for kontrolplanet (control plane) i Envoy-processen. Kontrolplanet her er lidt overbebyrdet, men når man ser det i rammerne af selve Envoy-processen og sammenligner med den videresendelse, som arbejdsstrømmene udfører, virker det fornuftigt. Som en generel regel udfører hovedstrømmen noget arbejde, og derefter skal den opdatere hver arbejdsstrøm i overensstemmelse med resultatet af dette arbejde. Der arbejdstråd behøver ikke at sætte en lås ved hver adgang..

TLS-systemet (Thread local storage) Envoy fungerer på følgende måde:

  • Koden, der kører i hovedtråden, kan allokere en TLS-slot for hele processen. Selvom dette er abstrakt, er det i praksis et indeks i en vektor, der sikrer O(1) adgang.
  • Hovedtråden kan sætte vilkårlige data i sin slot. Når dette gøres, publiceres dataene i hver arbejdstråd som en almindelig begivenhed i event-loop.
  • Arbejdsstrådene kan læse fra deres TLS-slot og hente eventuelle lokale tråddatas, der er tilgængelige der.

Selvom dette er en meget simpel og utrolig kraftfuld paradigme, der ligner RCU (Read-Copy-Update) låsekonceptet. I essensen ser arbejdsstrådene aldrig nogen dataændringer i TLS-slotene, mens de udfører arbejdet. Ændringen sker kun i hvileperioden mellem arbejdsbegivenheder.

Envoy bruger dette på to forskellige måder:

  • Ved at gemme forskellige data på hver arbejdstråd, hvilket giver adgang til disse data uden nogen lås.
  • Ved at gemme en fælles peger på globale data i 'kun læse'-tilstand på hver arbejdstråd. På denne måde har hver arbejdstråd en reference tæller til dataene, som ikke kan reduceres under arbejdet. Først når alle arbejdere er stille og indlæser nye fælles data, vil de gamle data blive destrueret. Dette er identisk med RCU.

Opdateringstråd til klynger (Cluster update threading)

I dette afsnit vil jeg beskrive, hvordan TLS (Thread local storage) bruges til at administrere klyngen. Administration af klyngen involverer behandling af API xDS og / eller DNS, samt sundhedstjek (health checking).
[Translation] Envoy thread model

Klustertrådsadministration omfatter følgende komponenter og trin:

  1. Kluster-manageren er en komponent inde i Envoy, der styrer alle kendte upstreams for klyngen, API-grænsefladerne CDS (Cluster Discovery Service), SDS (Secret Discovery Service) og EDS (Endpoint Discovery Service), DNS og aktive eksterne sundhedstjek (health checking). Den er ansvarlig for at skabe en 'endelig konsistent' repræsentation af hver upstream for klyngen, der inkluderer de opdagede værter samt sundhedstilstanden.
  2. Healthcheck-værktøjet udfører aktiv tilstandskontrol og rapporterer om ændringer i arbejdsstatus til cluster-manageren.
  3. CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Endpoint Discovery Service) / DNS udføres for at bestemme tilknytningen til klyngen. Ændringer i status returneres til cluster-manageren.
  4. Hver arbejdstråd udfører konstant en hændelsescykel.
  5. Når cluster-manageren fastslår, at tilstanden for klyngen er ændret, opretter den et nyt, skrivebeskyttet statusbillede af klyngen og sender det til hver arbejdstråd.
  6. I løbet af den næste hvileperiode opdaterer arbejdstråden billedet i det dedikerede TLS-slot.
  7. Under en I/O-hændelse, der skal bestemme værten til belastningsfordeling, vil belastningsbalanceren anmode om TLS (Thread local storage)-slot til at få oplysninger om værten. Dette kræver ingen låse. Vær også opmærksom på, at TLS også kan initiere hændelser ved opdateringer, så belastningsbalanceringssystemerne og andre komponenter kan genberegne caches, datastrukturer osv. Dette overstiger omfanget af dette indlæg, men bruges på forskellige steder i koden.

Ved hjælp af den ovenfor beskrevne procedure kan Envoy håndtere hver anmodning uden låse (bortset fra dem, der tidligere er nævnt). Udover kompleksiteten i selve TLS-koden er størstedelen af koden ikke nødvendig for at forstå, hvordan multitråd virker, og den kan skrives i en enkelttrådet tilstand. Dette lettede skrivningen af det meste af koden, hvilket giver fremragende ydeevne.

Andre subsystemer, der anvender TLS.

TLS (Thread local storage) og RCU (Read Copy Update) anvendes bredt i Envoy.

Eksempler på anvendelse:

  • Mekanismen for at ændre funktionalitet under kørsel: Den nuværende liste over aktiveret funktionalitet beregnes i hovedtråden. Derefter gives hver arbejdstråd et skrivebeskyttet snapshot ved hjælp af RCU-semantik.
  • Udskiftning af rutetabeller: til rute-tabeller leveret af RDS (Route Discovery Service) oprettes rutetabeller i hovedtråden. Et skrivebeskyttet snapshot vil derefter blive givet til hver arbejdstråd ved hjælp af RCU (Read Copy Update) semantik. Dette gør ændring af rutetabeller atomart effektivt.
  • Caching af HTTP-headere: Det viser sig, at beregningen af HTTP-headeren for hver forespørgsel (ved ~25K+ RPS pr. kerne) er ret kostbar. Envoy beregner centralt headeren omtrent hver halve sekund og leverer den til hver arbejdstråd via TLS og RCU.

Der er andre tilfælde, men de tidligere eksempler bør give en god forståelse af, hvad TLS bruges til.

Kendte præstationsfaldgruber (Known performance pitfalls)

Selvom Envoy generelt fungerer ganske godt, er der et par kendte områder, der kræver opmærksomhed, når den bruges med meget høj parallelisme og båndbredde:

  • Som beskrevet i denne artikel, får alle arbejdstråde aktuelt lås ved skrivning til hukommelsesbufferen for adgangsloggen. Ved høj parallelisme og høj båndbredde vil det være nødvendigt at pakke adgangslogge for hver arbejdstråd på bekostning af usorteret levering ved skrivning til den endelige fil. Som et alternativ kan der oprettes separate adgangslogs for hver arbejdstråd.
  • Selvom statistikken er meget optimeret, vil der sandsynligvis være atomar konkurrence om individuel statistik ved meget høj parallelisme og båndbredde. Løsningen på dette problem er tællere pr. arbejdstråd med periodisk nulstilling af centrale tællere. Dette vil blive diskuteret i et senere indlæg.
  • Den nuværende arkitektur fungerer ikke godt, hvis Envoy implementeres i et scenarie med meget få forbindelser, der kræver betydelige ressourcer til behandling. Der er ingen garanti for, at forbindelserne vil blive jævnt fordelt mellem arbejdstrådene. Dette kan løses ved at implementere arbejdsforbindelsesbalancering, hvor muligheden for at udveksle forbindelser mellem arbejdstrådene vil blive realiseret.

Konklusion (Conclusion)

Envoy-strømmodellen er designet til at sikre enkel programmering og massiv parallelisme gennem potentielt ineffektiv brug af hukommelse og forbindelser, medmindre de er konfigureret korrekt. Denne model gør det muligt for den at fungere særdeles godt ved meget høje antal tråde og båndbredde.
Som jeg kort nævnte på Twitter, kan designet også fungere oven på et fuldt funktionelt netværksstack i brugermode, såsom DPDK (Data Plane Development Kit), hvilket kan resultere i, at almindelige servere håndterer millioner af forespørgsler pr. sekund med fuld behandling af L7. Det bliver meget interessant at se, hvad der bliver bygget i de kommende år.
En sidste hurtig kommentar: Jeg er blevet spurgt mange gange, hvorfor vi valgte C++ til Envoy. Grunden er stadig, at det stadig er det eneste udbredte industrielt niveau sprog, hvorpå man kan bygge den arkitektur, der beskrives i dette indlæg. C++ er bestemt ikke egnet til alle eller endda mange projekter, men for bestemte brugssager er det stadig det eneste værktøj til at få arbejdet gjort.

Links til kodning

Links til filer med interface og implementering af overskrifter, der diskuteres i dette indlæg:

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster