[Fordítás] Envoy befűzési modell

A cikk fordítása: Envoy szálfűzési modell – https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310

Nagyon érdekesnek találtam ezt a cikket, és mivel az Envoy-t leggyakrabban az „istio” részeként vagy egyszerűen a kubernetes „belépési vezérlőjeként” használják, a legtöbb embernek nincs olyan közvetlen interakciója vele, mint például a tipikus. Nginx vagy Haproxy telepítések. Viszont ha valami eltörik, jó lenne belülről megérteni, hogyan működik. Igyekeztem a szöveget a lehető legtöbbet lefordítani oroszra, beleértve a speciális szavakat is, akiknek fájdalmas ezt nézni, az eredetit zárójelben hagytam. Üdvözöljük a macskában.

Az Envoy kódbázis alacsony szintű műszaki dokumentációja jelenleg meglehetősen ritka. Ennek orvoslására egy sor blogbejegyzést tervezek az Envoy különböző alrendszereiről. Mivel ez az első cikk, kérem, ossza meg velem, mit gondol, és mi érdekelheti a következő cikkekben.

Az egyik leggyakoribb technikai kérdés, amelyet az Envoy-val kapcsolatban kapok, az általa használt menetfűzési modell alacsony szintű leírása. Ebben a bejegyzésben leírom, hogyan képezi le az Envoy a kapcsolatokat a szálakkal, valamint a Thread Local Storage rendszert, amelyet belsőleg használ a kód párhuzamosabbá és nagyobb teljesítményűvé tételéhez.

Menetelés áttekintése

[Fordítás] Envoy befűzési modell

Az Envoy három különböző típusú adatfolyamot használ:

  • Fő: Ez a szál vezérli a folyamatok indítását és leállítását, az XDS (xDiscovery Service) API minden feldolgozását, beleértve a DNS-t, az állapotellenőrzést, az általános fürt- és futásidejű kezelést, a statisztikák visszaállítását, adminisztrációt és az általános folyamatkezelést – Linux-jelek, gyors újraindítás stb. Ebben a szálban történik aszinkron és "nem blokkoló". Általánosságban elmondható, hogy a főszál koordinálja az összes olyan kritikus működési folyamatot, amelyek futtatásához nincs szükség nagy mennyiségű CPU-ra. Ez lehetővé teszi, hogy a legtöbb vezérlőkódot úgy írjuk le, mintha egyszálas lenne.
  • Munkás: Alapértelmezés szerint az Envoy létrehoz egy worker szálat minden hardverszálhoz a rendszerben, ez az opcióval szabályozható --concurrency. Minden dolgozó szál egy „nem blokkoló” eseményhurkot futtat, amely az egyes hallgatók meghallgatásáért felelős; az írás idején (29. július 2017-én) nem történik meg a hallgató felosztása, új kapcsolatok elfogadása, szűrőverem példányosítása a kapcsolat, és az összes bemeneti/kimeneti (IO) művelet feldolgozása a kapcsolat élettartama alatt. Ez ismét lehetővé teszi, hogy a legtöbb kapcsolatkezelő kódot úgy írják le, mintha egyszálas lenne.
  • Fájlöblítő: Az Envoy által írt minden fájl, elsősorban a hozzáférési naplók, jelenleg független blokkolószálat tartalmaz. Ez annak a ténynek köszönhető, hogy a fájlrendszer által gyorsítótárazott fájlok írása használat közben is O_NONBLOCK néha elakadhat (sóhaj). Amikor a dolgozó szálaknak fájlba kell írniuk, az adatok ténylegesen a memória pufferébe kerülnek, ahol végül kiürülnek a szálon. fájl öblítés. Ez az egyik kódterület, ahol technikailag az összes dolgozó szál blokkolhatja ugyanazt a zárolást, miközben megpróbálja feltölteni a memóriapuffert.

Csatlakozás kezelése

Mint fentebb röviden tárgyaltuk, az összes dolgozó szál minden hallgatót meghallgat, szilánkolás nélkül. Így a kernelt arra használjuk, hogy kecsesen küldje el az elfogadott socketeket a dolgozói szálaknak. A modern kernelek általában nagyon jók ebben, olyan funkciókat használnak, mint a bemeneti/kimeneti (IO) prioritásnövelés, hogy megpróbáljanak egy szálat munkával feltölteni, mielőtt más olyan szálakat kezdenének használni, amelyek szintén hallgatnak ugyanazon a socketen, és nem használnak körpróbát. zárolás (Spinlock) az egyes kérések feldolgozásához.
Ha egy kapcsolat elfogadásra kerül egy dolgozó szálon, az soha nem hagyja el azt a szálat. A kapcsolat minden további feldolgozása teljes egészében a munkaszálban történik, beleértve az esetleges továbbítási viselkedést is.

Ennek számos fontos következménye van:

  • Az Envoy összes kapcsolatkészlete egy dolgozói szálhoz van hozzárendelve. Tehát, bár a HTTP/2 kapcsolatkészletek egyszerre csak egy kapcsolatot létesítenek minden egyes upstream gazdagéphez, ha négy munkaszál van, akkor állandó állapotban négy HTTP/2 kapcsolat lesz upstream gazdagépenként.
  • Az Envoy azért működik így, mert ha mindent egyetlen munkaszálon tart, szinte az összes kód blokkolás nélkül írható, és mintha egyetlen szálon lenne. Ez a kialakítás megkönnyíti a sok kód írását, és hihetetlenül jól skálázható szinte korlátlan számú dolgozó szálra.
  • Az egyik legfontosabb dolog azonban az, hogy memóriakészlet és kapcsolathatékonyság szempontjából valójában nagyon fontos a --concurrency. A szükségesnél több feldolgozószál használata memóriapazarlást, több tétlen kapcsolatot hoz létre, és csökkenti a kapcsolatkészletezés sebességét. A Lyftnél az küldött oldalkocsis konténereink nagyon alacsony párhuzamossággal futnak, így a teljesítmény nagyjából megegyezik a mellettük lévő szolgáltatásokkal. Az Envoy-t élproxyként csak maximális párhuzamosság mellett futtatjuk.

Mit jelent a nem blokkolás?

A "nem blokkoló" kifejezést eddig többször használták a fő és a munkaszál működésének megvitatásakor. Minden kód abból a feltételezésből van megírva, hogy soha semmi nincs blokkolva. Ez azonban nem teljesen igaz (mi nem teljesen igaz?).

Az Envoy több hosszú folyamatzárat használ:

  • Amint már említettük, hozzáférési naplók írásakor az összes dolgozó szál ugyanazt a zárolást kapja, mielőtt a memórián belüli naplópuffer megtelne. A zártartási időnek nagyon alacsonynak kell lennie, de lehetséges, hogy a zárat nagy egyidejűség és nagy áteresztőképesség mellett megtámadják.
  • Az Envoy egy nagyon összetett rendszert használ a szálon belüli helyi statisztikák kezelésére. Ez egy külön bejegyzés témája lesz. Mindazonáltal röviden megemlítem, hogy a szálstatisztika helyi feldolgozásának részeként néha szükséges egy központi "statisztikai tároló" zárolása. Ezt a zárolást soha nem szabad megkövetelni.
  • A főszálnak időnként koordinálnia kell az összes dolgozó szálat. Ez úgy történik, hogy "közzététel" történik a fő szálról a dolgozói szálakra, és néha a dolgozói szálakról vissza a főszálra. A küldéshez zárolás szükséges, hogy a közzétett üzenetet sorba lehessen állítani későbbi kézbesítésre. Ezeket a zárakat soha nem szabad komolyan megkérdőjelezni, de technikailag továbbra is blokkolhatók.
  • Amikor a Envoy naplót ír a rendszerhiba-folyamba (normál hiba), az a teljes folyamatot lezárja. Általánosságban elmondható, hogy az Envoy helyi fakitermelése a teljesítmény szempontjából borzalmasnak számít, ezért nem fordítottak nagy figyelmet a javítására.
  • Van még néhány véletlenszerű zárolás, de egyik sem kritikus a teljesítmény szempontjából, és soha nem szabad megkérdőjelezni.

Szál helyi tárhely

Mivel az Envoy elválasztja a fő szál és a dolgozói szál felelősségeit, követelmény, hogy a fő szálon komplex feldolgozást lehessen végezni, majd az egyes dolgozói szálakat rendkívül párhuzamosan kell biztosítani. Ez a szakasz magas szinten írja le az Envoy Thread Local Storage-t (TLS). A következő részben leírom, hogyan használható a fürt kezelésére.
[Fordítás] Envoy befűzési modell

Amint már leírtuk, a fő szál gyakorlatilag az összes felügyeleti és vezérlősík funkciót kezeli az Envoy folyamatban. A vezérlősík itt egy kicsit túlterhelt, de ha magán a Küldött folyamaton belül nézzük, és összehasonlítjuk a dolgozó szálak által végzett továbbítással, akkor van értelme. Az általános szabály az, hogy a főszál-folyamat elvégzi a munkát, majd frissítenie kell az egyes munkaszálakat a munka eredményének megfelelően. ebben az esetben a dolgozó szálnak nem kell minden hozzáférésnél zárolást szereznie.

Az Envoy TLS (Thread local storage) rendszere a következőképpen működik:

  • A fő szálon futó kód lefoglalhat egy TLS-helyet a teljes folyamat számára. Bár ez absztrahált, a gyakorlatban ez egy index egy vektorba, amely O(1) hozzáférést biztosít.
  • A főszál tetszőleges adatokat telepíthet a slotjába. Ha ez megtörtént, az adatok normál eseményhurok eseményként kerülnek közzétételre az egyes feldolgozószálakban.
  • A dolgozói szálak olvashatnak a TLS-nyílásukból, és lekérhetik az ott elérhető szálhelyi adatokat.

Noha ez egy nagyon egyszerű és hihetetlenül erős paradigma, nagyon hasonlít az RCU (Read-Copy-Update) blokkolás koncepciójához. Lényegében a munkaszálak soha nem látnak semmilyen adatváltozást a TLS-résekben, miközben a munka fut. Változás csak a munkaesemények közötti pihenőidőben történik.

Az Envoy ezt kétféleképpen használja:

  • Az egyes dolgozói szálakon különböző adatok tárolásával az adatok blokkolás nélkül elérhetők.
  • A globális adatokra mutató megosztott mutató fenntartásával írásvédett módban minden egyes munkaszálon. Így minden munkaszálnak van egy adathivatkozási száma, amely nem csökkenthető a munka futása közben. A régi adatok csak akkor semmisülnek meg, ha minden dolgozó megnyugszik, és új megosztott adatokat tölt fel. Ez megegyezik az RCU-val.

Fürtfrissítési szálfűzés

Ebben a részben leírom, hogyan használják a TLS-t (Thread local storage) egy fürt kezelésére. A fürtkezelés magában foglalja az xDS API és/vagy DNS feldolgozást, valamint az állapotellenőrzést.
[Fordítás] Envoy befűzési modell

A fürtfolyam-kezelés a következő összetevőket és lépéseket tartalmazza:

  1. A Cluster Manager az Envoy egyik összetevője, amely kezeli az összes ismert fürt upstream-et, a Cluster Discovery Service (CDS) API-t, a Secret Discovery Service (SDS) és az Endpoint Discovery Service (EDS) API-kat, a DNS-t és az aktív külső ellenőrzéseket. Feladata egy „végül konzisztens” nézet létrehozása minden egyes upstream klaszterről, amely magában foglalja a felfedezett gazdagépeket és az egészségi állapotot is.
  2. Az állapotellenőrző aktív állapotellenőrzést hajt végre, és jelenti az állapotváltozásokat a fürtkezelőnek.
  3. A CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Végpont-felderítési szolgáltatás) / DNS végrehajtása a fürttagság meghatározására szolgál. Az állapotváltozás visszakerül a fürtkezelőhöz.
  4. Minden munkaszál folyamatosan végrehajt egy eseményhurkot.
  5. Amikor a fürtkezelő megállapítja, hogy a fürt állapota megváltozott, létrehoz egy új, csak olvasható pillanatképet a fürt állapotáról, és elküldi azt minden egyes munkaszálnak.
  6. A következő csendes időszak során a dolgozó szál frissíti a pillanatképet a lefoglalt TLS-résben.
  7. Egy I/O esemény során, amelynek meg kell határoznia a gazdagép terheléselosztását, a terheléselosztó egy TLS (Thread local storage) nyílást kér, hogy információkat szerezzen a gazdagépről. Ehhez nincs szükség zárra. Vegye figyelembe azt is, hogy a TLS frissítési eseményeket is indíthat, így a terheléselosztók és más összetevők újra tudják számítani a gyorsítótárakat, adatstruktúrákat stb. Ez kívül esik ennek a bejegyzésnek a hatókörén, de a kód különböző helyein használatos.

A fenti eljárást alkalmazva a Envoy minden kérést le tud oldani (kivéve a korábban leírtakat). Eltekintve a TLS-kód összetettségétől, a legtöbb kódnak nem kell értenie a többszálú működés működését, és egyszálasan is írható. Ezáltal a kód nagy része könnyebben írható a kiváló teljesítmény mellett.

Egyéb TLS-t használó alrendszerek

A TLS-t (Thread local storage) és az RCU-t (Read Copy Update) széles körben használják az Envoyban.

Példák felhasználásra:

  • A funkcionalitás végrehajtás közbeni megváltoztatásának mechanizmusa: Az engedélyezett funkciók aktuális listája a fő szálban kerül kiszámításra. Ezután minden munkaszál egy csak olvasható pillanatképet kap az RCU szemantikával.
  • Útvonaltáblázatok cseréje: Az RDS (Route Discovery Service) által biztosított útvonaltáblázatok esetében az útvonaltáblázatok a fő szálon jönnek létre. A csak olvasható pillanatképet ezután minden dolgozó szál megkapja az RCU (Read Copy Update) szemantikával. Ez atomosan hatékonysá teszi az útvonaltáblázatok cseréjét.
  • HTTP-fejléc gyorsítótárazása: Mint kiderült, az egyes kérések HTTP-fejlécének kiszámítása (magonként ~25 XNUMX RPS futtatása közben) meglehetősen drága. Az Envoy körülbelül fél másodpercenként központilag kiszámítja a fejlécet, és minden dolgozóhoz eljuttatja TLS-en és RCU-n keresztül.

Vannak más esetek is, de az előző példák jó megértést nyújtanak a TLS használatáról.

Ismert teljesítménybeli buktatók

Noha az Envoy összességében meglehetősen jól teljesít, van néhány figyelemre méltó terület, amelyre figyelmet kell fordítani, ha nagyon nagy párhuzamossággal és átviteli sebességgel használják:

  • A cikkben leírtak szerint jelenleg minden munkaszál zárolást kap, amikor a hozzáférési napló memóriapufferébe ír. Nagy egyidejűség és nagy átviteli sebesség esetén a végső fájlba íráskor kötegelni kell a hozzáférési naplókat minden egyes feldolgozói szálhoz a nem megfelelő kézbesítés rovására. Alternatív megoldásként külön hozzáférési naplót is létrehozhat minden dolgozói szálhoz.
  • Noha a statisztikák nagymértékben optimalizáltak, nagyon nagy egyidejűség és áteresztőképesség esetén valószínűleg atomverseny lesz az egyes statisztikákkal kapcsolatban. A probléma megoldása a dolgozói szálonkénti számlálók a központi számlálók időszakos visszaállításával. Erről egy következő bejegyzésben lesz szó.
  • A jelenlegi architektúra nem fog megfelelően működni, ha az Envoy-t olyan forgatókönyvben telepítik, ahol nagyon kevés olyan kapcsolat van, amely jelentős feldolgozási erőforrásokat igényel. Nincs garancia arra, hogy a kapcsolatok egyenletesen oszlanak el a munkaszálak között. Ez megoldható a dolgozói kapcsolatkiegyenlítés megvalósításával, amely lehetővé teszi a munkásszálak közötti kapcsolatok cseréjét.

Következtetés

Az Envoy szálfűzési modelljét úgy tervezték, hogy egyszerű programozást és masszív párhuzamosságot biztosítson a potenciálisan pazarló memória és kapcsolatok rovására, ha nem megfelelően konfigurálják. Ez a modell lehetővé teszi, hogy nagyon jól teljesítsen nagyon magas szálszám és átviteli sebesség mellett.
Amint azt a Twitteren röviden említettem, a terv futhat egy teljes felhasználói módú hálózati stack, például a DPDK (Data Plane Development Kit) tetején is, ami azt eredményezheti, hogy a hagyományos szerverek másodpercenként több millió kérést kezelnek teljes L7 feldolgozással. Nagyon érdekes lesz látni, mi épül fel a következő néhány évben.
Egy utolsó gyors megjegyzés: sokszor kérdezték már, hogy miért a C++-t választottuk az Envoy-nak. Az ok továbbra is az, hogy még mindig ez az egyetlen széles körben használt ipari minőségű nyelv, amelyen az ebben a bejegyzésben leírt architektúra megépíthető. A C++ biztosan nem alkalmas minden vagy akár sok projekthez, de bizonyos felhasználási esetekben továbbra is ez az egyetlen eszköz a munka elvégzéséhez.

Linkek a kódhoz

Hivatkozások az ebben a bejegyzésben tárgyalt interfészekkel és fejlécmegvalósításokkal rendelkező fájlokhoz:

Forrás: will.com

Hozzászólás