A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

Ez egy hosszú történet folytatása, amely arról szól, hogy egy nagy teljesítményű, nagy terhelésű rendszert hozunk létre, amely biztosítja az Exchange működését. Az első rész itt található: habr.com/en/post/444300

Rejtélyes hiba

Számos teszt után üzembe helyezték a frissített kereskedési és elszámolási rendszert, és egy olyan hibával találkoztunk, amelyről detektív-misztikus történetet írhattunk.

Röviddel a fő szerveren való indítás után az egyik tranzakció feldolgozása hibásan történt. A tartalék szerveren azonban minden rendben volt. Kiderült, hogy a kitevő kiszámításának egyszerű matematikai művelete a fő szerveren negatív eredményt adott a valódi érvből! Folytattuk a kutatást, és az SSE2 regiszterben egy bitben találtunk eltérést, ami a lebegőpontos számokkal végzett munka során a kerekítésért felelős.

Írtunk egy egyszerű teszt segédprogramot a kitevő kiszámításához a kerekítési bitkészlettel. Kiderült, hogy a RedHat Linux általunk használt verziójában hiba lépett fel a matematikai függvénnyel való munka során, amikor beszúrták a szerencsétlenül járt bitet. Ezt jelentettük a RedHatnek, egy idő után kaptunk tőlük egy patch-et és kiterítettük. A hiba már nem fordult elő, de nem volt világos, honnan származik ez a bit? A funkció felelős volt érte fesetround A C nyelvből gondosan elemeztük a kódunkat a feltételezett hiba keresésére: minden lehetséges helyzetet ellenőriztünk; megnézte az összes kerekítést használó függvényt; megpróbált reprodukálni egy sikertelen munkamenetet; különböző fordítókat használtak különböző opciókkal; Statikus és dinamikus elemzést használtunk.

A hiba okát nem sikerült megtalálni.

Ezután megkezdték a hardver ellenőrzését: elvégezték a processzorok terhelési tesztelését; ellenőrizte a RAM-ot; Még teszteket is lefuttattunk arra a nagyon valószínűtlen forgatókönyvre, amikor egy cellában több bites hiba lép fel. Hiába.

Végül a nagyenergiájú fizika világából származó elméletre támaszkodtunk: valami nagy energiájú részecske berepült az adatközpontunkba, átlyukasztotta a ház falát, eltalálta a processzort, és a kioldóretesz pontosan beragadt. Ezt az abszurd elméletet „neutrínónak” nevezték. Ha távol áll a részecskefizikától: a neutrínók szinte nem lépnek kölcsönhatásba a külvilággal, és biztosan nem képesek befolyásolni a processzor működését.

Mivel a hiba okát nem sikerült megállapítani, a „sértő” szervert minden esetre eltávolították a működésből.

Egy idő után elkezdtük javítani a forró biztonsági rendszert: bevezettük az úgynevezett „meleg tartalékokat” (meleg) - aszinkron replikákat. Tranzakciók folyamát kapták, amelyek különböző adatközpontokban helyezkedtek el, de a melegítők nem léptek aktív kapcsolatba más szerverekkel.

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

Miért tették ezt? Ha a biztonsági mentési kiszolgáló meghibásodik, akkor a fő szerverhez kötve lesz az új biztonsági másolat. Ez azt jelenti, hogy meghibásodás után a rendszer nem marad egy fő szervernél a kereskedési munkamenet végéig.

És amikor a rendszer új verzióját tesztelték és üzembe helyezték, ismét fellépett a kerekítési bithiba. Ráadásul a meleg szerverek számának növekedésével a hiba gyakrabban jelentkezett. Ugyanakkor az eladónak nem volt mit felmutatnia, mivel nem volt konkrét bizonyíték.

A helyzet következő elemzése során felmerült egy elmélet, hogy a probléma az operációs rendszerrel hozható összefüggésbe. Írtunk egy egyszerű programot, amely végtelen ciklusban hív meg egy függvényt fesetround, emlékszik az aktuális állapotra, és alvás közben ellenőrzi, és ez számos versengő szálon megtörténik. Miután kiválasztottuk az alvás paramétereit és a szálak számát, a segédprogram futtatása után körülbelül 5 perccel elkezdtük következetesen reprodukálni a bithibát. A Red Hat támogatása azonban nem tudta reprodukálni. Más szervereink tesztelése azt mutatta, hogy csak bizonyos processzorokkal rendelkezők érzékenyek a hibára. Ugyanakkor az új kernelre váltás megoldotta a problémát. Végül egyszerűen kicseréltük az operációs rendszert, és a hiba valódi oka tisztázatlan maradt.

És hirtelen tavaly megjelent egy cikk a HabrérólHogyan találtam hibát az Intel Skylake processzorokban?" A benne leírt szituáció nagyon hasonlított a miénkhez, de a szerző továbbvitte a vizsgálatot, és olyan elméletet állított fel, hogy a hiba a mikrokódban van. És amikor a Linux kerneleket frissítik, a gyártók frissítik a mikrokódot is.

A rendszer továbbfejlesztése

Bár megszabadultunk a hibától, ez a történet arra kényszerített minket, hogy újragondoljuk a rendszer architektúráját. Hiszen nem voltunk védve az ilyen hibák ismétlődésétől.

A következő elvek képezték az alapját a foglalási rendszer következő fejlesztéseinek:

  • Nem bízhatsz senkiben. Előfordulhat, hogy a szerverek nem működnek megfelelően.
  • Többségi fenntartás.
  • A konszenzus biztosítása. A többségi fenntartás logikus kiegészítéseként.
  • Dupla meghibásodás lehetséges.
  • Életerő. Az új forró készenléti séma nem lehet rosszabb, mint az előző. A kereskedésnek megszakítás nélkül kell folytatódnia az utolsó szerverig.
  • A látencia enyhe növekedése. Minden leállás hatalmas anyagi veszteséggel jár.
  • Minimális hálózati interakció, hogy a késleltetés a lehető legalacsonyabb legyen.
  • Új főkiszolgáló kiválasztása másodpercek alatt.

A piacon elérhető megoldások egyike sem felelt meg nekünk, a Raft protokoll pedig még gyerekcipőben járt, ezért megalkottuk a saját megoldásunkat.

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

Hálózatépítés

A foglalási rendszer mellett megkezdtük a hálózati interakció modernizálását. Az I/O alrendszer számos folyamatból állt, amelyek a legrosszabb hatással voltak a jitterre és a késleltetésre. A TCP kapcsolatokat kezelő folyamatok százai miatt kénytelenek voltunk folyamatosan váltani közöttük, és ez mikroszekundumos léptékben meglehetősen időigényes művelet. De a legrosszabb az, hogy amikor egy folyamat kapott egy csomagot feldolgozásra, elküldte azt az egyik SystemV-sorba, majd várt egy eseményre egy másik SystemV-sorból. Ha azonban nagyszámú csomópont van, az új TCP-csomag érkezése az egyik folyamatban, és a várakozási sorban lévő adatok fogadása egy másik folyamatban két versengő eseményt jelent az operációs rendszer számára. Ebben az esetben, ha nincs elérhető fizikai processzor mindkét feladathoz, akkor az egyik feldolgozásra kerül, a második pedig egy várakozó sorba kerül. A következményeket lehetetlen megjósolni.

Ilyen helyzetekben dinamikus folyamatprioritás-vezérlés használható, de ehhez erőforrás-igényes rendszerhívásokra lesz szükség. Ennek eredményeként a klasszikus epoll segítségével egy szálra váltottunk, ami nagymértékben növelte a sebességet és csökkentette a tranzakciófeldolgozási időt. Megszabadultunk a különálló hálózati kommunikációs folyamatoktól és a SystemV-n keresztüli kommunikációtól is, jelentősen csökkentettük a rendszerhívások számát és elkezdtük ellenőrizni a műveletek prioritásait. Csak az I/O alrendszeren forgatókönyvtől függően körülbelül 8-17 mikroszekundumot lehetett megtakarítani. Ezt az egyszálas sémát azóta is változatlanul használjuk, egy margós epoll szál elegendő az összes kapcsolat kiszolgálásához.

Tranzakciófeldolgozás

A rendszerünkre nehezedő növekvő terhelés szinte minden alkatrészének frissítését tette szükségessé. Sajnos azonban a processzorok órajelének növekedésében az elmúlt években tapasztalt stagnálás már nem tette lehetővé a folyamatok előrehaladását. Ezért úgy döntöttünk, hogy az Engine folyamatot három szintre osztjuk, amelyek közül a legforgalmasabb a kockázat-ellenőrző rendszer, amely értékeli a számlákon lévő források rendelkezésre állását, és maguk hozza létre a tranzakciókat. De a pénz különböző pénznemekben is előfordulhat, és ki kellett találni, hogy mi alapján kell felosztani a kérelmek feldolgozását.

A logikus megoldás a pénznemek szerinti felosztás: az egyik szerver dollárban, a másik fontban, a harmadik pedig euróban kereskedik. De ha egy ilyen sémával két tranzakciót küldenek különböző pénznemek vásárlására, akkor felmerül a pénztárca deszinkronizálásának problémája. De a szinkronizálás nehéz és drága. Ezért helyes lenne a pénztárcák és a műszerek külön-külön szilánkolása. Mellesleg, a legtöbb nyugati tőzsdének nem feladata olyan élesen ellenőrizni a kockázatokat, mint mi, ezért ez leggyakrabban offline történik. Be kellett vezetnünk az online ellenőrzést.

Magyarázzuk meg egy példával. Egy kereskedő 30 dollárt szeretne vásárolni, és a kérés a tranzakció érvényesítésére irányul: ellenőrizzük, hogy a kereskedő jogosult-e erre a kereskedési módra, és rendelkezik-e a szükséges jogokkal. Ha minden rendben van, akkor a kérés eljut a kockázatellenőrző rendszerhez, pl. a tranzakció megkötéséhez szükséges pénzeszközök ellenőrzésére. Megjegyzendő, hogy a szükséges összeg jelenleg zárolva van. A kérelmet ezután továbbítják a kereskedési rendszernek, amely jóváhagyja vagy elutasítja a tranzakciót. Tegyük fel, hogy a tranzakciót jóváhagyták - ekkor a kockázatellenőrző rendszer megjelöli, hogy a pénz fel van oldva, és a rubel dollárra változik.

Általánosságban elmondható, hogy a kockázatellenőrző rendszer összetett algoritmusokat tartalmaz, és nagy mennyiségű, igen erőforrásigényes számítást végez, és nem egyszerűen a „számlaegyenleget” ellenőrzi, ahogyan az első pillantásra tűnhet.

Amikor elkezdtük felosztani az Engine folyamatot szintekre, problémába ütköztünk: az akkoriban elérhető kód ugyanazt az adattömböt használta aktívan az érvényesítési és ellenőrzési szakaszban, ami a teljes kódbázis átírását tette szükségessé. Ennek eredményeként kölcsönvettünk egy technikát az utasítások feldolgozására a modern processzoroktól: mindegyik kis szakaszokra van felosztva, és több műveletet hajtanak végre párhuzamosan egy ciklusban.

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

A kód kis adaptációja után létrehoztunk egy folyamatot a párhuzamos tranzakciófeldolgozáshoz, amelyben a tranzakciót a folyamat 4 szakaszára osztottuk: hálózati interakció, érvényesítés, végrehajtás és az eredmény közzététele.

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

Nézzünk egy példát. Két feldolgozó rendszerünk van, soros és párhuzamos. Megérkezik az első tranzakció, amelyet mindkét rendszerben elküldenek érvényesítésre. A második tranzakció azonnal megérkezik: egy párhuzamos rendszerben azonnal munkába áll, a szekvenciális rendszerben pedig egy sorba kerül, és várja, hogy az első tranzakció végigmenjen az aktuális feldolgozási szakaszon. Vagyis a csővezetékes feldolgozás fő előnye, hogy gyorsabban dolgozzuk fel a tranzakciós sort.

Így találtuk ki az ASTS+ rendszert.

Igaz, a szállítószalagokkal sem minden olyan zökkenőmentes. Tegyük fel, hogy van egy tranzakciónk, amely a szomszédos tranzakcióban lévő adattömböket érinti; ez egy tipikus helyzet egy cserénél. Egy ilyen tranzakció nem hajtható végre folyamatban, mert hatással lehet másokra. Ezt a helyzetet adatveszélynek nevezik, és az ilyen tranzakciókat egyszerűen külön dolgozzák fel: amikor a sorban lévő „gyors” tranzakciók elfogynak, a folyamat leáll, a rendszer feldolgozza a „lassú” tranzakciót, majd újraindítja a folyamatot. Szerencsére az ilyen tranzakciók aránya a teljes áramlásban nagyon kicsi, így a csővezeték olyan ritkán áll le, hogy ez nem befolyásolja az általános teljesítményt.

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

Ezután elkezdtük megoldani a három végrehajtási szál szinkronizálásának problémáját. Az eredmény egy gyűrűs pufferen alapuló rendszer volt, rögzített méretű cellákkal. Ebben a rendszerben minden a feldolgozási sebességtől függ, az adatok nem másolódnak.

  • Minden bejövő hálózati csomag az elosztási szakaszba lép.
  • Elhelyezzük őket egy tömbben, és megjelöljük az 1. szakaszban elérhetőként.
  • Megérkezett a második tranzakció, ismét elérhető az 1. szakaszhoz.
  • Az első feldolgozási szál látja az elérhető tranzakciókat, feldolgozza azokat, és áthelyezi őket a második feldolgozási szál következő szakaszába.
  • Ezután feldolgozza az első tranzakciót, és megjelöli a megfelelő cellát deleted — most új felhasználásra elérhető.

A teljes sor feldolgozása így történik.

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

Az egyes szakaszok feldolgozása egységeket vagy több tíz mikroszekundumot vesz igénybe. És ha szabványos operációs rendszer szinkronizálási sémákat használunk, akkor több időt veszítünk magára a szinkronizálásra. Ezért kezdtük el használni a spinlockot. Ez azonban egy valós idejű rendszerben nagyon rossz forma, és a RedHat szigorúan nem javasolja ezt, ezért 100 ms-ig spinlockot alkalmazunk, majd átváltunk szemafor módba, hogy kiküszöböljük a holtpont lehetőségét.

Ennek eredményeként körülbelül 8 millió tranzakciós teljesítményt értünk el másodpercenként. És szó szerint két hónappal később cikk az LMAX Disruptor kapcsán láttunk egy azonos funkcionalitású áramkör leírását.

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

Most több végrehajtási szál lehet egy szakaszban. Minden tranzakció egyenként, a beérkezés sorrendjében került feldolgozásra. Ennek eredményeként a csúcsteljesítmény 18 ezerről 50 ezer tranzakcióra nőtt másodpercenként.

Árfolyamkockázat-kezelési rendszer

A tökéletességnek nincs határa, hamarosan újra megkezdtük a modernizációt: az ASTS+ keretein belül megkezdtük a kockázatkezelési és elszámolási műveletek rendszereinek autonóm komponensekbe való áthelyezését. Rugalmas modern architektúrát és új hierarchikus kockázati modellt dolgoztunk ki, és ahol csak lehetett, igyekeztünk az osztályt használni fixed_point helyett double.

De azonnal felmerült egy probléma: hogyan lehet szinkronizálni a sok éve működő üzleti logikát és átvinni az új rendszerbe? Ennek eredményeként az új rendszer prototípusának első változatát fel kellett hagyni. A második verzió, amely jelenleg gyártásban működik, ugyanazon a kódon alapul, amely mind a kereskedelmi, mind a kockázati részben működik. A fejlesztés során a legnehezebb dolog a git egyesítése volt két verzió között. Kollégánk, Jevgenyij Mazurenok hetente végezte el ezt a műtétet, és minden alkalommal nagyon sokáig káromkodott.

Egy új rendszer kiválasztásakor azonnal meg kellett oldanunk az interakció problémáját. Az adatbusz kiválasztásakor a stabil jitter és a minimális késleltetés biztosítására volt szükség. Erre az InfiniBand RDMA hálózat volt a legalkalmasabb: az átlagos feldolgozási idő 4-szer rövidebb, mint a 10 G Ethernet hálózatokban. De ami igazán magával ragadott, az a százalékos különbség – 99 és 99,9.

Természetesen az InfiniBandnak megvannak a maga kihívásai. Először is, egy másik API - ibverbs a socketek helyett. Másodszor, szinte nincsenek széles körben elérhető nyílt forráskódú üzenetküldési megoldások. Megpróbáltunk saját prototípust készíteni, de ez nagyon nehéznek bizonyult, ezért egy kereskedelmi megoldást választottunk - Confinity Low Latency Messaging (korábban IBM MQ LLM).

Ekkor merült fel a kockázati rendszer megfelelő felosztásának feladata. Ha egyszerűen eltávolítja a kockázati motort, és nem hoz létre közbenső csomópontot, akkor a két forrásból származó tranzakciók keveredhetnek.

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

Az úgynevezett Ultra Low Latency megoldások átrendezési móddal rendelkeznek: a két forrásból érkező tranzakciók a beérkezéskor a kívánt sorrendben rendezhetők, ez a megbízással kapcsolatos információcsere külön csatornáján valósul meg. De ezt a módot még nem használjuk: bonyolítja az egész folyamatot, és számos megoldásban egyáltalán nem támogatott. Ezenkívül minden tranzakcióhoz hozzá kell rendelni a megfelelő időbélyegeket, és a mi rendszerünkben ezt a mechanizmust nagyon nehéz helyesen megvalósítani. Ezért a klasszikus sémát használtuk egy üzenetközvetítővel, vagyis egy diszpécserrel, amely elosztja az üzeneteket a kockázati motor között.

A második probléma a kliens hozzáféréssel volt kapcsolatos: ha több kockázati átjáró van, akkor a kliensnek mindegyikhez csatlakoznia kell, és ehhez a kliens rétegen kell változtatni. Ebben a szakaszban el akartunk kerülni ettől, ezért a jelenlegi Risk Gateway kialakítás a teljes adatfolyamot feldolgozza. Ez nagymértékben korlátozza a maximális átviteli sebességet, de nagyban leegyszerűsíti a rendszerintegrációt.

Másolás

Rendszerünknek nem lehet egyetlen hibapontja sem, vagyis minden komponenst meg kell duplikálni, beleértve az üzenetközvetítőt is. Ezt a problémát a CLLM rendszerrel oldottuk meg: tartalmaz egy RCMS klasztert, amelyben két diszpécser tud master-slave módban dolgozni, és ha az egyik meghibásodik, a rendszer automatikusan átvált a másikra.

Biztonsági adatközponttal végzett munka

Az InfiniBand helyi hálózatként való működésre optimalizált, azaz rack-be szerelhető berendezések csatlakoztatására, és InfiniBand hálózat nem fektethető le két földrajzilag elosztott adatközpont között. Ezért bevezettünk egy hidat/diszpécsert, amely normál Ethernet hálózatokon keresztül csatlakozik az üzenettárolóhoz, és minden tranzakciót továbbít egy második IB hálózatra. Amikor át kell költöznünk egy adatközpontból, kiválaszthatjuk, hogy most melyik adatközponttal dolgozzunk.

Eredményei

A fentiek mindegyike nem történt egyszerre, több iterációba telt egy új architektúra kidolgozása. A prototípust egy hónap alatt készítettük el, de több mint két évbe telt, mire működőképes állapotba került. Igyekeztünk a legjobb kompromisszumot elérni a tranzakció-feldolgozási idő növelése és a rendszermegbízhatóság növelése között.

Mivel a rendszert alaposan frissítették, két független forrásból valósítottuk meg az adatok helyreállítását. Ha az üzenettároló valamilyen okból nem működik megfelelően, a tranzakciós naplót egy második forrásból is lekérheti – a kockázati motorból. Ezt az elvet az egész rendszerben betartják.

Többek között meg tudtuk őrizni a kliens API-t úgy, hogy sem a brókerek, sem más nem igényel jelentős átdolgozást az új architektúrához. Néhány interfészen változtatnunk kellett, de a működési modellen nem volt szükség jelentős változtatásokra.

Platformunk jelenlegi verzióját Rebus-nak neveztük el – az architektúra két legszembetűnőbb újításának, a Risk Engine-nek és a BUS-nak a rövidítéseként.

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

Kezdetben csak az elszámolási részt szerettük volna kiosztani, de az eredmény egy hatalmas elosztott rendszer lett. Az ügyfelek mostantól kapcsolatba léphetnek a Trade Gateway-vel, a Clearing Gateway-vel vagy mindkettővel.

Amit végül elértünk:

A moszkvai tőzsde kereskedési és elszámolási rendszerének architektúrájának alakulása. 2. rész

Csökkentette a várakozási időt. Kis mennyiségű tranzakció mellett a rendszer ugyanúgy működik, mint az előző verzió, ugyanakkor sokkal nagyobb terhelést is elbír.

A csúcsteljesítmény 50 ezerről 180 ezer tranzakcióra nőtt másodpercenként. A további növekedést hátráltatja az egyetlen rendelésillesztési folyam.

Két módja van a további fejlesztésnek: az egyeztetés párhuzamosítása és a Gateway működésének megváltoztatása. Mostantól minden átjáró egy replikációs séma szerint működik, amely ilyen terhelés esetén nem működik megfelelően.

Végezetül néhány tanácsot tudok adni azoknak, akik a vállalati rendszereket véglegesítik:

  • Mindig készülj fel a legrosszabbra. A problémák mindig váratlanul merülnek fel.
  • Az építészetet általában lehetetlen gyorsan újrakészíteni. Különösen akkor, ha több mutató esetén is maximális megbízhatóságot kell elérnie. Minél több csomópont, annál több erőforrásra van szükség a támogatáshoz.
  • Minden egyedi és szabadalmaztatott megoldás további erőforrásokat igényel a kutatáshoz, támogatáshoz és karbantartáshoz.
  • Ne halassza el a rendszer megbízhatóságával és a meghibásodások utáni helyreállítással kapcsolatos problémák megoldását; vegye figyelembe ezeket a tervezés kezdeti szakaszában.

Forrás: will.com

Hozzászólás