[Oversættelse] Envoy threading model

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

Jeg fandt denne artikel ret interessant, og da Envoy oftest bruges som en del af "istio" eller blot som "ingress controller" af kubernetes, har de fleste mennesker ikke den samme direkte interaktion med den som f.eks. Nginx eller Haproxy installationer. Men hvis noget går i stykker, ville det være godt at forstå, hvordan det fungerer indefra. Jeg forsøgte at oversætte så meget af teksten til russisk som muligt, inklusive specielle ord; for dem, der finder det smertefuldt at se på dette, lod jeg originalerne stå i parentes. Velkommen til kat.

Teknisk dokumentation på lavt niveau til Envoy-kodebasen er i øjeblikket ret sparsom. For at afhjælpe dette planlægger jeg at lave en række blogindlæg om de forskellige undersystemer af Envoy. Da dette er den første artikel, så lad mig vide, hvad du synes, og hvad du kunne være interesseret i i fremtidige artikler.

Et af de mest almindelige tekniske spørgsmål, jeg modtager om Envoy, er at bede om en beskrivelse på lavt niveau af den trådningsmodel, den bruger. I dette indlæg vil jeg beskrive, hvordan Envoy kortlægger forbindelser til tråde, såvel som det Thread Local Storage-system, det bruger internt for at gøre koden mere parallel og højtydende.

Tråd oversigt

[Oversættelse] Envoy threading model

Envoy bruger tre forskellige typer streams:

  • Hoved: Denne tråd styrer processtart og -afslutning, al behandling af XDS (xDiscovery Service) API, inklusive DNS, sundhedstjek, generel klynge- og runtime-styring, statistik-nulstilling, administration og generel processtyring - Linux-signaler. hot genstart osv. Alt, hvad der sker i denne tråd er asynkron og "ikke-blokerende". Generelt koordinerer hovedtråden alle kritiske funktionalitetsprocesser, der ikke kræver en stor mængde CPU for at køre. Dette gør det muligt at skrive den meste kontrolkode, som om den var enkelttrådet.
  • Arbejder: Som standard opretter Envoy en arbejdstråd for hver hardwaretråd i systemet, dette kan styres ved hjælp af indstillingen --concurrency. Hver arbejdstråd kører en "ikke-blokerende" hændelsesløkke, som er ansvarlig for at lytte til hver lytter; i skrivende stund (29. juli 2017) er der ingen sønderdeling af lytteren, accepterer nye forbindelser, instansierer en filterstack for forbindelsen og behandling af alle input/output (IO) operationer i forbindelsens levetid. Igen tillader dette, at det meste af forbindelseshåndteringskoden kan skrives, som om den var enkelttrådet.
  • Fil skylning: Hver fil, som Envoy skriver, hovedsagelig adgangslogfiler, har i øjeblikket en uafhængig blokeringstråd. Dette skyldes det faktum, at skrivning til filer, der er cachelagret af filsystemet, selv når du bruger O_NONBLOCK kan nogle gange blive blokeret (suk). Når arbejdstråde skal skrive til en fil, flyttes dataene faktisk til en buffer i hukommelsen, hvor de til sidst skylles gennem tråden fil skylning. Dette er et kodeområde, hvor teknisk set alle arbejdertråde kan blokere den samme lås, mens de forsøger at fylde en hukommelsesbuffer.

Forbindelseshåndtering

Som diskuteret kort ovenfor, lytter alle arbejdertråde til alle lyttere uden at blive skåret. Således bruges kernen til elegant at sende accepterede sockets til arbejdertråde. Moderne kerner er generelt meget gode til dette, de bruger funktioner som input/output (IO) priority boosting til at prøve at fylde en tråd med arbejde, før de begynder at bruge andre tråde, der også lytter på den samme socket, og heller ikke bruger round robin låsning (Spinlock) for at behandle hver anmodning.
Når først en forbindelse er accepteret på en arbejdstråd, forlader den aldrig denne tråd. Al videre bearbejdning af forbindelsen håndteres udelukkende i arbejdertråden, inklusive eventuel videresendelsesadfærd.

Dette har flere vigtige konsekvenser:

  • Alle forbindelsespuljer i Envoy er tildelt en arbejdstråd. Så selvom HTTP/2-forbindelsespuljer kun laver én forbindelse til hver upstream-vært ad gangen, vil der være fire HTTP/2-forbindelser pr. upstream-vært i en stabil tilstand, hvis der er fire arbejdertråde.
  • Grunden til at Envoy arbejder på denne måde er, at ved at holde alt på en enkelt arbejdstråd, kan næsten al kode skrives uden blokering og som om den var enkelttrådet. Dette design gør det nemt at skrive en masse kode og skalerer utroligt godt til et næsten ubegrænset antal arbejdstråde.
  • Men en af ​​de vigtigste fordele er, at fra et hukommelsespulje og forbindelseseffektivitetssynspunkt er det faktisk meget vigtigt at konfigurere --concurrency. At have flere arbejdstråde end nødvendigt vil spilde hukommelse, skabe flere ledige forbindelser og reducere hastigheden af ​​forbindelsespooling. Hos Lyft kører vores envoy-sidevognscontainere med meget lav samtidighed, så ydeevnen svarer nogenlunde til de tjenester, de sidder ved siden af. Vi kører Envoy som en edge proxy kun ved maksimal samtidighed.

Hvad betyder ikke-blokering?

Udtrykket "ikke-blokerende" er blevet brugt flere gange indtil videre, når vi diskuterer, hvordan hoved- og arbejdstrådene fungerer. Al kode er skrevet ud fra den antagelse, at intet nogensinde er blokeret. Dette er dog ikke helt rigtigt (hvad er ikke helt sandt?).

Envoy bruger flere lange proceslåse:

  • Som diskuteret, når du skriver adgangslogfiler, opnår alle arbejdstråde den samme lås, før logbufferen i hukommelsen fyldes. Låsens holdetid bør være meget lav, men det er muligt for låsen at blive bestridt ved høj samtidighed og høj gennemstrømning.
  • Envoy bruger et meget komplekst system til at håndtere statistik, der er lokal for tråden. Dette vil være emnet for et separat indlæg. Jeg vil dog kort nævne, at det som led i behandlingen af ​​trådstatistik lokalt nogle gange er nødvendigt at anskaffe en lås på en central "statistikbutik". Denne låsning bør aldrig være påkrævet.
  • Hovedtråden skal med jævne mellemrum koordinere med alle arbejdertråde. Dette gøres ved at "publicere" fra hovedtråden til arbejdertråde, og nogle gange fra arbejdertråde tilbage til hovedtråden. Afsendelse kræver en lås, så den offentliggjorte besked kan sættes i kø til senere levering. Disse låse bør aldrig for alvor bestrides, men de kan stadig teknisk set blokeres.
  • Når Envoy skriver en log til systemfejlstrømmen (standardfejl), får den en lås på hele processen. Generelt anses Envoys lokale logning for at være forfærdelig ud fra et præstationssynspunkt, så der har ikke været meget opmærksomhed på at forbedre den.
  • Der er et par andre tilfældige låse, men ingen af ​​dem er ydeevnekritiske og bør aldrig udfordres.

Tråd lokal lagring

På grund af den måde, Envoy adskiller ansvaret for hovedtråden fra ansvarstråden for arbejdertråden, er der et krav om, at kompleks behandling kan udføres på hovedtråden og derefter leveres til hver medarbejdertråd på en meget samtidig måde. Dette afsnit beskriver Envoy Thread Local Storage (TLS) på et højt niveau. I næste afsnit vil jeg beskrive, hvordan det bruges til at administrere en klynge.
[Oversættelse] Envoy threading model

Som allerede beskrevet håndterer hovedtråden stort set al styrings- og kontrolplanfunktionalitet i Envoy-processen. Kontrolplanet er lidt overbelastet her, men når man ser på det i selve Envoy-processen og sammenligner det med den videresendelse, som arbejdertrådene laver, giver det mening. Den generelle regel er, at hovedtrådsprocessen udfører noget arbejde, og så skal den opdatere hver arbejdstråd i henhold til resultatet af det arbejde. i dette tilfælde behøver arbejdstråden ikke at anskaffe en lås på hver adgang.

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

  • Kode, der kører på hovedtråden, kan tildele en TLS-slot til hele processen. Selvom dette er abstraheret, er det i praksis et indeks til en vektor, der giver O(1) adgang.
  • Hovedtråden kan installere vilkårlige data i sin slot. Når dette er gjort, publiceres dataene til hver arbejdstråd som en normal hændelsesløkkehændelse.
  • Arbejdstråde kan læse fra deres TLS-slot og hente eventuelle trådlokale data, der er tilgængelige der.

Selvom det er et meget enkelt og utroligt kraftfuldt paradigme, ligner det meget konceptet RCU(Read-Copy-Update) blokering. I bund og grund ser arbejdstråde aldrig nogen dataændringer i TLS-slots, mens arbejdet kører. Ændring sker kun i hvileperioden mellem arbejdsbegivenheder.

Envoy bruger dette på to forskellige måder:

  • Ved at gemme forskellige data på hver arbejdstråd, kan dataene tilgås uden blokering.
  • Ved at opretholde en delt pointer til globale data i skrivebeskyttet tilstand på hver arbejdstråd. Således har hver arbejdstråd et datareferenceantal, der ikke kan nedsættes, mens arbejdet kører. Først når alle arbejdere falder til ro og uploader nye delte data, vil de gamle data blive ødelagt. Dette er identisk med RCU.

Klyngeopdateringstråde

I dette afsnit vil jeg beskrive, hvordan TLS (Thread local storage) bruges til at administrere en klynge. Klyngestyring omfatter xDS API og/eller DNS-behandling samt sundhedstjek.
[Oversættelse] Envoy threading model

Cluster flow management inkluderer følgende komponenter og trin:

  1. Cluster Manager er en komponent i Envoy, der administrerer alle kendte klynge upstreams, Cluster Discovery Service (CDS) API, Secret Discovery Service (SDS) og Endpoint Discovery Service (EDS) API'er, DNS og aktive eksterne kontroller. Den er ansvarlig for at skabe en "efterhånden konsistent" visning af hver opstrømsklynge, som inkluderer opdagede værter såvel som sundhedsstatus.
  2. Sundhedstjekkeren udfører et aktivt sundhedstjek og rapporterer ændringer i sundhedsstatus til klyngeadministratoren.
  3. CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Endpoint Discovery Service) / DNS udføres for at bestemme klyngemedlemskab. Tilstandsændringen returneres til klyngeadministratoren.
  4. Hver arbejdstråd udfører kontinuerligt en hændelsesløkke.
  5. Når klyngeadministratoren bestemmer, at tilstanden for en klynge er ændret, opretter den et nyt skrivebeskyttet snapshot af klyngens tilstand og sender det til hver arbejdstråd.
  6. I løbet af den næste stille periode vil arbejdstråden opdatere øjebliksbilledet i den tildelte TLS-plads.
  7. Under en I/O-hændelse, der formodes at bestemme værten til belastningsbalance, vil belastningsbalanceren anmode om en TLS (Thread Local Storage) slot for at få oplysninger om værten. Dette kræver ikke låse. Bemærk også, at TLS også kan udløse opdateringshændelser, så load balancere og andre komponenter kan genberegne caches, datastrukturer osv. Dette er uden for rammerne af dette indlæg, men bruges forskellige steder i koden.

Ved at bruge ovenstående procedure kan Envoy behandle enhver anmodning uden nogen form for blokering (undtagen som beskrevet tidligere). Bortset fra kompleksiteten af ​​selve TLS-koden, behøver det meste af koden ikke at forstå, hvordan multithreading fungerer og kan skrives enkelttrådet. Dette gør det meste af koden lettere at skrive udover overlegen ydeevne.

Andre undersystemer, der gør brug af TLS

TLS (Thread local storage) og RCU (Read Copy Update) er meget brugt i Envoy.

Eksempler på brug:

  • Mekanisme til ændring af funktionalitet under udførelse: Den aktuelle liste over aktiverede funktioner beregnes i hovedtråden. Hver arbejdstråd får derefter et skrivebeskyttet snapshot ved hjælp af RCU-semantik.
  • Udskiftning af rutetabeller: For rutetabeller leveret af RDS (Route Discovery Service) oprettes rutetabellerne på hovedtråden. Det skrivebeskyttede snapshot vil efterfølgende blive leveret til hver arbejdstråd ved hjælp af RCU (Read Copy Update) semantik. Dette gør ændring af rutetabeller atommæssigt effektiv.
  • HTTP-header-cache: Som det viser sig, er det ret dyrt at beregne HTTP-headeren for hver anmodning (mens du kører ~25K+ RPS pr. kerne). Envoy beregner hovedet centralt cirka hvert halve sekund og giver det til hver medarbejder via TLS og RCU.

Der er andre tilfælde, men de foregående eksempler skulle give en god forståelse af, hvad TLS bruges til.

Kendte præstationsfaldgruber

Selvom Envoy generelt klarer sig ret godt, er der et par bemærkelsesværdige områder, der kræver opmærksomhed, når det bruges med meget høj samtidighed og gennemstrømning:

  • Som beskrevet i denne artikel får alle arbejdstråde i øjeblikket en lås, når de skriver til adgangsloghukommelsesbufferen. Ved høj samtidighed og høj gennemstrømning bliver du nødt til at batchere adgangsloggene for hver arbejdstråd på bekostning af levering uden for ordre, når du skriver til den endelige fil. Alternativt kan du oprette en separat adgangslog for hver arbejdstråd.
  • Selvom statistikken er meget optimeret, vil der ved meget høj samtidighed og gennemløb sandsynligvis være atomstrid om individuelle statistikker. Løsningen på dette problem er tællere pr. arbejdstråd med periodisk nulstilling af centrale tællere. Dette vil blive diskuteret i et efterfølgende indlæg.
  • Den nuværende arkitektur vil ikke fungere godt, hvis Envoy er indsat i et scenarie, hvor der er meget få forbindelser, der kræver betydelige behandlingsressourcer. Der er ingen garanti for, at forbindelser vil blive jævnt fordelt mellem arbejdertråde. Dette kan løses ved at implementere worker connection balancering, som vil tillade udveksling af forbindelser mellem arbejdstråde.

Konklusion

Envoys threading-model er designet til at give nem programmering og massiv parallelitet på bekostning af potentielt spildende hukommelse og forbindelser, hvis de ikke er konfigureret korrekt. Denne model gør det muligt for den at yde meget godt ved meget høje trådtal og gennemløb.
Som jeg kort nævnte på Twitter, kan designet også køre oven på en fuld bruger-mode netværksstack såsom DPDK (Data Plane Development Kit), hvilket kan resultere i, at konventionelle servere håndterer millioner af anmodninger i sekundet med fuld L7-behandling. Det bliver meget interessant at se, hvad der bliver bygget i de næste par år.
En sidste hurtig kommentar: Jeg er blevet spurgt mange gange, hvorfor vi valgte C++ til Envoy. Årsagen er, at det stadig er det eneste udbredte sprog i industrikvalitet, hvor arkitekturen beskrevet i dette indlæg kan bygges. C++ er bestemt ikke egnet til alle eller endda mange projekter, men i visse tilfælde er det stadig det eneste værktøj til at få arbejdet gjort.

Links til kode

Links til filer med grænseflader og headerimplementeringer diskuteret i dette indlæg:

Kilde: www.habr.com

Tilføj en kommentar