Elosztott alkalmazások építőelemei. Második megközelítés

Bejelentés

Kollégák, nyár közepén tervezem, hogy kiadok egy új ciklust a tömegszolgáltatási rendszerek tervezéséről: "VTrade Kísérlet" — kísérlet egy kereskedési rendszerekhez készült keretrendszer megírására. A ciklusban a tőzsde, aukció és bolt felépítésének elméletét és gyakorlatát fogjuk elemezni. A cikk végén javaslom, hogy szavazzunk a legérdekesebb témákra.

Elosztott alkalmazások építőelemei. Második megközelítés

Ez a cikk zárja le a ciklust az Erlang/Elixir alapú elosztott reaktív alkalmazásokról. első cikkünkben Az elméleti alapokat a reaktív architektúrához találhatjuk. A második cikk Megmutatja az ilyen rendszerek felépítésének alapvető mintáit és mechanizmusait.

Ma a kód alapjának és a projektek fejlődésének kérdéseit fogjuk megvitatni.

Szolgáltatások szervezése

A valós életben, amikor egy szolgáltatást fejlesztünk, gyakran több interakciós mintát kell egyesítenünk egyetlen vezérlőben. Például a felhasználói profilok kezelésére szolgáló users szolgáltatásnak válaszolnia kell a req-resp kérésekre, és értesítéseket kell küldenie a profilok frissítésekor a pub-sub rendszeren keresztül. Ez az eset viszonylag egyszerű: az üzenetküldésért egy vezérlő felel, amely megvalósítja a szolgáltatás logikáját és közzéteszi a frissítéseket.

A helyzet bonyolultabbá válik, amikor egy hibatűrő elosztott szolgáltatást kell megvalósítanunk. Tegyük fel, hogy a users követelményei megváltoztak:

  1. most a szolgáltatásnak öt csomóponton kell kezelnie a kéréseket,
  2. lehetőséggel kell rendelkeznie háttérfeldolgozási feladatok végrehajtására,
  3. valamint képesnek kell lennie dinamikusan kezelni a profilfrissítésekre való feliratkozási listákat.

Megjegyzés: A konzisztens adatok tárolásának és replikálásának kérdésével nem foglalkozunk. Tegyük fel, hogy ezeket a kérdéseket korábban megoldották, és a rendszerben már létezik egy megbízható és skálázható tárolási réteg, amelynek kezelői rendelkeznek a szükséges együttműködési mechanizmusokkal.

A users szolgáltatás formális leírása bonyolultabbá vált. A programozó szempontjából az üzenetküldés használatával változások minimálisak. Az első követelménynek való megfeleléshez be kell állítanunk a terheléselosztást a req-resp cserepontján.

A háttérfeladatok feldolgozásának igénye gyakran felmerül. A felhasználóknál ez lehet a felhasználói dokumentumok ellenőrzése, a feltöltött multimédia feldolgozása, vagy az adatok szinkronizálása a közösségi médiával. Ezeket a feladatokat valahogy el kell osztani a klaszteren belül, és nyomon kell követni a végrehajtás menetét. Ezért két megoldási lehetőségünk van: vagy használjunk egy feladatelosztási sablont a korábbi cikkből, vagy ha ez nem megfelelő, írjunk egy testreszabott feladatütemezőt, amely a szükséges módon kezeli a feldolgozók medencéjét.

A harmadik pont a pub-sub sablon kiterjesztését igényli. A megvalósításhoz, a pub-sub cserép létrehozása után, további lépésként el kell indítanunk e pont vezérlőt a szolgáltatásunk keretein belül. Így mintegy kiemeljük a feliratkozás és leiratkozás feldolgozásának logikáját a messaging rétegből a felhasználók megvalósításába.

Végül a feladat dekompozíciója azt mutatta, hogy a követelmények kielégítéséhez 5 példányt kell futtatnunk különböző csomópontokon és létre kell hoznunk egy további entitást - a pub-sub vezérlőt, amely felelős a feliratkozásért.
Az 5 feldolgozó elindításához nem szükséges megváltoztatni a szolgáltatás kódját. Az egyetlen további lépés a kiegyensúlyozási szabályok beállítása a cserép pontján, amiről később fogunk beszélni.
További bonyodalmak is felmerültek: a pub-sub vezérlőnek és a testreszabott feladatütemezőnek egyetlen példányban kell működnie. Ismételten, a messaging, mint alapvető szolgáltatás, biztosítania kell a vezető kiválasztásának mechanizmusát.

Vezető kiválasztása

Elosztott rendszerekben a vezető kiválasztása – az egyetlen folyamat kijelölésének eljárása, amely a terhelt elosztott feldolgozásért felelős.

A centralizálásra nem hajlamos rendszerekben általában univerzális algoritmusok és konszenzus-alapú algoritmusok, például paxos vagy raft találhatóak.
Mivel a messaging egy broker és központi elem, tudomással bír minden szolgáltatási vezérlőről – a vezetőjelöltekről. A messaging vezetőt tud kijelölni szavazás nélkül.

Minden szolgáltatás a kezdés után és a cseréphez való csatlakozáskor rendszerüzenetet kap #'$leader'{exchange = ?EXCHANGE, pid = LeaderPid, servers = Servers}. Amennyiben LeaderPid megegyezik a — a démon pid fájljának elérési útja aktuális folyamattal, őt nevezik ki vezetőnek, és a lista Servers tartalmazza az összes csomópontot és paramétereiket.
Amikor egy új csomópont megjelenik vagy egy működő csomópont lekapcsolódik a klaszterben, minden szolgáltatásvezérlő értesítést kap. #'$slave_up'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} és #'$slave_down'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} vagy.

Ezért minden komponens tud minden változásról, és a klaszterben bármikor garantáltan egy vezető van.

Közvetítők

Bonyolult elosztott feldolgozási folyamatok megvalósítása, valamint a már meglévő architektúra optimalizálási feladataihoz közvetítők alkalmazása kényelmes.
Annak érdekében, hogy ne kelljen módosítani a szolgáltatások kódját, és például további feldolgozási, irányítási vagy üzenetnaplózási feladatokat végezni, egy proxy feldolgozót helyezhetünk a szolgáltatás elé, amely elvégzi az összes kiegészítő munkát.

A pub-sub optimalizálás klasszikus példája egy elosztott alkalmazás, amelynek üzleti magja frissítési eseményeket generál, például piaci árváltozást, és a hozzáférési réteg — N kiszolgáló, amelyek websocket API-t biztosítanak a webes kliensek számára.
Ha „közvetlenül” akarjuk megoldani, a kliens kiszolgálása a következőképpen történik:

  • a kliens kapcsolódik a platformhoz. A kiszolgáló oldalán, ahol a forgalom terhelése történik, elindul egy folyamat, amely kezeli ezt a kapcsolatot.
  • A kiszolgáló fájlok következő lépése a hitelesítés és az eseményekre való feliratkozás. A folyamat meghívja a subscribe metódust a témákra.
  • Az esemény generálása után a magban azt a folyamatokhoz juttatják, amelyek a kapcsolatokat kezelik.

Tegyük fel, hogy 50000 feliratkozónk van a "news" témára. A feliratkozók egyenletesen van elosztva 5 kiszolgálón. Ennek eredményeként minden frissítés, amely a cserepontra érkezik, 50000-szer kerül reprodukálásra: 10000-szer minden kiszolgálón, a feliratkozók számának megfelelően. Ez nem éppen hatékony séma, igaz?
A helyzet javítása érdekében bevezetjük a proxyt, amelynek ugyanaz a neve, mint a cserepontról. A globális név regisztrátornak képesnek kell lennie arra, hogy a legközelebbi folyamatot visszaadja név alapján, ez fontos.

Futtassuk ezt a proxyt a hozzáférési réteg kiszolgálóin, és minden folyamatunk, amely websocket API-t kezel, rá fog iratkozni, nem a magbeli eredeti pub-sub cserepontra. A proxy csak akkor iratkozik rá a magra, ha egyedi feliratkozás található, és minden beérkező üzenetet reprodukál a saját feliratkozóin.
Végül a mag és a hozzáférési kiszolgálók között 5 üzenet fog átkelni, nem pedig 50000.

Irányítás és terheléselosztás

Req-Resp

A jelenlegi üzenetkezelési megvalósításban 7 kérésterjesztési stratégia létezik:

  • alapértelmezett. A kéréseket minden vezérlőnek továbbítják.
  • körforgásos. A kérések vezérlők közötti ciklikus elosztásra kerülnek.
  • együttható. A szolgáltatást ellátó vezérlők vezetőre és követőkre oszlanak. A kérések csak a vezetőhöz kerülnek.
  • együttható & körforgásos. A csoportnak van vezetője, de a kérések minden tag között oszlanak meg.
  • ragadós. Hash függvényt számítunk ki, amely meghatároz egy adott kezelőhöz. A következő kérések ezzel a jelzéssel ugyanahhoz a kezelőhöz kerülnek.
  • ragadós-fun. A cserepont inicializálásakor további hash számítási függvény adható meg a ragadós terheléselosztáshoz.
  • . Új. Hasonló a ragadós-funhoz, de itt lehetőség van további átirányításra, elutasításra vagy előfeldolgozásra.

Az elosztási stratégia a cserepont inicializálásakor van megadva.

A terheléselosztáson túl a messaging lehetővé teszi az entitások címkézését. Tekintsük át a rendszer címketípusait:

  • Csatlakozási címke. Lehetővé teszi, hogy megértsük, milyen csatlakozáson keresztül érkeztek az események. Akkor használatos, amikor a vezérlő folyamata ugyanahhoz a csereponthoz csatlakozik, de különböző útválasztási kulcsokkal.
  • Szolgáltatási címke. Lehetővé teszi, hogy egy szolgáltatást kezelőket csoportosítsunk, és bővítsük az útválasztás és terheléselosztás lehetőségeit. A req-resp mintázatnál az útválasztás lineáris. A kéréseket a cserepontra küldjük, onnan továbbítja a szolgáltatásnak. De ha a kezelőket logikai csoportokba kell bontani, akkor ezt címkékkel tesszük. Ha megadjuk a címkét, a kérés egy adott vezérlőcsoporthoz kerül.
  • Kérés címke. Lehetővé teszi a válaszok megkülönböztetését. Mivel a rendszerünk aszinkron, a szolgáltatás válaszainak kezeléséhez meg kell adni a RequestTag-et a kérés küldésekor. Ezzel megérthetjük, hogy melyik kérés válasza érkezett hozzánk.

Pub-sub

A pub-sub esetén minden egyszerűbb. Van egy cserepontunk, ahová üzeneteket publikálnak. A cserepont az üzeneteket az előfizetők között osztja el, akik a számukra szükséges útválasztási kulcsokra feliratkoztak (mondhatjuk, hogy ez a témák analógiája).

Skálázhatóság és hibamentesség

A rendszer skálázhatósága összességében a rendszeren belüli rétegek és komponensek skálázhatóságának mértékétől függ:

  • A szolgáltatások skálázódnak azáltal, hogy további csomópontokat adunk a klaszterhez, amelyek kezelik ezt a szolgáltatást. A tapasztalt üzemeltetés során optimalizált kiegyenlítési politika választható.
  • A messaging szolgáltatás maga egy külön klaszteren belül általában vagy azáltal skálázódik, hogy a különösen terhelt cserepontokat külön klasztercsomópontokra helyezzük, vagy proxy folyamatok hozzáadásával a különösen terhelt klaszterzónákhoz.
  • A rendszer skálázhatósága, mint jellemző, függ az architektúra rugalmasságától és attól, hogy az egyes klaszterek összevonhatók-e egy közös logikai egységgé.

A skálázás egyszerűsége és gyorsasága gyakran befolyásolja a projekt sikerét. A messaging jelenlegi megvalósítása együtt növekszik a alkalmazással. Még ha 50-60 gépes klaszterből is hiányunk van, lehetőség van a federációra. Sajnos a federálás témája túllép ezen cikk keretein.

Kölcsönzés

A terheléselosztás elemzése során már tárgyaltuk a szolgáltatáskoncepciók redundanciáját. Azonban a messaging-et is rezerválni kell. Ha egy csomópont vagy gép leáll, a messaging-nek automatikusan helyre kell állnia, mégpedig a lehető legrövidebb időn belül.

A projektjeimben kiegészítő csomópontokat használok, amelyek átveszik a terhelést a leállások esetén. Az Erlangban van egy standard megvalósítás a disztribúciós állapothoz az OTP alkalmazások számára. A disztribúciós mód pontosan ezt a helyreállítást végzi a hiba esetén, a leállt alkalmazás egy másik előre aktivált csomóponton való újraindításával. A folyamat átlátható, a hiba után az alkalmazás automatikusan átkonfigurálódik a failover csomóra. Erről a funkcióról bővebben itt olvashat. itt.

Teljesítmény

Próbáljuk meg legalább körülbelül összehasonlítani a rabbitmq teljesítményét a saját testreszabott messaging-ünkkel.
Megtaláltam a hivatalos eredményeket a rabbitmq teszteléséről az openstack csapatától.

A 6.14.1.2.1.2.2 pontban az eredeti dokumentumból az RPC CAST eredménye található:
Elosztott alkalmazások építőelemei. Második megközelítés

Előre nem fogunk semmilyen kiegészítő beállítást végezni az operációs rendszer kernelében vagy az erlang VM-en. A tesztelési feltételek:

  • erl opts: +A1 +sbtu.
  • A teszt egyetlen erlang csomóponton fut egy régi i7 mobil gépen.
  • A klaszteres tesztek 10 Gbps hálózatos szervereken zajlanak.
  • A kód docker konténerekben fut. A hálózat NAT módban üzemel.

A teszt kódja:

req_resp_bench(_) ->
  W = perftest:comprehensive(10000,
    fun() ->
      messaging:request(?EXCHANGE, default, ping, self()),
      receive
        #'$msg'{message = pong} -> ok
      after 5000 ->
        throw(timeout)
      end
    end
  ),
  true = lists:any(fun(E) -> E >= 30000 end, W),
  ok.

1. Szenárió: A teszt egy régi i7 mobiltelefonos gépen fut. A teszt, a messaging és a szolgáltatás ugyanazon a csomóponton, egy docker-konténerben fut:

Szekvenciális 10000 ciklus ~0 másodperc alatt (26987 ciklus/s)
Szekvenciális 20000 ciklus ~1 másodperc alatt (26915 ciklus/s)
Szekvenciális 100000 ciklus ~4 másodperc alatt (26957 ciklus/s)
Párhuzamos 2 100000 ciklus ~2 másodperc alatt (44240 ciklus/s)
Párhuzamos 4 100000 ciklus ~2 másodperc alatt (53459 ciklus/s)
Párhuzamos 10 100000 ciklus ~2 másodperc alatt (52283 ciklus/s)
Párhuzamos 100 100000 ciklus ~3 másodperc alatt (49317 ciklus/s)

2. forgatókönyv: 3 csomópont különböző gépeken, docker alatt (NAT).

Szekvenciális 10000 ciklus ~1 másodperc alatt (8684 ciklus/s)
Szekvenciális 20000 ciklus ~2 másodperc alatt (8424 ciklus/s)
Szekvenciális 100000 ciklus ~12 másodperc alatt (8655 ciklus/s)
Párhuzamos 2 100000 ciklus ~7 másodperc alatt (15160 ciklus/s)
Párhuzamos 4 100000 ciklus ~5 másodperc alatt (19133 ciklus/s)
Párhuzamos 10 100000 ciklus ~4 másodperc alatt (24399 ciklus/s)
Párhuzamos 100 100000 ciklus ~3 másodperc alatt (34517 ciklus/s)

Minden esetben a CPU kihasználtsága nem haladta meg a 250%-ot

Összegzés

Remélem, ez a ciklus nem tűnik tudat-dömpingnek, és tapasztalataim valódi hasznot nyújtanak a elosztott rendszerek kutatóinak és azoknak a gyakorlati szakembereknek, akik az elosztott architektúrák építése alatt állnak, és érdeklődéssel tekintenek az Erlang/Elixir irányába, de kételkednek benne...

Fénykép @chuttersnap

Csak a regisztrált felhasználók vehetnek részt a szavazáson. Kérem, jelentkezzen be., kérem.

Milyen témákat érdemes a legrészletesebben feldolgozni az "Experiment VTrade" ciklus keretein belül?

  • Elmélet: Piacok, megrendelések és azok érvényességi ideje: DAY, GTD, GTC, IOC, FOK, MOO, MOC, LOO, LOC

  • Megrendelési könyv. Elmélet és megvalósítás gyakorlata csoportosítással

  • Kereskedési vizualizáció: Tickek, sávok, felbontások. Hogyan tároljuk és hogyan fűzük össze

  • Háttérvezeték. Tervezés és fejlesztés. Munkavállalók ellenőrzése és incidensek kivizsgálása

  • API. Megvizsgáljuk, mely interfészekre van szükség és hogyan valósítsuk meg őket

  • Információ tárolása: PostgreSQL, Timescale, Tarantool kereskedési rendszerekben

  • Reaktivitás a kereskedési rendszerekben

  • Egyéb. Írok a megjegyzésekben

6 felhasználó szavazott. 4 felhasználó tartózkodott.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster