[Preklad] Envoy threading model

Preklad článku: Envoy threading model – https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310

Tento článok ma celkom zaujal a keďže Envoy sa najčastejšie používa ako súčasť „istio“ alebo jednoducho ako „kontrolér vstupu“ kubernetes, väčšina ľudí s ním nemá takú priamu interakciu ako napríklad s typickým Inštalácie Nginx alebo Haproxy. Ak sa však niečo pokazí, bolo by dobré pochopiť, ako to funguje zvnútra. Snažil som sa preložiť čo najviac textu do ruštiny, vrátane špeciálnych slov, pre tých, ktorým je nepríjemné pozerať sa na to, som nechal originály v zátvorkách. Vitajte v mačke.

Nízkoúrovňová technická dokumentácia pre kódovú základňu Envoy je v súčasnosti dosť riedka. Aby som to napravil, plánujem urobiť sériu blogových príspevkov o rôznych podsystémoch Envoy. Keďže toto je prvý článok, dajte mi prosím vedieť, čo si myslíte a čo by vás mohlo zaujímať v ďalších článkoch.

Jednou z najčastejších technických otázok, ktoré dostávam o Envoy, je žiadosť o nízkoúrovňový popis modelu závitovania, ktorý používa. V tomto príspevku opíšem, ako Envoy mapuje pripojenia k vláknam, ako aj systém Thread Local Storage, ktorý interne používa, aby bol kód paralelnejší a výkonnejší.

Prehľad závitov

[Preklad] Envoy threading model

Envoy používa tri rôzne typy streamov:

  • Hlavná: Toto vlákno riadi spúšťanie a ukončovanie procesov, celé spracovanie XDS (xDiscovery Service) API, vrátane DNS, kontrola stavu, všeobecná správa klastrov a runtime, reset štatistík, administrácia a všeobecná správa procesov – signály Linuxu. Hot reštart atď. Všetko, čo deje v tomto vlákne je asynchrónny a „neblokujúci“. Vo všeobecnosti hlavné vlákno koordinuje všetky kritické funkčné procesy, ktoré na spustenie nevyžadujú veľké množstvo CPU. To umožňuje napísať väčšinu riadiaceho kódu, ako keby bol jednovláknový.
  • pracovník: V predvolenom nastavení Envoy vytvorí pracovné vlákno pre každé hardvérové ​​vlákno v systéme, čo možno ovládať pomocou voľby --concurrency. Každé pracovné vlákno beží „neblokujúcu“ slučku udalostí, ktorá je zodpovedná za počúvanie každého poslucháča; v čase písania (29. júla 2017) nedochádza k shardingu poslucháča, prijímaniu nových spojení, vytváraniu inštancie zásobníka filtrov pre pripojenie a spracovanie všetkých vstupno/výstupných (IO) operácií počas životnosti pripojenia. Opäť to umožňuje, aby sa väčšina kódu na obsluhu pripojenia zapisovala tak, ako keby bol jednovláknový.
  • Splachovač súborov: Každý súbor, ktorý Envoy zapisuje, najmä prístupové protokoly, má v súčasnosti nezávislé blokovacie vlákno. Je to spôsobené tým, že zapisovanie do súborov uložených vo vyrovnávacej pamäti súborového systému aj pri použití O_NONBLOCK môže sa niekedy zablokovať (vzdych). Keď pracovné vlákna potrebujú zapisovať do súboru, údaje sa v skutočnosti presunú do vyrovnávacej pamäte v pamäti, kde sa nakoniec vyprázdnia cez vlákno. spláchnutie súboru. Toto je jedna oblasť kódu, kde môžu technicky všetky pracovné vlákna blokovať rovnaký zámok pri pokuse o naplnenie vyrovnávacej pamäte.

Spracovanie pripojenia

Ako bolo stručne popísané vyššie, všetky pracovné vlákna počúvajú všetkých poslucháčov bez akéhokoľvek strihania. Jadro sa teda používa na elegantné odosielanie akceptovaných soketov do pracovných vlákien. Moderné jadrá sú v tomto vo všeobecnosti veľmi dobré, používajú funkcie ako zvýšenie priority vstupu/výstupu (IO), aby sa pokúsili naplniť vlákno prácou predtým, ako začnú používať iné vlákna, ktoré tiež počúvajú na rovnakom sokete, a tiež nepoužívajú kruhovú robinanciu. uzamknutie (Spinlock) na spracovanie každej požiadavky.
Akonáhle je pripojenie prijaté na pracovnom vlákne, nikdy neopustí toto vlákno. Všetky ďalšie spracovanie pripojenia je spracované výlučne v pracovnom vlákne, vrátane akéhokoľvek správania pri preposielaní.

To má niekoľko dôležitých dôsledkov:

  • Všetky oblasti pripojení v Envoy sú priradené k pracovnému vláknu. Takže hoci oblasti pripojení HTTP/2 vytvárajú naraz iba jedno pripojenie ku každému hostiteľovi v smere toku, ak existujú štyri pracovné vlákna, na hostiteľa v smere toku budú v ustálenom stave štyri pripojenia HTTP/2.
  • Dôvod, prečo Envoy funguje týmto spôsobom, je ten, že tým, že sa všetko udržiava na jednom pracovnom vlákne, takmer všetok kód možno písať bez blokovania a ako keby bol jednovláknový. Tento dizajn uľahčuje písanie veľkého množstva kódu a neuveriteľne dobre sa prispôsobuje takmer neobmedzenému počtu pracovných vlákien.
  • Jedným z hlavných poznatkov však je, že z hľadiska oblasti pamäte a efektívnosti pripojenia je skutočne veľmi dôležité nakonfigurovať --concurrency. Viac pracovných vlákien, ako je potrebné, spôsobí plytvanie pamäťou, vytvorí viac nečinných pripojení a zníži rýchlosť združovania pripojení. V spoločnosti Lyft jazdia naše kontajnery postranných vozíkov s veľmi nízkou súbežnosťou, takže výkon približne zodpovedá službám, pri ktorých sú umiestnené. Spúšťame Envoy ako okrajový proxy len pri maximálnej súbežnosti.

Čo znamená neblokovanie?

Pri diskusii o tom, ako fungujú hlavné a pracovné vlákna, sa zatiaľ viackrát použil výraz „neblokovanie“. Celý kód je napísaný za predpokladu, že nikdy nie je nič blokované. Nie je to však celkom pravda (čo nie je celkom pravda?).

Envoy používa niekoľko dlhých procesných zámkov:

  • Ako už bolo spomenuté, pri zapisovaní denníkov prístupu všetky pracovné vlákna získajú rovnaký zámok predtým, ako sa naplní vyrovnávacia pamäť denníka v pamäti. Čas držania zámku by mal byť veľmi krátky, ale je možné, že zámok bude napadnutý vysokou súbežnosťou a vysokou priepustnosťou.
  • Envoy používa veľmi zložitý systém na spracovanie štatistík, ktoré sú lokálne pre vlákno. Toto bude téma samostatného príspevku. Stručne však spomeniem, že v rámci lokálneho spracovania štatistík vlákien je niekedy potrebné získať zámok na centrálnom „úložisku štatistík“. Toto uzamknutie by sa nikdy nemalo vyžadovať.
  • Hlavné vlákno sa musí pravidelne koordinovať so všetkými pracovnými vláknami. Robí sa to „zverejnením“ z hlavného vlákna do pracovných vlákien a niekedy z pracovných vlákien späť do hlavného vlákna. Odoslanie vyžaduje uzamknutie, aby sa zverejnená správa mohla zaradiť do frontu na neskoršie doručenie. Tieto zámky by nikdy nemali byť vážne napadnuté, ale stále môžu byť technicky zablokované.
  • Keď Envoy zapíše protokol do streamu systémových chýb (štandardná chyba), získa zámok na celý proces. Vo všeobecnosti sa miestne protokolovanie Envoy považuje z hľadiska výkonu za hrozné, takže jeho zlepšovaniu sa nevenovala veľká pozornosť.
  • Existuje niekoľko ďalších náhodných zámkov, ale žiadny z nich nie je kritický pre výkon a nikdy by nemal byť napadnutý.

Vlákno lokálneho úložiska

Vzhľadom na spôsob, akým Envoy oddeľuje zodpovednosti hlavného vlákna od zodpovedností pracovného vlákna, existuje požiadavka, aby bolo možné vykonať zložité spracovanie v hlavnom vlákne a potom ho poskytnúť každému pracovnému vláknu vysoko súbežným spôsobom. Táto časť popisuje Envoy Thread Local Storage (TLS) na vysokej úrovni. V ďalšej časti popíšem, ako sa používa na správu klastra.
[Preklad] Envoy threading model

Ako už bolo popísané, hlavné vlákno spracováva prakticky všetky funkcie správy a riadiacej roviny v procese Envoy. Riadiaca rovina je tu trochu preťažená, ale keď sa na to pozriete v rámci samotného procesu Envoy a porovnáte to s preposielaním, ktoré robia pracovné vlákna, dáva to zmysel. Všeobecným pravidlom je, že proces hlavného vlákna vykonáva určitú prácu a potom musí aktualizovať každé pracovné vlákno podľa výsledku tejto práce. v tomto prípade pracovné vlákno nemusí získať zámok na každý prístup.

Envoy's TLS (Thread local storage) systém funguje nasledovne:

  • Kód bežiaci na hlavnom vlákne môže celému procesu prideliť slot TLS. Aj keď je to abstrahované, v praxi je to index do vektora, ktorý poskytuje prístup O(1).
  • Hlavné vlákno môže do svojho slotu nainštalovať ľubovoľné údaje. Keď sa tak stane, údaje sa zverejnia do každého pracovného vlákna ako normálna udalosť slučky udalostí.
  • Pracovné vlákna môžu čítať zo svojho slotu TLS a získavať všetky lokálne dostupné údaje vlákna.

Hoci ide o veľmi jednoduchú a neuveriteľne výkonnú paradigmu, je veľmi podobná koncepcii blokovania RCU (Read-Copy-Update). Pracovné vlákna v podstate nikdy nevidia žiadne zmeny údajov v slotoch TLS, keď je práca spustená. K zmene dochádza iba počas obdobia odpočinku medzi pracovnými udalosťami.

Envoy to používa dvoma rôznymi spôsobmi:

  • Uložením rôznych údajov v každom pracovnom vlákne je možné k údajom pristupovať bez akéhokoľvek blokovania.
  • Udržiavaním zdieľaného ukazovateľa na globálne údaje v režime len na čítanie v každom pracovnom vlákne. Každé pracovné vlákno má teda počet odkazov na údaje, ktoré nemožno znížiť, keď je práca spustená. Až keď sa všetci pracovníci upokoja a nahrajú nové zdieľané dáta, staré dáta sa zničia. Toto je identické s RCU.

Vlákno aktualizácie klastra

V tejto časti popíšem, ako sa TLS (Thread local storage) používa na správu klastra. Správa klastrov zahŕňa spracovanie xDS API a/alebo DNS, ako aj kontrolu stavu.
[Preklad] Envoy threading model

Správa toku klastra zahŕňa nasledujúce komponenty a kroky:

  1. Cluster Manager je komponent v rámci Envoy, ktorý spravuje všetky známe upstreamy klastra, API Cluster Discovery Service (CDS), API Secret Discovery Service (SDS) a Endpoint Discovery Service (EDS), DNS a aktívne externé kontroly. Je zodpovedný za vytvorenie „nakoniec konzistentného“ pohľadu na každý upstream klaster, ktorý zahŕňa objavených hostiteľov, ako aj zdravotný stav.
  2. Kontrola stavu vykoná aktívnu kontrolu stavu a nahlási zmeny zdravotného stavu správcovi klastra.
  3. Na určenie členstva v klastri sa vykonávajú CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Endpoint Discovery Service) / DNS. Zmena stavu sa vráti správcovi klastra.
  4. Každé pracovné vlákno nepretržite vykonáva slučku udalostí.
  5. Keď správca klastra zistí, že stav klastra sa zmenil, vytvorí novú snímku stavu klastra len na čítanie a odošle ju každému pracovnému vláknu.
  6. Počas nasledujúceho pokojného obdobia pracovné vlákno aktualizuje snímku v pridelenom slote TLS.
  7. Počas I/O udalosti, ktorá má určiť hostiteľa na vyváženie zaťaženia, nástroj na vyvažovanie zaťaženia požiada o slot TLS (miestne úložisko vlákien), aby získal informácie o hostiteľovi. To nevyžaduje zámky. Všimnite si tiež, že TLS môže tiež spúšťať udalosti aktualizácie, takže nástroje na vyrovnávanie zaťaženia a ďalšie komponenty môžu prepočítať vyrovnávaciu pamäť, dátové štruktúry atď. Toto je nad rámec tohto príspevku, ale používa sa na rôznych miestach v kóde.

Použitím vyššie uvedeného postupu môže Envoy spracovať každú požiadavku bez akéhokoľvek blokovania (okrem vyššie popísaných). Okrem zložitosti samotného kódu TLS väčšina kódu nemusí rozumieť tomu, ako funguje viacvláknové spracovanie a môže byť napísané jednovláknovo. To okrem vynikajúceho výkonu uľahčuje písanie väčšiny kódu.

Ďalšie podsystémy, ktoré využívajú TLS

V Envoy sú široko používané TLS (Thread local storage) a RCU (Read Copy Update).

Príklady použitia:

  • Mechanizmus zmeny funkčnosti počas vykonávania: Aktuálny zoznam povolených funkcií sa vypočíta v hlavnom vlákne. Každé pracovné vlákno potom dostane snímku iba na čítanie pomocou sémantiky RCU.
  • Výmena tabuliek trás: Pre smerovacie tabuľky poskytované službou RDS (Route Discovery Service) sa smerovacie tabuľky vytvárajú v hlavnom vlákne. Snímka len na čítanie bude následne poskytnutá každému pracovnému vláknu pomocou sémantiky RCU (Read Copy Update). Vďaka tomu je zmena tabuliek ciest atomicky efektívna.
  • Ukladanie hlavičiek HTTP do vyrovnávacej pamäte: Ako sa ukázalo, výpočet hlavičky HTTP pre každú požiadavku (pri behu ~25K+ RPS na jadro) je dosť drahý. Envoy centrálne vypočítava hlavičku približne každú pol sekundu a poskytuje ju každému pracovníkovi prostredníctvom TLS a RCU.

Existujú aj iné prípady, ale predchádzajúce príklady by mali poskytnúť dobré pochopenie toho, na čo sa TLS používa.

Známe výkonnostné úskalia

Zatiaľ čo Envoy celkovo funguje celkom dobre, existuje niekoľko pozoruhodných oblastí, ktoré vyžadujú pozornosť, keď sa používa s veľmi vysokou súbežnosťou a priepustnosťou:

  • Ako je popísané v tomto článku, v súčasnosti všetky pracovné vlákna získajú zámok pri zapisovaní do vyrovnávacej pamäte denníka prístupu. Pri vysokej súbežnosti a vysokej priepustnosti budete musieť dávkovať prístupové protokoly pre každé pracovné vlákno na úkor doručenia mimo poradia pri zápise do konečného súboru. Prípadne môžete vytvoriť samostatný denník prístupu pre každé pracovné vlákno.
  • Hoci sú štatistiky vysoko optimalizované, pri veľmi vysokej súbežnosti a priepustnosti pravdepodobne dôjde k atómovým sporom o jednotlivých štatistikách. Riešením tohto problému sú počítadlá na pracovné vlákno s periodickým resetovaním centrálnych počítadiel. O tom sa bude diskutovať v nasledujúcom príspevku.
  • Súčasná architektúra nebude fungovať dobre, ak bude Envoy nasadený v scenári, kde je veľmi málo pripojení, ktoré vyžadujú značné prostriedky na spracovanie. Neexistuje žiadna záruka, že pripojenia budú rovnomerne rozdelené medzi pracovné vlákna. Dá sa to vyriešiť implementáciou vyvažovania pracovných pripojení, ktoré umožní výmenu pripojení medzi pracovnými vláknami.

Záver

Envoy's threading model je navrhnutý tak, aby poskytoval jednoduché programovanie a masívny paralelizmus na úkor potenciálne zbytočnej pamäte a pripojení, ak nie sú správne nakonfigurované. Tento model mu umožňuje veľmi dobrý výkon pri veľmi vysokom počte vlákien a priepustnosti.
Ako som stručne spomenul na Twitteri, dizajn môže bežať aj nad plnohodnotným sieťovým zásobníkom v užívateľskom režime, ako je napríklad DPDK (Data Plane Development Kit), čo môže viesť k tomu, že konvenčné servery spracujú milióny požiadaviek za sekundu s úplným spracovaním L7. Bude veľmi zaujímavé sledovať, čo sa postaví v najbližších rokoch.
Posledná rýchla poznámka: Mnohokrát som dostal otázku, prečo sme si vybrali C++ pre Envoy. Dôvodom zostáva, že je to stále jediný široko používaný priemyselný jazyk, v ktorom je možné vybudovať architektúru opísanú v tomto príspevku. C++ určite nie je vhodný pre všetky alebo dokonca mnohé projekty, ale pre určité prípady použitia je to stále jediný nástroj na vykonanie práce.

Odkazy na kód

Odkazy na súbory s rozhraniami a implementáciami hlavičiek, o ktorých sa hovorí v tomto príspevku:

Zdroj: hab.com

Pridať komentár