[Oversettelse] Envoy-trådingsmodell

Oversettelse av artikkelen: Envoy-trådmodell - https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310

Jeg fant denne artikkelen ganske interessant, og siden Envoy oftest brukes som en del av "istio" eller ganske enkelt som "inngangskontrolleren" for kubernetes, har de fleste ikke den samme direkte interaksjonen med den som for eksempel med typiske Nginx- eller Haproxy-installasjoner. Men hvis noe går i stykker, ville det være greit å forstå hvordan det fungerer fra innsiden. Jeg prøvde å oversette så mye av teksten som mulig til russisk, inkludert spesielle ord; for de som synes det er vondt å se på dette, la jeg originalene i parentes. Velkommen til katt.

Teknisk dokumentasjon på lavt nivå for Envoy-kodebasen er for tiden ganske sparsom. For å bøte på dette planlegger jeg å lage en serie blogginnlegg om de ulike undersystemene til Envoy. Siden dette er den første artikkelen, vennligst gi meg beskjed om hva du synes og hva du kan være interessert i i fremtidige artikler.

Et av de vanligste tekniske spørsmålene jeg får om Envoy er å be om en lavnivåbeskrivelse av trådmodellen den bruker. I dette innlegget skal jeg beskrive hvordan Envoy kartlegger tilkoblinger til tråder, samt Thread Local Storage-systemet den bruker internt for å gjøre koden mer parallell og høy ytelse.

Trådoversikt

[Oversettelse] Envoy-trådingsmodell

Envoy bruker tre forskjellige typer strømmer:

  • Hoved: Denne tråden kontrollerer oppstart og avslutning av prosesser, all behandling av XDS (xDiscovery Service) API, inkludert DNS, helsesjekking, generell klynge- og kjøretidsadministrasjon, tilbakestilling av statistikk, administrasjon og generell prosessadministrasjon - Linux-signaler. varm omstart osv. Alt som skjer i denne tråden er asynkron og "ikke-blokkerende". Generelt koordinerer hovedtråden alle kritiske funksjonalitetsprosesser som ikke krever en stor mengde CPU for å kjøre. Dette gjør at det meste av kontrollkoden kan skrives som om den var enkeltgjenget.
  • Arbeider: Som standard oppretter Envoy en arbeidertråd for hver maskinvaretråd i systemet, dette kan kontrolleres ved å bruke alternativet --concurrency. Hver arbeidertråd kjører en "ikke-blokkerende" hendelsesløkke, som er ansvarlig for å lytte til hver lytter; i skrivende stund (29. juli 2017) er det ingen sønderdeling av lytteren, aksept av nye tilkoblinger, instansiering av en filterstabel for tilkoblingen, og behandle alle input/output-operasjoner (IO) i løpet av tilkoblingens levetid. Igjen, dette gjør at de fleste tilkoblingshåndteringskoder kan skrives som om den var entrådet.
  • Filspyler: Hver fil som Envoy skriver, hovedsakelig tilgangslogger, har for øyeblikket en uavhengig blokkeringstråd. Dette skyldes det faktum at skriving til filer som er bufret av filsystemet selv når du bruker O_NONBLOCK kan noen ganger bli blokkert (sukk). Når arbeidertråder trenger å skrive til en fil, flyttes dataene faktisk til en buffer i minnet hvor de til slutt spyles gjennom tråden spyle fil. Dette er et kodeområde der teknisk sett alle arbeidertråder kan blokkere den samme låsen mens de prøver å fylle en minnebuffer.

Tilkoblingshåndtering

Som diskutert kort ovenfor, lytter alle arbeidertråder til alle lyttere uten skjæring. Dermed blir kjernen brukt til å sende aksepterte sockets til arbeidertråder. Moderne kjerner er generelt veldig gode på dette, de bruker funksjoner som input/output (IO) prioritetsforsterkning for å prøve å fylle en tråd med arbeid før de begynner å bruke andre tråder som også lytter på samme socket, og heller ikke bruker round robin låsing (Spinlock) for å behandle hver forespørsel.
Når en tilkobling er akseptert på en arbeidertråd, forlater den aldri den tråden. All videre behandling av forbindelsen håndteres i sin helhet i arbeidertråden, inkludert eventuell videresending.

Dette har flere viktige konsekvenser:

  • Alle tilkoblingsgrupper i Envoy er tilordnet en arbeidertråd. Så selv om HTTP/2-tilkoblingspooler bare oppretter én tilkobling til hver oppstrømsvert om gangen, hvis det er fire arbeidertråder, vil det være fire HTTP/2-tilkoblinger per oppstrømsvert i en stabil tilstand.
  • Grunnen til at Envoy fungerer på denne måten er at ved å holde alt på en enkelt arbeidstråd, kan nesten all kode skrives uten blokkering og som om den var én tråd. Denne designen gjør det enkelt å skrive mye kode og skaleres utrolig godt til et nesten ubegrenset antall arbeidertråder.
  • En av de viktigste fordelene er imidlertid at fra et minnebasseng og tilkoblingseffektivitetssynspunkt er det faktisk veldig viktig å konfigurere --concurrency. Å ha flere arbeidertråder enn nødvendig vil sløse med minne, skape flere ledige tilkoblinger og redusere hastigheten på tilkoblingspooling. Hos Lyft kjører våre utsendingssidevogncontainere med svært lav samtidighet slik at ytelsen omtrent samsvarer med tjenestene de sitter ved siden av. Vi kjører Envoy som en edge proxy kun ved maksimal samtidighet.

Hva betyr ikke-blokkering?

Begrepet "ikke-blokkerende" har blitt brukt flere ganger så langt når vi diskuterer hvordan hoved- og arbeidertrådene fungerer. All kode er skrevet under antagelsen om at ingenting er blokkert. Dette er imidlertid ikke helt sant (hva er ikke helt sant?).

Envoy bruker flere lange prosesslåser:

  • Som diskutert, når du skriver tilgangslogger, får alle arbeidertråder den samme låsen før loggbufferen i minnet fylles. Låsens holdetid bør være svært lav, men det er mulig for låsen å bestrides ved høy samtidighet og høy gjennomstrømning.
  • Envoy bruker et svært komplekst system for å håndtere statistikk som er lokal for tråden. Dette vil være tema for et eget innlegg. Jeg vil imidlertid kort nevne at som en del av behandling av trådstatistikk lokalt, er det noen ganger nødvendig å anskaffe en lås på en sentral "statistikkbutikk". Denne låsingen bør aldri være nødvendig.
  • Hovedtråden må periodisk koordineres med alle arbeidertråder. Dette gjøres ved å "publisere" fra hovedtråden til arbeidertråder, og noen ganger fra arbeidertråder tilbake til hovedtråden. Sending krever lås slik at den publiserte meldingen kan settes i kø for senere levering. Disse låsene bør aldri bestrides seriøst, men de kan likevel teknisk sett blokkeres.
  • Når Envoy skriver en logg til systemfeilstrømmen (standardfeil), får den en lås på hele prosessen. Generelt anses Envoys lokale logging forferdelig fra et ytelsessynspunkt, så det har ikke vært mye oppmerksomhet til å forbedre den.
  • Det er noen få andre tilfeldige låser, men ingen av dem er ytelseskritiske og bør aldri utfordres.

Tråd lokal lagring

På grunn av måten Envoy skiller ansvaret til hovedtråden fra ansvaret til arbeidertråden, er det et krav om at kompleks behandling kan gjøres på hovedtråden og deretter leveres til hver arbeidertråd på en svært samtidig måte. Denne delen beskriver Envoy Thread Local Storage (TLS) på et høyt nivå. I neste avsnitt vil jeg beskrive hvordan det brukes til å administrere en klynge.
[Oversettelse] Envoy-trådingsmodell

Som allerede beskrevet, håndterer hovedtråden praktisk talt all administrasjons- og kontrollplanfunksjonalitet i Envoy-prosessen. Kontrollplanet er litt overbelastet her, men når du ser på det innenfor selve Envoy-prosessen og sammenligner det med videresendingen som arbeidertrådene gjør, gir det mening. Den generelle regelen er at hovedtrådsprosessen gjør noe arbeid, og så må den oppdatere hver arbeidertråd i henhold til resultatet av det arbeidet. i dette tilfellet trenger ikke arbeidertråden å anskaffe en lås på hver tilgang.

Envoys TLS (Thread local storage) system fungerer som følger:

  • Kode som kjører på hovedtråden kan tildele et TLS-spor for hele prosessen. Selv om dette er abstrahert, er det i praksis en indeks til en vektor som gir O(1)-tilgang.
  • Hovedtråden kan installere vilkårlige data i sporet. Når dette er gjort, publiseres dataene til hver arbeidertråd som en vanlig hendelsessløyfehendelse.
  • Arbeidstråder kan lese fra deres TLS-spor og hente alle trådlokale data som er tilgjengelige der.

Selv om det er et veldig enkelt og utrolig kraftig paradigme, er det veldig likt konseptet med RCU(Read-Copy-Update)-blokkering. I hovedsak ser arbeidertråder aldri noen dataendringer i TLS-sporene mens arbeidet pågår. Endring skjer kun i hvileperioden mellom arbeidshendelser.

Envoy bruker dette på to forskjellige måter:

  • Ved å lagre forskjellige data på hver arbeidstråd, kan dataene nås uten blokkering.
  • Ved å opprettholde en delt peker til globale data i skrivebeskyttet modus på hver arbeidertråd. Dermed har hver arbeidertråd en datareferansetelling som ikke kan reduseres mens arbeidet kjører. Først når alle arbeidere roer seg og laster opp nye delte data vil de gamle dataene bli ødelagt. Dette er identisk med RCU.

Klyngeoppdateringstråd

I denne delen vil jeg beskrive hvordan TLS (Thread local storage) brukes til å administrere en klynge. Klyngeadministrasjon inkluderer xDS API og/eller DNS-behandling, samt helsesjekking.
[Oversettelse] Envoy-trådingsmodell

Cluster flow management inkluderer følgende komponenter og trinn:

  1. Cluster Manager er en komponent i Envoy som administrerer alle kjente klynge oppstrøms, Cluster Discovery Service (CDS) API, Secret Discovery Service (SDS) og Endpoint Discovery Service (EDS) APIer, DNS og aktive eksterne kontroller. helsesjekking. Den er ansvarlig for å lage en "etter hvert konsistent" visning av hver oppstrømsklynge, som inkluderer oppdagede verter så vel som helsestatus.
  2. Helsesjekkeren utfører en aktiv helsesjekk og rapporterer endringer i helsestatus til klyngelederen.
  3. CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Endpoint Discovery Service) / DNS utføres for å fastslå klyngemedlemskap. Tilstandsendringen returneres til klyngelederen.
  4. Hver arbeidertråd kjører kontinuerlig en hendelsesløkke.
  5. Når klyngelederen bestemmer at tilstanden for en klynge er endret, oppretter den et nytt skrivebeskyttet øyeblikksbilde av klyngens tilstand og sender det til hver arbeidertråd.
  6. I løpet av neste stille periode vil arbeidertråden oppdatere øyeblikksbildet i det tildelte TLS-sporet.
  7. Under en I/O-hendelse som er ment å bestemme verten for å laste balanse, vil lastbalanseren be om en TLS (Thread local storage)-spor for å få informasjon om verten. Dette krever ikke låser. Merk også at TLS også kan utløse oppdateringshendelser slik at lastbalansere og andre komponenter kan beregne cacher, datastrukturer osv. Dette er utenfor rammen av dette innlegget, men brukes flere steder i koden.

Ved å bruke prosedyren ovenfor kan Envoy behandle hver forespørsel uten blokkering (bortsett fra som beskrevet tidligere). Bortsett fra kompleksiteten til selve TLS-koden, trenger ikke det meste av koden å forstå hvordan multithreading fungerer og kan skrives entråds. Dette gjør det meste av koden lettere å skrive i tillegg til overlegen ytelse.

Andre delsystemer som benytter seg av TLS

TLS (Thread local storage) og RCU (Read Copy Update) er mye brukt i Envoy.

Eksempler på bruk:

  • Mekanisme for å endre funksjonalitet under utførelse: Den gjeldende listen over aktiverte funksjoner beregnes i hovedtråden. Hver arbeidertråd får deretter et skrivebeskyttet øyeblikksbilde ved hjelp av RCU-semantikk.
  • Bytte rutetabeller: For rutetabeller levert av RDS (Route Discovery Service), opprettes rutetabellene på hovedtråden. Det skrivebeskyttede øyeblikksbildet vil deretter bli gitt til hver arbeidertråd ved hjelp av RCU (Read Copy Update) semantikk. Dette gjør endring av rutetabeller atommessig effektiv.
  • HTTP-hodebufring: Som det viser seg, er det ganske dyrt å beregne HTTP-headeren for hver forespørsel (mens du kjører ~25K+ RPS per kjerne). Envoy beregner toppteksten sentralt omtrent hvert halve sekund og gir den til hver arbeider via TLS og RCU.

Det finnes andre tilfeller, men de foregående eksemplene skal gi en god forståelse av hva TLS brukes til.

Kjente ytelsesfeller

Selv om Envoy totalt sett presterer ganske bra, er det noen få bemerkelsesverdige områder som krever oppmerksomhet når den brukes med svært høy samtidighet og gjennomstrømning:

  • Som beskrevet i denne artikkelen får for øyeblikket alle arbeidertråder en lås når de skriver til minnebufferen for tilgangslogg. Ved høy samtidighet og høy gjennomstrømming, må du gruppere tilgangsloggene for hver arbeidertråd på bekostning av levering utenfor rekkefølge når du skriver til den endelige filen. Alternativt kan du opprette en egen tilgangslogg for hver arbeidertråd.
  • Selv om statistikken er svært optimalisert, vil det ved svært høy samtidighet og gjennomstrømning sannsynligvis være atomstrid om individuell statistikk. Løsningen på dette problemet er tellere per arbeidertråd med periodisk tilbakestilling av sentrale tellere. Dette vil bli diskutert i et påfølgende innlegg.
  • Den nåværende arkitekturen vil ikke fungere bra hvis Envoy er utplassert i et scenario der det er svært få forbindelser som krever betydelige behandlingsressurser. Det er ingen garanti for at forbindelsene vil bli jevnt fordelt mellom arbeidertråder. Dette kan løses ved å implementere arbeiderforbindelsesbalansering, som vil tillate utveksling av forbindelser mellom arbeidertråder.

Konklusjon

Envoys trådmodell er designet for å gi enkel programmering og massiv parallellitet på bekostning av potensielt sløsende minne og tilkoblinger hvis de ikke er konfigurert riktig. Denne modellen gjør at den kan yte veldig bra ved svært høye trådtall og gjennomstrømning.
Som jeg kort nevnte på Twitter, kan designet også kjøres på toppen av en full brukermodus nettverksstabel som DPDK (Data Plane Development Kit), noe som kan resultere i at konvensjonelle servere håndterer millioner av forespørsler per sekund med full L7-behandling. Det blir veldig interessant å se hva som bygges de neste årene.
En siste rask kommentar: Jeg har blitt spurt mange ganger hvorfor vi valgte C++ for Envoy. Årsaken er fortsatt at det fortsatt er det eneste mye brukte språket i industriklasse som arkitekturen beskrevet i dette innlegget kan bygges på. C++ er definitivt ikke egnet for alle eller til og med mange prosjekter, men for visse brukstilfeller er det fortsatt det eneste verktøyet for å få jobben gjort.

Lenker til kode

Lenker til filer med grensesnitt og overskriftsimplementeringer diskutert i dette innlegget:

Kilde: www.habr.com

Legg til en kommentar