Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

Dette er en fortsettelse av en lang historie om vår vanskelige vei til å skape et kraftig, høylast system som sikrer driften av børsen. Den første delen er her: habr.com/en/post/444300

Mystisk feil

Etter utallige tester ble det oppdaterte handels- og clearingsystemet satt i drift, og vi møtte en feil som vi kunne skrive en detektiv-mystisk historie om.

Kort tid etter oppstart på hovedserveren ble en av transaksjonene behandlet med en feil. Men alt var bra på backup-serveren. Det viste seg at en enkel matematisk operasjon med å beregne eksponenten på hovedserveren ga et negativt resultat fra det virkelige argumentet! Vi fortsatte forskningen vår, og i SSE2-registeret fant vi en forskjell på én bit, som er ansvarlig for avrunding når man jobber med flyttall.

Vi skrev et enkelt testverktøy for å beregne eksponenten med avrundingsbitsett. Det viste seg at i versjonen av RedHat Linux som vi brukte, var det en feil i arbeidet med den matematiske funksjonen da den skjebnesvangre biten ble satt inn. Vi rapporterte dette til RedHat, etter en stund fikk vi en oppdatering fra dem og rullet den ut. Feilen oppsto ikke lenger, men det var uklart hvor denne biten i det hele tatt kom fra? Funksjonen var ansvarlig for det fesetround fra språket C. Vi analyserte koden vår nøye på leting etter den antatte feilen: vi sjekket alle mulige situasjoner; så på alle funksjoner som brukte avrunding; prøvde å reprodusere en mislykket økt; brukte forskjellige kompilatorer med forskjellige alternativer; Statisk og dynamisk analyse ble brukt.

Årsaken til feilen ble ikke funnet.

Så begynte de å sjekke maskinvaren: de utførte belastningstesting av prosessorene; sjekket RAM; Vi kjørte til og med tester for det svært usannsynlige scenariet med en multi-bit feil i én celle. Til ingen nytte.

Til slutt bestemte vi oss for en teori fra høyenergifysikkverdenen: en høyenergipartikkel fløy inn i datasenteret vårt, gjennomboret kabinettveggen, traff prosessoren og fikk utløserlåsen til å feste seg i akkurat den biten. Denne absurde teorien ble kalt «nøytrinoen». Hvis du er langt fra partikkelfysikk: nøytrinoer samhandler nesten ikke med omverdenen, og er absolutt ikke i stand til å påvirke driften av prosessoren.

Siden det ikke var mulig å finne årsaken til feilen, ble den "fornærmende" serveren fjernet fra drift for sikkerhets skyld.

Etter en tid begynte vi å forbedre det varme sikkerhetskopieringssystemet: vi introduserte såkalte "varme reserver" (varme) - asynkrone kopier. De mottok en strøm av transaksjoner som kunne være plassert i forskjellige datasentre, men warms samhandlet ikke aktivt med andre servere.

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

Hvorfor ble dette gjort? Hvis backupserveren mislykkes, blir varm knyttet til hovedserveren den nye sikkerhetskopien. Det vil si at etter en feil forblir ikke systemet med én hovedserver før slutten av handelsøkten.

Og da den nye versjonen av systemet ble testet og satt i drift, oppsto avrundingsbitfeilen igjen. Dessuten, med økningen i antall varme servere, begynte feilen å dukke opp oftere. Samtidig hadde selgeren ingenting å vise til, siden det ikke var noen konkrete bevis.

Under neste analyse av situasjonen dukket det opp en teori om at problemet kunne være relatert til OS. Vi skrev et enkelt program som kaller en funksjon i en endeløs loop fesetround, husker gjeldende tilstand og sjekker den gjennom søvn, og dette gjøres i mange konkurrerende tråder. Etter å ha valgt parameterne for søvn og antall tråder, begynte vi konsekvent å reprodusere bitfeilen etter omtrent 5 minutter med kjøring av verktøyet. Imidlertid klarte ikke Red Hat-støtte å reprodusere den. Testing av våre andre servere har vist at bare de med visse prosessorer er mottakelige for feilen. Samtidig løste det å bytte til en ny kjerne problemet. Til slutt erstattet vi ganske enkelt OS, og den sanne årsaken til feilen forble uklar.

Og plutselig i fjor ble det publisert en artikkel på Habré “Hvordan jeg fant en feil i Intel Skylake-prosessorer" Situasjonen beskrevet i den var veldig lik vår, men forfatteren tok etterforskningen videre og la frem en teori om at feilen lå i mikrokoden. Og når Linux-kjerner oppdateres, oppdaterer også produsentene mikrokoden.

Videreutvikling av systemet

Selv om vi ble kvitt feilen, tvang denne historien oss til å revurdere systemarkitekturen. Tross alt var vi ikke beskyttet mot gjentakelse av slike feil.

Følgende prinsipper lå til grunn for de neste forbedringene av reservasjonssystemet:

  • Du kan ikke stole på noen. Servere fungerer kanskje ikke som de skal.
  • Flertallsreservasjon.
  • Sikre konsensus. Som et logisk tillegg til flertallsreservasjon.
  • Doble feil er mulig.
  • Vitalitet. Den nye hot standby-ordningen skal ikke være dårligere enn den forrige. Handelen bør fortsette uavbrutt til siste server.
  • Litt økning i ventetid. Enhver nedetid medfører store økonomiske tap.
  • Minimal nettverksinteraksjon for å holde ventetiden så lav som mulig.
  • Velge en ny hovedserver på sekunder.

Ingen av løsningene som var tilgjengelige på markedet passet oss, og Raft-protokollen var fortsatt i sin spede begynnelse, så vi laget vår egen løsning.

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

Nettverk

I tillegg til reservasjonssystemet begynte vi å modernisere nettverksinteraksjonen. I/O-undersystemet besto av mange prosesser, som hadde den verste innvirkningen på jitter og latens. Med hundrevis av prosesser som håndterer TCP-forbindelser, ble vi tvunget til hele tiden å bytte mellom dem, og på mikrosekundskala er dette en ganske tidkrevende operasjon. Men det verste er at når en prosess mottok en pakke for behandling, sendte den den til en SystemV-kø og ventet deretter på en hendelse fra en annen SystemV-kø. Men når det er et stort antall noder, representerer ankomsten av en ny TCP-pakke i én prosess og mottak av data i køen i en annen to konkurrerende hendelser for operativsystemet. I dette tilfellet, hvis det ikke er noen fysiske prosessorer tilgjengelig for begge oppgavene, vil den ene bli behandlet, og den andre vil bli plassert i en ventekø. Det er umulig å forutse konsekvensene.

I slike situasjoner kan dynamisk prosessprioritetskontroll benyttes, men dette vil kreve bruk av ressurskrevende systemanrop. Som et resultat byttet vi til én tråd ved hjelp av klassisk epoll, dette økte hastigheten betraktelig og reduserte transaksjonsbehandlingstiden. Vi ble også kvitt separate nettverkskommunikasjonsprosesser og kommunikasjon gjennom SystemV, reduserte antallet systemanrop betydelig og begynte å kontrollere prioriteringene av operasjoner. På I/O-delsystemet alene var det mulig å spare ca. 8-17 mikrosekunder, avhengig av scenariet. Denne entrådede ordningen har blitt brukt uendret siden den gang; én epolltråd med margin er nok til å betjene alle tilkoblinger.

Transaksjon behandles

Den økende belastningen på systemet vårt krevde oppgradering av nesten alle komponentene. Men dessverre har stagnasjon i veksten av prosessorens klokkehastigheter de siste årene ikke lenger gjort det mulig å skalere prosesser direkte. Derfor bestemte vi oss for å dele Engine-prosessen inn i tre nivåer, hvor det travleste av dem er risikokontrollsystemet, som evaluerer tilgjengeligheten av midler på kontoer og oppretter transaksjonene selv. Men penger kan være i forskjellige valutaer, og det var nødvendig å finne ut på hvilket grunnlag behandlingen av forespørsler skulle deles.

Den logiske løsningen er å dele den etter valuta: én server handler i dollar, en annen i pund og en tredje i euro. Men hvis det med en slik ordning sendes to transaksjoner for å kjøpe forskjellige valutaer, vil problemet med desynkronisering av lommeboken oppstå. Men synkronisering er vanskelig og dyrt. Derfor vil det være riktig å skjære separat med lommebøker og separat med instrumenter. De fleste vestlige børser har forresten ikke som oppgave å sjekke risiko like akutt som oss, så som oftest gjøres dette offline. Vi trengte å implementere online verifisering.

La oss forklare med et eksempel. En trader ønsker å kjøpe $30, og forespørselen går til transaksjonsvalidering: vi sjekker om denne traderen har lov til denne handelsmodusen og om han har de nødvendige rettighetene. Hvis alt er i orden, går forespørselen til risikoverifiseringssystemet, dvs. for å kontrollere tilstrekkelige midler til å fullføre en transaksjon. Det er en merknad om at det nødvendige beløpet for øyeblikket er blokkert. Forespørselen videresendes deretter til handelssystemet, som godkjenner eller avviser transaksjonen. La oss si at transaksjonen er godkjent - da markerer risikoverifiseringssystemet at pengene er opphevet, og rublene blir til dollar.

Generelt inneholder risikokontrollsystemet komplekse algoritmer og utfører en stor mengde svært ressurskrevende beregninger, og sjekker ikke bare "kontosaldoen", slik det kan virke ved første øyekast.

Da vi begynte å dele inn Engine-prosessen i nivåer, støtt på et problem: koden som var tilgjengelig på det tidspunktet brukte aktivt det samme datautvalget på validerings- og verifiseringsstadiene, noe som krevde omskrivning av hele kodebasen. Som et resultat lånte vi en teknikk for å behandle instruksjoner fra moderne prosessorer: hver av dem er delt inn i små stadier og flere handlinger utføres parallelt i en syklus.

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

Etter en liten tilpasning av koden laget vi en pipeline for parallell transaksjonsbehandling, der transaksjonen ble delt inn i 4 stadier av pipelinen: nettverksinteraksjon, validering, utførelse og publisering av resultatet

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

La oss se på et eksempel. Vi har to prosesseringssystemer, seriell og parallell. Den første transaksjonen kommer og sendes til validering i begge systemene. Den andre transaksjonen kommer umiddelbart: i et parallelt system blir den umiddelbart tatt til arbeid, og i et sekvensielt system settes den i en kø som venter på at den første transaksjonen skal gå gjennom det nåværende behandlingsstadiet. Det vil si at hovedfordelen med pipeline-behandling er at vi behandler transaksjonskøen raskere.

Slik kom vi opp med ASTS+-systemet.

Riktignok er ikke alt så glatt med transportbånd heller. La oss si at vi har en transaksjon som påvirker datamatriser i en nabotransaksjon; dette er en typisk situasjon for en utveksling. En slik transaksjon kan ikke utføres i en pipeline fordi den kan påvirke andre. Denne situasjonen kalles datafare, og slike transaksjoner behandles ganske enkelt separat: når de "raske" transaksjonene i køen går tom, stopper rørledningen, systemet behandler den "langsomme" transaksjonen og starter deretter rørledningen igjen. Heldigvis er andelen slike transaksjoner i den samlede flyten svært liten, så rørledningen stopper så sjelden at den ikke påvirker den totale ytelsen.

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

Så begynte vi å løse problemet med å synkronisere tre utførelsestråder. Resultatet ble et system basert på en ringbuffer med celler med fast størrelse. I dette systemet er alt underlagt behandlingshastighet, data kopieres ikke.

  • Alle innkommende nettverkspakker går inn i tildelingsstadiet.
  • Vi plasserer dem i en matrise og merker dem som tilgjengelige for trinn #1.
  • Den andre transaksjonen har kommet, den er igjen tilgjengelig for trinn nr. 1.
  • Den første behandlingstråden ser de tilgjengelige transaksjonene, behandler dem og flytter dem til neste trinn i den andre behandlingstråden.
  • Den behandler deretter den første transaksjonen og flagger den tilsvarende cellen deleted — den er nå tilgjengelig for ny bruk.

Hele køen behandles på denne måten.

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

Behandling av hvert trinn tar enheter eller titalls mikrosekunder. Og hvis vi bruker standard OS-synkroniseringsordninger, vil vi tape mer tid på selve synkroniseringen. Derfor begynte vi å bruke spinlock. Dette er imidlertid veldig dårlig form i et sanntidssystem, og RedHat anbefaler strengt tatt ikke å gjøre dette, så vi bruker en spinlock i 100 ms, og bytter deretter til semaformodus for å eliminere muligheten for dødlås.

Som et resultat oppnådde vi en ytelse på rundt 8 millioner transaksjoner per sekund. Og bokstavelig talt to måneder senere artikkel om LMAX Disruptor så vi en beskrivelse av en krets med samme funksjonalitet.

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

Nå kan det være flere tråder av henrettelse på ett stadium. Alle transaksjoner ble behandlet én etter én, i den rekkefølgen de ble mottatt. Som et resultat økte toppytelsen fra 18 tusen til 50 tusen transaksjoner per sekund.

Utvekslingsrisikostyringssystem

Det er ingen grense for perfeksjon, og snart begynte vi moderniseringen igjen: innenfor rammen av ASTS+ begynte vi å flytte systemer for risikostyring og oppgjørsdrift til autonome komponenter. Vi utviklet en fleksibel moderne arkitektur og en ny hierarkisk risikomodell, og prøvde å bruke klassen der det var mulig fixed_point i stedet for double.

Men et problem oppsto umiddelbart: hvordan synkronisere all forretningslogikken som har fungert i mange år og overføre den til det nye systemet? Som et resultat måtte den første versjonen av prototypen til det nye systemet forlates. Den andre versjonen, som for tiden jobber i produksjon, er basert på samme kode, som fungerer både i trading- og risikodelen. Under utviklingen var den vanskeligste tingen å gjøre git merge mellom to versjoner. Vår kollega Evgeniy Mazurenok utførte denne operasjonen hver uke, og hver gang bannet han i veldig lang tid.

Da vi valgte et nytt system, måtte vi umiddelbart løse problemet med samhandling. Ved valg av databuss var det nødvendig å sikre stabil jitter og minimal latens. InfiniBand RDMA-nettverket var best egnet for dette: den gjennomsnittlige behandlingstiden er 4 ganger mindre enn i 10 G Ethernet-nettverk. Men det som virkelig fanget oss var forskjellen i persentiler – 99 og 99,9.

Selvfølgelig har InfiniBand sine utfordringer. For det første et annet API - ibverbs i stedet for sockets. For det andre er det nesten ingen allment tilgjengelige meldingsløsninger med åpen kildekode. Vi prøvde å lage vår egen prototype, men det viste seg å være veldig vanskelig, så vi valgte en kommersiell løsning – Confinity Low Latency Messaging (tidligere IBM MQ LLM).

Da oppstod oppgaven med å dele risikosystemet riktig. Hvis du ganske enkelt fjerner Risk Engine og ikke oppretter en mellomnode, kan transaksjoner fra to kilder blandes.

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

De såkalte Ultra Low Latency-løsningene har en ombestillingsmodus: transaksjoner fra to kilder kan ordnes i ønsket rekkefølge ved mottak; dette implementeres ved hjelp av en egen kanal for utveksling av informasjon om ordren. Men vi bruker ikke denne modusen ennå: den kompliserer hele prosessen, og i en rekke løsninger støttes den ikke i det hele tatt. I tillegg vil hver transaksjon måtte tildeles tilsvarende tidsstempler, og i vår ordning er denne mekanismen svært vanskelig å implementere riktig. Derfor brukte vi den klassiske ordningen med en meldingsmegler, det vil si med en dispatcher som distribuerer meldinger mellom Risk Engine.

Det andre problemet var relatert til klienttilgang: hvis det er flere Risk Gateways, må klienten koble seg til hver av dem, og dette vil kreve endringer i klientlaget. Vi ønsket å komme vekk fra dette på dette stadiet, så dagens Risk Gateway-design behandler hele datastrømmen. Dette begrenser den maksimale gjennomstrømningen i stor grad, men forenkler systemintegrasjonen.

Duplisering

Systemet vårt skal ikke ha et enkelt feilpunkt, det vil si at alle komponenter må dupliseres, inkludert meldingsmegleren. Vi løste dette problemet ved å bruke CLLM-systemet: det inneholder en RCMS-klynge der to dispatchere kan jobbe i master-slave-modus, og når den ene svikter, bytter systemet automatisk til den andre.

Jobber med et backup datasenter

InfiniBand er optimert for drift som et lokalt nettverk, det vil si for tilkobling av rackmontert utstyr, og et InfiniBand-nettverk kan ikke legges mellom to geografisk distribuerte datasentre. Derfor implementerte vi en bro/dispatcher, som kobles til meldingslagringen via vanlige Ethernet-nettverk og videresender alle transaksjoner til et andre IB-nettverk. Når vi skal migrere fra et datasenter, kan vi velge hvilket datasenter vi skal jobbe med nå.

Resultater av

Alt det ovennevnte ble ikke gjort på en gang; det tok flere iterasjoner å utvikle en ny arkitektur. Vi laget prototypen på en måned, men det tok mer enn to år å få den til å fungere. Vi prøvde å oppnå det beste kompromisset mellom å øke transaksjonsbehandlingstiden og øke systemets pålitelighet.

Siden systemet ble kraftig oppdatert, implementerte vi datagjenoppretting fra to uavhengige kilder. Hvis meldingslageret av en eller annen grunn ikke fungerer som det skal, kan du ta transaksjonsloggen fra en annen kilde - fra Risk Engine. Dette prinsippet følges i hele systemet.

Blant annet klarte vi å ta vare på klient-API-en slik at verken meglere eller noen andre skulle kreve betydelig omarbeiding av den nye arkitekturen. Vi måtte endre noen grensesnitt, men det var ikke nødvendig å gjøre vesentlige endringer i driftsmodellen.

Vi kalte den nåværende versjonen av vår plattform Rebus – som en forkortelse for de to mest merkbare innovasjonene innen arkitekturen, Risk Engine og BUS.

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

I utgangspunktet ønsket vi kun å tildele clearing-delen, men resultatet ble et enormt distribuert system. Kunder kan nå samhandle med enten Trade Gateway, Clearing Gateway eller begge deler.

Hva vi til slutt oppnådde:

Evolusjon av arkitekturen til handels- og clearingsystemet til Moskva-børsen. Del 2

Reduserte latensnivået. Med et lite volum av transaksjoner fungerer systemet på samme måte som forrige versjon, men tåler samtidig en mye høyere belastning.

Toppytelsen økte fra 50 tusen til 180 tusen transaksjoner per sekund. En ytterligere økning hindres av den eneste strømmen av ordrematching.

Det er to måter for ytterligere forbedring: parallellisering av matching og endring av måten det fungerer på med Gateway. Nå opererer alle gatewayer i henhold til et replikeringsskjema, som under en slik belastning slutter å fungere normalt.

Til slutt kan jeg gi noen råd til de som ferdigstiller bedriftssystemer:

  • Vær forberedt på det verste til enhver tid. Problemer oppstår alltid uventet.
  • Det er vanligvis umulig å raskt gjenskape arkitektur. Spesielt hvis du trenger å oppnå maksimal pålitelighet på tvers av flere indikatorer. Jo flere noder, jo flere ressurser trengs for støtte.
  • Alle tilpassede og proprietære løsninger vil kreve ekstra ressurser for forskning, støtte og vedlikehold.
  • Ikke utsett å løse problemer med systempålitelighet og gjenoppretting etter feil; ta dem i betraktning i det innledende designstadiet.

Kilde: www.habr.com

Legg til en kommentar