Raksta tulkojums:
Šis raksts man šķita diezgan interesants, un tā kā Envoy visbiežāk tiek izmantots kā daļa no «istio» vai vienkārši kā «ingress controller» Kubernetes, lielākajai daļai cilvēku nav tiešas saskarsmes ar to, piemēram, kā tas ir ar parastām Nginx vai Haproxy instalācijām. Tomēr, ja kaut kas saplīst, būtu labi saprast, kā tas ir veidots iekšējā līmenī. Es centos pārtulkot tik daudz tekstu krievu valodā, cik vien iespējams, ieskaitot speciālos vārdus, bet tiem, kuriem tas ir grūti panest, es atstāju oriģinālus iekavās. Laipni lūdzam tālāk.
Zemas līmeņa tehniskā dokumentācija par Envoy kodu pašlaik ir diezgan ierobežota. Lai to labotu, plānoju veidot rakstu sēriju par dažādām Envoy apakšsistēmām. Tā kā tas ir pirmais raksts, lūdzu, dariet man zināmu, ko jūs domājat un kas varētu būt interesanti nākamajos rakstos.
Viens no visbiežāk uzdotajiem tehniskajiem jautājumiem, ko saņemu par Envoy, ir pieprasījums pēc zemas līmeņa apraksta par izmantoto pavedienu modeli (threading model). Šajā ierakstā es aprakstīšu, kā Envoy sasaista savienojumus ar pavedieniem, kā arī aprakstu par pavedienu lokālo krātuvi (Thread Local Storage), kas tiek izmantota iekšēji, lai padarītu kodu paralēlāku un augstāka veiktspēja.
Pavedienu apraksts (Threading overview)
![[Tulkojums] Envoy modelis plūsmām (Envoy threading model)](/wp-content/uploads/2019/04/c5028463db1eb5b07ccb1a3af1f64a03.jpeg)
Envoy izmanto trīs dažādus pavedienu tipus:
- Galvenais (Main): Šis pavediens pārvalda procesa palaišanu un izbeigšanu, visu XDS (xDiscovery Service) API apstrādi, tostarp DNS, veselības pārbaudi (health checking), kopējo klastera pārvaldību un pakalpojuma procesa (runtime) vadību, statistikas atiestatīšanu, administrēšanu un vispārēju procesu pārvaldību — Linux signāli, karstā pārstartēšana (hot restart) utt. Viss, kas notiek šajā pavedienā, ir asinhrons un «nebloķējošs». Kopumā galvenais pavediens koordinē visus kritiskos funkcionalitātes procesus, kuru izpildei nav nepieciešams liels CPU apjoms. Tas ļauj lielāko daļu vadības koda rakstīt tā, it kā tas būtu vienpavediena.
- Darba (Worker): Pēc noklusējuma Envoy izveido darba pavedienu (worker thread) katram sistēmas aparatūras pavedienam, ko var kontrolēt, izmantojot opciju
--concurrencyKatrs darba pavedienu uzsāk "nebloķējošu" notikumu ciklu, kas ir atbildīgs par katra klausītāja uzklausīšanu, rakstīšanas brīdī (2017. gada 29. jūlijā) nav klausītāja segmentēšanas, jaunu savienojumu pieņemšanu, filtru steka izveidi savienojumam un visu ievades-izvades (IO) operāciju apstrādi savienojuma pastāvēšanas laikā. Atkal, tas ļauj lielāko daļu savienojumu apstrādes koda rakstīt tā, it kā tas būtu vienpavediena. - Failu (File flusher): Katrs fails, kuru raksta Envoy, būtībā piekļuves žurnāli (access logs), pašlaik ir neatkarīgā bloķējošā pavedienā. Tas ir saistīts ar to, ka rakstīšana failos, kuri tiek kešoti failu sistēmā, pat izmantojot
O_NONBLOCKdažreiz var bloķēties (ac). Kad darba pavedieniem nepieciešams ierakstīt failā, dati faktiski tiek pārvietoti buferī atmiņā, kur tie galu galā tiek izsviesti caur pavedienu file flush. Tas ir viens no koda apgabaliem, kur tehniski visi darba pavedieni var bloķēt to pašu bloķēšanu, mēģinot piepildīt buferi atmiņā.
Savienojumu apstrāde (Connection handling)
Kā jau īsi apspriests iepriekš, visi darba pavedieni klausās visus klausītājus bez jebkādas segmentēšanas. Tādējādi kodols tiek izmantots, lai prasmīgi nosūtītu saņemtos soketus darba pavedieniem. Mūsdienu kodoli kopumā ir ļoti labi šajā jomā, tie izmanto tādas funkcijas kā ievades-izvades (IO) prioritātes paaugstināšanu, lai mēģinātu piepildīt pavedienu ar darbu, pirms sākt izmantot citus pavedienus, kas arī klausās to pašu soketu, kā arī neizmanto rotējošu bloķēšanu (Spinlock) katra pieprasījuma apstrādei.
Kad savienojums ir pieņemts darba pavedienā, tas nekad nepamet šo pavedienu. Visa tālākā savienojuma apstrāde tiek pilnībā veikta darba pavedienā, tostarp jebkurš pārsūtīšanas uzvedība (forwarding behavior).
Tam ir vairākas būtiskas sekas:
- Visi savienojumu baseini Envoy ir saistīti ar darba plūsmu. Tādējādi, lai arī HTTP/2 savienojumu baseini veido tikai vienu savienojumu ar katru augstākstāvošo hostu vienlaicīgi, ja ir četri darba plūsmas, būs četri HTTP/2 savienojumi uz augstākstāvošo hostu stabili.
- Iemesls, kāpēc Envoy darbojas šādā veidā, ir tas, ka saglabājot visu vienā darba plūsmā, praktiski visu kodu var uzrakstīt bez bloķēšanas un kā vienu pavedienu. Šis dizains vienkāršo lielā daudzuma koda rakstīšanu un neticami labi mērogojas gandrīz neierobežotam darba plūsmu skaitam.
- Tomēr viens no galvenajiem secinājumiem ir tas, ka no efektivitātes viedokļa atmiņas un savienojumu baseina iestatīšana ir patiešām ļoti svarīga.
--concurrencyLielāka darba plūsmu skaita klātbūtne, nekā nepieciešams, nov leadīs pie atmiņas zuduma, lielāka skaita neaktīvo savienojumu izveidošanās un samazināta ātruma iekļūšanai savienojumu baseinā. Lyft mūsu envoy sidecar konteineri darbojas ar ļoti zemu paralelismu, tādējādi veiktspēja aptuveni atbilst pakalpojumiem, blakus kuriem tie atrodas. Mēs palaidām Envoy kā robežas starpniekserveri tikai maksimālā paralelisma gadījumā.
Kas nozīmē nebloķējošu režīmu
Termins "nebloķējošs" joprojām ir ticis lietots vairākas reizes, apspriežot, kā darbojas galvenais un darba pavedienu. Viss kods ir uzrakstīts, pieņemot, ka nekas nekad netiks bloķēts. Tomēr tas nav pilnīgi pareizi.
Envoy izmanto vairākas ilgstošas bloķēšanas procesu:
- Kā jau tika minēts, ierakstot piekļuves žurnālus, visām darba plūsmām ir vienāda bloķēšana pirms žurnāla bufera piepildīšanas atmiņā. Bloķēšanas turēšanas laiks ir jābūt ļoti zemam, taču ir iespējams, ka šī bloķēšana tiks apstrīdēta augsta paralelisma un augstas caurlaidības laikā.
- Envoy izmanto ļoti sarežģītu sistēmu statistikas apstrādei, kas ir lokāla plūsmai. Tas būs atsevišķas ziņas tēma. Tomēr es īsi pieminēšu, ka, kā daļa no lokālās statistikas apstrādes, dažreiz ir nepieciešams iegūt bloķēšanu centrālajam "statistikas noliktavai". Šī bloķēšana nekad nedrīkst būt nepieciešama.
- Galvenajai plūsmai regulāri nepieciešama koordinācija ar visām darba plūsmām. Tas tiek darīts, "publicējot" no galvenās plūsmas uz darba plūsmām, un dažreiz arī no darba plūsmām atpakaļ uz galveno plūsmu. Publicēšanai ir nepieciešama bloķēšana, lai publicētais ziņojums varētu tikt ievietots rindā turpmākai piegādei. Šīm bloķēšanām nekad nevajadzētu pakļauties nopietnam konkurences spiedienam, taču tās tehniski var tikt bloķētas.
- Kad Envoy ieraksta žurnālu sistēmas kļūdu plūsmā (standard error), tas iegūst bloķēšanu visam procesam. Kopumā Envoy lokālā žurnālu sagatavošana tiek uzskatīta par izsliktu attiecībā uz veiktspēju, tādēļ tās uzlabošanai netiek pievērsta liela uzmanība.
- Ir dažas citas nejaušas bloķēšanas, taču neviena no tām nav kritiska veiktspējai un tām nekad nevajadzētu tikt apstrīdētām.
Lokālais plūsmas krājums (Thread local storage)
Dēļ veida, kā Envoy atdala galvenās plūsmas pienākumus no darba plūsmām, pastāv prasība, ka sarežģīta apstrāde var tikt veikta galvenajā plūsmā un pēc tam nodota katrai darba plūsmai ar augstu paralelisma pakāpi. Šajā sadaļā augstā līmenī tiek aprakstīta Envoy Thread Local Storage (TLS) sistēma. Nākamajā sadaļā es aprakstīšu, kā tā tiek izmantota klastera pārvaldīšanai.
![[Tulkojums] Envoy modelis plūsmām (Envoy threading model)](/wp-content/uploads/2019/04/a47af1e8eb1f55a4d7609b3ff3ee9ce1.jpeg)
Kā jau tika aprakstīts, galvenā plūsma apstrādā praktiski visas vadības funkcijas un kontrolēšanas līdzekļus (management) Envoy procesā. Kontroles plāns šeit ir nedaudz pārslodzes, bet, ja to skatāmies pašu Envoy procesu ietvaros un salīdzinām ar pārsūtīšanu, ko veic darba plūsmas, tas šķiet saprotami. Kopumā galvenās plūsmas process veic kādu darbu, un pēc tam tas ir jāatjaunina katrai darba plūsmai saskaņā ar šī darba rezultātiem. tajā pašā laikā darba plūsmām nav nepieciešams uzstādīt bloķēšanu pie katras piekļuves.
TLS (Thread local storage) sistēma Envoy darbojas šādi:
- Kods, kas tiek izpildīts galvenajā plūsmā, var piešķirt TLS slotu visam procesam. Lai gan tas ir abstrakts, praksē tas ir indekss vektorā, kas nodrošina O(1) piekļuvi.
- Galvenā plūsma var iestatīt jebkādus datus savā slotā. Kad tas ir izdarīts, dati tiek publicēti katrā darba plūsmā kā parasts notikums notikumu ciklā.
- Darba plūsmas var lasīt no sava TLS slota un iegūt jebkādus tur pieejamos lokālos plūsmu datus.
Lai gan tas ir ļoti vienkāršs un neticami jaudīgs paraugs, kas ir ļoti līdzīgs RCU (Read-Copy-Update) bloķēšanas koncepcijai. Patiesībā darba plūsmas nekad neredz nekādus datu izmaiņas TLS slotos, kamēr tās veic darbu. Izmaiņas notiek tikai miera periodā starp darba notikumiem.
Envoy to izmanto divos dažādos veidos:
- Saglabājot dažādus datus katrā darba plūsmā, piekļuve šiem datiem notiek bez jebkādas bloķēšanas.
- Saglabājot kopīgu rādītāju uz globāliem datiem ‘tikai lasāmā’ režīmā katrā darba plūsmā. Tādējādi katrai darba plūsmai ir datu atsauces skaitītājs, kuru nevar samazināt darba izpildes laikā. Tikai kad visi darbinieki ir nomierinājušies un ielādējuši jaunus kopīgus datus, vecie dati tiks iznīcināti. Tas ir līdzīgi RCU.
Klāsteru atjaunināšanas plūsma (Cluster update threading)
Šajā sadaļā es aprakstīšu, kā TLS (Thread local storage) tiek izmantots klastera pārvaldībā. Klastera vadība ietver xDS API un / vai DNS apstrādi, kā arī veselības pārbaudi (health checking).
![[Tulkojums] Envoy modelis plūsmām (Envoy threading model)](/wp-content/uploads/2019/04/238f5718729e1f12f8f0d6507f16abe8.jpeg)
Klāsteru plūsmu pārvaldība ietver šādas sastāvdaļas un posmus:
- Klāsteru menedžeris ir komponents iekšā Envoy, kas pārvalda visus zināmos klastera uzplūdus (upstreams), CDS (Cluster Discovery Service) API, SDS (Secret Discovery Service) un EDS (Endpoint Discovery Service) API, DNS un aktīvas ārējās veselības pārbaudes (health checking). Tas ir atbildīgs par ‘galu galā konsekventa’ (eventually consistent) katra klastera uzplūda attēla izveidi, kas ietver atklātos viesus, kā arī veselības stāvokli (health status).
- Veselības pārbaudes rīks (health checker) veic aktīvu pieejamības pārbaudi un ziņo par pieejamības stāvokļa izmaiņām klastera administratoram.
- CDS (Klastera atklāšanas pakalpojums) / SDS (Slepenā atklāšanas pakalpojums) / EDS (Gala punkta atklāšanas pakalpojums) / DNS tiek izpildīti, lai noteiktu piederību klasterim. Stāvokļa izmaiņas tiek atgriezenētas klastera menedžerim.
- Katrs darba pavedienu straume pastāvīgi veic notikumu apstrādes ciklu.
- Kad klastera menedžeris nosaka, ka klastera stāvoklis ir mainījies, viņš izveido jaunu klastera stāvokļa attēlu, kas pieejams tikai lasīšanai, un nosūta to katram darba pavedienam.
- Nākamajā miera periodā darba pavediena atjauninās attēlu rezervētajā TLS slotā.
- Ievades/izvades notikuma laikā, kuram jānosaka viesdators slodzes vienmērīgai sadalīšanai, slodzes sadalītājs pieprasīs TLS slotu (Thread local storage) hosta informācijas iegūšanai. Lai to izdarītu, nav nepieciešamas bloķēšanas. Ņemiet vērā arī to, ka TLS var uzsākt notikumus, veicot atjauninājumus, tāpēc slodzes līdzsvarošanas apakšsistēmām un citiem komponentiem var būt jāpārskaita kešatmiņas, datu struktūras utt. Tas pārsniedz šī ieraksta ietvarus, bet tiek izmantots dažādās koda vietās.
Izmantojot iepriekš aprakstīto procedūru, Envoy var apstrādāt katru pieprasījumu bez bloķēšanas (izņemot iepriekš aprakstītās). Papildus TLS paša koda sarežģījumam lielākajai daļai koda nav jāizprot, kā darbojas vairāku pavedienu režīms, un to var rakstīt vienpavediena režīmā. Tas atvieglo lielākās daļas koda rakstīšanu, papildus sniedzot izcilu sniegumu.
Citas apakšsistēmas, kuras izmanto TLS
TLS (Thread local storage) un RCU (Read Copy Update) plaši tiek izmantoti Envoy.
Lietošanas piemēri:
- Funkcionalitātes izmaiņu mehānisms izpildes laikā: Pašreizējais iekļauto funkciju saraksts tiek aprēķināts galvenajā pavedienā. Pēc tam katram darba pavedienam tiek sniegts tikai lasīšanai pieejams attēls, izmantojot RCU semantiku.
- Maršruta tabulu aizstāšana: maršruta tabulām, ko nodrošina RDS (Maršruta atklāšanas pakalpojums), maršruta tabulas tiek izveidotas galvenajā pavedienā. Nolasīšanu tikai paredzētu snapshots vēlāk nodrošinās katram darba pavedienam, izmantojot RCU (Read Copy Update) semantiku. Tas padara maršruta tabulu izmaiņas atomāri efektīvas.
- HTTP virsrakstu kešatmiņa: Kā izrādās, HTTP virsraksta aprēķināšana katram pieprasījumam (veicot ~25K+ RPS uz kodolu) ir diezgan dārga. Envoy centralizēti aprēķina virsrakstu aptuveni ik pēc pussekundes un nodod to katram darbiniekam caur TLS un RCU.
Ir arī citi gadījumi, bet iepriekšējie piemēri vajadzētu nodrošināt labu izpratni par to, kādām vajadzībām tiek izmantots TLS.
Zināmās veiktspējas problēmas (Known performance pitfalls)
Lai gan kopumā Envoy darbojas pietiekami labi, ir dažas zināmas jomas, kas prasa uzmanību, kad to izmanto ļoti lielā paralēlismā un caurlaidspējā:
- Kā jau tika aprakstīts šajā rakstā, šobrīd visi darba pavedieni saņem bloķēšanu, ierakstot piekļuves žurnāla atmiņas buferī. Augstā paralēlismā un augstā caurlaidspējā piekļuves žurnāliem būs jāsakārto katram darba pavedienam, vismaz ierakstīšanas uz galīgo failu laikā. Kā alternatīvu var izveidot atsevišķu piekļuves žurnālu katram darba pavedienam.
- Lai gan statistika ir ļoti optimizēta, ļoti augsta paralēlisma un caurlaidspējas apstākļos, visticamāk, būs atomāra konkurence par individuālo statistiku. Šīs problēmas risinājums ir skaitītāji tiem pašiem darba pavedieniem ar periodisku centrālo skaitītāju atjaunošanu. Par šo tiks runāts nākamajā ierakstā.
- Esošā arhitektūra labi nedarbosies, ja Envoy tiks ieviests scenārijā, kur ir ļoti maz savienojumu, kas prasa būtiskus resursus apstrādei. Nav garantijas, ka savienojumi tiks vienmērīgi sadalīti starp darba pavedieniem. To var atrisināt, ieviešot darba savienojumu līdzsvarošanas mehānismu, kurā tiks ieviesta iespēja apmainīt savienojumus starp darba pavedieniem.
Secinājums (Conclusion)
Envoy plūsmas modelis ir izstrādāts, lai nodrošinātu programmēšanas vienkāršību un masveida paralēlismu, taču tas var radīt pārmērīgu atmiņas un savienojumu izmantošanu, ja tas nav pareizi konfigurēts. Šis modelis ļauj tam ļoti labi darboties ar ļoti augstu plūsmu un caurlaidību.
Kā es īsi minēju Twitter, dizains var darboties arī virs pilna funkcionalitātes tīkla steka lietotāja režīmā, piemēram, DPDK (Data Plane Development Kit), kas var ļaut parastajiem serveriem apstrādāt miljonus pieprasījumu sekundē ar pilnīgu L7 apstrādi. Būs ļoti interesanti redzēt, kas tiks izstrādāts tuvāko gadu laikā.
Vēl viens īss komentārs: man daudzas reizes jautāja, kāpēc mēs izvēlējāmies C++ Envoy. Iemesls joprojām ir tāds, ka tas ir vienīgais plaši izplatītais rūpnieciskā līmeņa valoda, uz kuras var uzbūvēt šajā ierakstā aprakstīto arhitektūru. C++ noteikti nav piemērots visiem vai pat daudzu projektu vajadzībām, taču noteiktām lietojumprogrammām tas joprojām ir vienīgais rīks, lai paveiktu darbu.
Saites uz kodu
Saites uz saskarnēm un galvenes īstenojumiem, par kuriem runāts šajā ierakstā:
Avots: habr.com
