
Fejltolerance og høj tilgængelighed er store emner, så vi vil dedikere separate artikler til RabbitMQ og Kafka. Denne artikel handler om RabbitMQ, og den næste handler om Kafka sammenlignet med RabbitMQ. Dette er en lang artikel, så sæt dig godt til rette.
Lad os se på fejltolerance, konsistens og høj tilgængelighed (HA) strategier og de afvejninger, der er involveret i hver strategi. RabbitMQ kan køre på en klynge af noder - og klassificeres derefter som et distribueret system. Når vi taler om distribuerede systemer, taler vi ofte om konsistens og tilgængelighed.
Disse begreber beskriver, hvordan systemet opfører sig, når der opstår en fejl. Netværksforbindelsesfejl, serverfejl, harddiskfejl, server midlertidigt utilgængelig på grund af affaldsindsamling, pakketab eller langsom netværksforbindelse. Alt dette kan føre til datatab eller konflikter. Det viser sig, at det er praktisk talt umuligt at skabe et system, der både er fuldstændig konsistent (intet tab af data, ingen dataafvigelser) og tilgængeligt (vil acceptere læse- og skriveoperationer) for alle fejltilstande.
Vi vil se, at konsistens og tilgængelighed er i forskellige ender af spektret, og du skal vælge, hvilken du vil optimere til. Den gode nyhed er, at med RabbitMQ er dette valg muligt. Du har disse nørdede små håndtag til at flytte balancen mod mere sammenhæng eller mere tilgængelighed.
Vi vil være særligt opmærksomme på, hvilke konfigurationer der fører til datatab på grund af forpligtede registreringer. Der er en ansvarskæde mellem forlag, mæglere og forbrugere. Når en besked er sendt til mægleren, er det hans opgave ikke at miste beskeden. Når en mægler bekræfter modtagelsen af en besked til en udgiver, forventer vi ikke, at den går tabt. Men vi vil se, at dette faktisk kan ske afhængigt af din mægler- og udgiverkonfiguration.
Single Node Stabilitet Primitiver
Vedvarende køer/routing
RabbitMQ har to køtyper: holdbar og ikke-holdbar. Alle køer er gemt i Mnesia-databasen. Holdbare køer annonceres igen, når en node starter, og overlever således en genstart, systemnedbrud eller serverfejl (så længe dataene består). Det betyder, at så længe du erklærer udvekslingen og køen som modstandsdygtig, vil kø/routing-infrastrukturen komme online igen.
Ustabile køer og routing fjernes, når noden genstartes.
Vedvarende beskeder
Bare fordi en kø er langvarig, betyder det ikke, at alle dens meddelelser vil overleve en genstart af node. Kun meddelelser angivet af udgiveren som vedvarende (vedvarende). Vedvarende beskeder skaber yderligere belastning på mægleren, men hvis beskedtab er uacceptabelt, er der ingen anden måde.

Ris. 1. Stabilitetsmatrix
Klynger med køspejling
For at overleve tabet af en mægler har vi brug for redundans. Vi kan kombinere flere RabbitMQ-noder til en klynge og derefter tilføje yderligere redundans ved at replikere køer på tværs af flere knudepunkter. På denne måde, hvis en node går ned, mister vi ikke data, og vi forbliver tilgængelige.
Køspejling:
- én hovedkø (master), der modtager alle skrive- og læsekommandoer
- et eller flere spejle, der modtager alle beskeder og metadata fra hovedkøen. Disse spejle eksisterer ikke for skalering, men udelukkende for redundans.

Ris. 2. Køspejling
Spejling er indstillet af den tilsvarende politik. I den kan du vælge replikeringsfaktoren og endda de noder, som køen skal placeres på. Eksempler:
ha-mode: allha-mode: exactly, ha-params: 2(en mester og et spejl)ha-mode: nodes, ha-params: rabbit@node1, rabbit@node2
Bekræftelse til forlaget
Udgiverbekræftelser er påkrævet for at opnå ensartet optagelse. Uden dem er der risiko for at miste beskeder. En bekræftelse sendes til udgiveren, efter at meddelelsen er skrevet til disken. RabbitMQ skriver beskeder til disken ikke ved modtagelse, men på en periodisk basis i området af nogle få hundrede millisekunder. Når en kø spejles, sendes der først en bekræftelse, efter at alle spejle også har skrevet deres kopi af meddelelsen til disken. Det betyder, at brug af bekræftelser tilføjer latens, men hvis datasikkerhed er vigtig, så er de nødvendige.
Fejltolerant kø
Når en mægler stopper eller går ned, går alle mastere på den node ned med den. Klyngen vælger derefter det ældste spejl af hver master og promoverer det som den nye master.

Ris. 3. Flere spejlede køer og deres politikker
Broker 3 er nede. Bemærk venligst, at Queue C-spejlet på Broker 2 er forfremmet til master. Bemærk også, at der er oprettet et nyt spejl for Kø C på Broker 1. RabbitMQ forsøger altid at opretholde den replikeringsfaktor, der er angivet i dine politikker.

Ris. 4. Broker 3 går ned, hvilket får kø C til at mislykkes.
Den næste Broker 1 falder! Vi har kun én mægler tilbage. Kø B-spejlet er forfremmet til at mestre.

Fig. 5
Vi har bragt Broker 1 tilbage. Uanset hvor godt dataene overlevede tabet og gendannelsen af mægleren, kasseres alle spejlede kømeddelelser ved genstart. Dette er vigtigt at bemærke, fordi der vil være konsekvenser. Vi vil se på disse implikationer snart. Så Broker 1 er nu medlem af klyngen igen, og klyngen forsøger at overholde politikkerne og skaber derfor spejle på Broker 1.
I dette tilfælde var tabet af Broker 1 totalt, ligesom dataene var, så den uspejlede kø B gik fuldstændig tabt.

Ris. 6. Broker 1 er tilbage i aktion
Broker 3 er tilbage online, så køerne A og B får deres spejle oprettet på den tilbage for at opfylde deres HA-politikker. Men nu er alle hovedkøerne på én knude! Dette er ikke ideelt, en ensartet fordeling mellem noder er bedre. Desværre er der ingen særlige muligheder for rebalancering af mastere her. Vi vender tilbage til dette problem senere, da vi først skal overveje køsynkronisering.

Ris. 7. Broker 3 vender tilbage til tjeneste. Alle hovedkøer på én knude!
Så du skulle nu have en idé om, hvordan spejle giver redundans og fejltolerance. Dette sikrer tilgængelighed i tilfælde af en enkelt knudefejl og beskytter mod tab af data. Men vi er ikke færdige endnu, for i virkeligheden er alt meget mere kompliceret.
synkronisering
Når du opretter et nyt spejl, vil alle nye beskeder altid blive replikeret til det spejl og eventuelle andre. Hvad angår de eksisterende data i masterkøen, kan vi replikere dem til et nyt spejl, som bliver en komplet kopi af masteren. Vi kan også vælge ikke at replikere eksisterende beskeder og lade hovedkøen og det nye spejl konvergere i tide, efterhånden som nye beskeder ankommer til halen, og eksisterende beskeder forlader hovedkøen.
Denne synkronisering udføres automatisk eller manuelt og styres af en køpolitik. Lad os se på et eksempel.
Vi har to spejlede køer. Kø A synkroniseres automatisk, mens Kø B synkroniseres manuelt. Der er ti beskeder i begge køer.

Ris. 8. To køer med forskellige synkroniseringstilstande
Nu mister vi Broker 3.

Ris. 9. Mægler 3 faldt
Broker 3 er tilbage i aktion. Klyngen opretter et spejl for hver kø på den nye node og synkroniserer automatisk den nye kø A med masteren. Men spejlet i den nye kø B forbliver tomt. Vi har således fuld redundans af Kø A og kun ét spejl for eksisterende Kø B-meddelelser.

Ris. 10. Det nye spejl af Kø A modtager alle eksisterende meddelelser, men det nye spejl i Kø B gør det ikke.
Der kommer ti beskeder mere i begge køer. Så går Broker 2 ned, og Kø A ruller tilbage til det ældste spejl, som er på Broker 1. Der er intet datatab, når nedbruddet sker. Kø B har tyve beskeder på masteren og kun ti på spejlet, fordi denne kø aldrig replikerede de originale ti beskeder.

Ris. 11. Kø A ruller tilbage til Broker 1 uden at miste beskeder
Der kommer ti beskeder mere i begge køer. Nu går Broker 1 ned. Kø A skifter til spejlet uden problemer og uden at miste beskeder. Kø B har dog problemer. På dette stadium kan vi optimere enten tilgængelighed eller konsistens.
Hvis vi vil optimere tilgængeligheden, så politikken ha-fremme-ved-fejl skal monteres i altid. Dette er standardværdien, så du kan simpelthen ikke angive politikken overhovedet. I dette tilfælde tillader vi i det væsentlige fejl i usynkroniserede spejle. Dette vil resultere i, at beskeder går tabt, men køen forbliver tilgængelig til læsning og skrivning.

Ris. 12. Kø A ruller tilbage til Broker 3 uden at miste beskeder. Kø B falder tilbage til Broker 3 med tab af ti beskeder
Vi kan også installere ha-promote-on-failure i betydning when-synced. I dette tilfælde, i stedet for at rulle tilbage til spejlet, vil køen vente, indtil Broker 1 med sine data vender tilbage til driftstilstand. Efter hans tilbagevenden er hovedkøen igen på Broker 1 uden tab af data. Tilgængelighed er ofret for datasikkerhed. Men dette er en risikabel tilstand, der endda kan føre til fuldstændigt datatab, som vi vil se på om kort tid.

Ris. 13. Kø B forbliver utilgængelig efter at have mistet Broker 1
Du spørger måske: "Måske er det bedre aldrig at bruge automatisk synkronisering?" Svaret er, at synkronisering er en blokerende operation. Under synkronisering kan hovedkøen ikke udføre nogen læse- eller skrivehandlinger!
Lad os se på et eksempel. Vi har meget lange køer nu. Hvordan kan de vokse til sådan en størrelse? Af flere grunde:
- Køer bruges ikke aktivt
- Det er højhastighedskøer, og lige nu arbejder forbrugerne langsomt.
- Det er højhastighedskøer, der har været en fejl, og forbrugerne er ved at indhente det

Ris. 14. To store køer med forskellige synkroniseringstilstande
Nu falder Broker 3.

Ris. 15. Broker 3 går ned og efterlader én master og et spejl i hver kø
Broker 3 er tilbage online, og nye spejle bliver skabt. Hovedkø A begynder at replikere eksisterende meddelelser til et nyt spejl, og i dette tidsrum er køen ikke tilgængelig. Det tager to timer at replikere dataene, hvilket resulterer i to timers nedetid for denne kø!
Kø B forbliver dog tilgængelig i hele perioden. Hun ofrede noget redundans for tilgængelighed.

Ris. 16. Køen forbliver utilgængelig under synkronisering
Efter to timer bliver kø A også tilgængelig og kan begynde at acceptere læse- og skrivehandlinger igen.
Opdateringer
Denne blokeringsadfærd under synkronisering gør det vanskeligt at opdatere klynger med meget store køer. På et tidspunkt skal masternoden genstartes, hvilket betyder enten at skifte til et spejl eller at deaktivere køen, mens serveren opdateres. Hvis vi vælger at migrere, mister vi beskeder, hvis spejlene ikke er synkroniserede. Som standard, når en mægler er afbrudt, skiftes der ikke til et usynkroniseret spejl. Det betyder, at når først mægleren kommer tilbage, mister vi ingen beskeder, den eneste skade er kun nedetiden i køen. Adfærdsreglerne ved deaktivering af en mægler er fastsat af politikken ha-promote-on-shutdown. Du kan indstille en af to værdier:
always= skift til usynkroniserede spejle er aktiveretwhen-synced= skift kun til et synkroniseret spejl, ellers bliver køen utilgængelig til læsning og skrivning. Køen vil være i orden igen, så snart mægleren vender tilbage.
Uanset hvad, med lange køer skal du vælge mellem tab af data og utilgængelighed.
Når tilgængelighed forbedrer datasikkerheden
Der er endnu en komplikation at overveje, før du træffer en beslutning. Selvom automatisk synkronisering er bedre til redundans, hvordan påvirker det datasikkerheden? Sikker på, takket være bedre redundans er RabbitMQ mindre tilbøjelige til at miste eksisterende beskeder, men hvad med nye beskeder fra udgivere?
Her skal du tage højde for følgende:
- Kan udgiveren blot returnere en fejl, og upstream-tjenesten eller brugeren kan prøve igen senere?
- Kan udgiveren gemme meddelelsen lokalt eller i en database for at prøve igen senere?
Hvis en udgiver kun kan kassere en besked, så forbedrer en forbedring af tilgængeligheden faktisk også datasikkerheden.
Så det er nødvendigt at søge en balance, og løsningen afhænger af den specifikke situation.
Problemer med ha-promote-on-failure=when-synced
Idea ha-fremme-ved-fejl= når den er synkroniseret er, at vi forhindrer skift til et usynkroniseret spejl og dermed undgår datatab. Køen forbliver utilgængelig til læsning eller skrivning. I stedet forsøger vi at bringe den nedbrudte mægler tilbage med intakte data, så den kan genoptage at fungere som en master uden at miste data.
Men (og det er et stort men) hvis mægleren har mistet sine data, så har vi et stort problem: køen er tabt! Alle data er tabt! Selvom du har spejle, der for det meste indhenter hovedkøen, kasseres disse spejle også.
For at tilføje en knude med samme navn igen, beder vi klyngen om at glemme den tabte knude (ved hjælp af kommandoen rabbitmqctl forget_cluster_node) og start en ny mægler med det samme værtsnavn. Så længe klyngen husker den tabte node, husker den den gamle kø og de usynkroniserede spejle. Når en klynge får besked på at glemme en tabt node, bliver denne kø også glemt. Nu skal vi annoncere det igen. Vi mistede alle data, selvom vi havde spejle med delvise data. Det ville være bedre at skifte til et ikke-synkroniseret spejl!
Derfor manuel synkronisering (og manglende synkronisering) i kombination med ha-promote-on-failure=when-synced, efter min mening, er ret risikabelt. Dokumenterne siger, at denne mulighed eksisterer for datasikkerhed, men det er et tveægget sværd.
Genbalancering af mestre
Som lovet vender vi tilbage til problemet med alle mestre, der samles på en eller flere noder. Dette kan endda ske som et resultat af en rullende klyngeopgradering. I en tre-node-klynge vil alle hovedkøer være koncentreret om en eller to noder.
Rebalancering af mastere kan være problematisk af to årsager:
- Der er ingen gode værktøjer til at udføre rebalancering
- Køsynkronisering
Der er en tredjepart til rebalancering , som ikke er officielt understøttet. Angående tredjeparts plugins i RabbitMQ manualen : "Pluginnet giver nogle ekstra konfigurations- og rapporteringsværktøjer, men er ikke understøttet eller testet af RabbitMQ-teamet. Brug på egen risiko."
Der er et andet trick til at flytte hovedkøen gennem HA-politikker. Manualen nævner for dette. Det fungerer sådan her:
- Fjerner alle spejle ved hjælp af en midlertidig politik med højere prioritet end den eksisterende HA-politik.
- Ændrer den midlertidige HA-politik til at bruge "nodes"-tilstanden, og specificerer den node, som masterkøen skal migreres til.
- Synkroniserer køen til tvungen migrering.
- Når migreringen er fuldført, fjerner den midlertidige politik. Den oprindelige HA-politik sættes i kraft, og det nødvendige antal spejle oprettes.
Ulempen er, at denne tilgang muligvis ikke virker, hvis du har store køer eller strenge krav til redundans.
Lad os nu se på, hvordan RabbitMQ-klynger fungerer med netværkspartitioner.
Overtrædelse af tilslutningsmuligheder
Noder i et distribueret system er forbundet med netværkslinks, og netværkslinks kan og vil blive afbrudt. Hyppigheden af udfald afhænger af den lokale infrastruktur eller pålideligheden af den valgte sky. Under alle omstændigheder bør distribuerede systemer kunne håndtere dem. Endnu en gang står vi over for et valg mellem tilgængelighed og konsistens, og endnu en gang er den gode nyhed, at RabbitMQ leverer begge dele (bare ikke på samme tid).
Med RabbitMQ har vi to hovedmuligheder:
- Tillad split-brain. Dette sikrer tilgængelighed, men kan resultere i tab af data.
- Deaktiver logisk adskillelse. Kan resultere i kortsigtet tab af tilgængelighed afhængigt af, hvordan klienter opretter forbindelse til klyngen. Det kan også føre til fuldstændig utilgængelighed i en to-node klynge.
Men hvad er en logisk opdeling? Dette er, når en klynge opdeles i to på grund af tab af netværksforbindelser. Hver side af spejlet forfremmes til en master, så hver kø ender med flere mastere.

Ris. 17. Hovedkøen og to spejle, hver på en separat knude. Så opstår der en netværksfejl, og det ene spejl bliver adskilt. Den adskilte node ser, at de to andre er faldet af og fører sine spejle frem til masteren. Vi har nu to hovedkøer, både skrivbare og læsbare.
Hvis udgivere sender data til begge mastere, ender vi med to divergerende kopier af køen.
RabbitMQs forskellige tilstande giver enten tilgængelighed eller konsistens.
Ignorer tilstand (standard)
Denne tilstand giver tilgængelighed. Efter tabet af sammenhæng sker der en logisk adskillelse. Når forbindelsen er gendannet, skal administratoren beslutte, hvilken partition der skal prioriteres. Den tabende side vil blive genstartet, og alle akkumulerede data på den side går tabt.

Ris. 18. Tre forlag er tilknyttet tre mæglere. Internt dirigerer klyngen alle anmodninger til hovedkøen på Broker 2.
Nu mister vi Broker 3. Han ser, at andre mæglere er droppet ud og promoverer sit spejl til mesteren. Sådan opstår den logiske opdeling.

Ris. 19. Logisk opdeling (split-brain). Posterne går i to hovedkøer, og de to kopier divergerer.
Sammenhængen genoprettes, men logisk adskillelse forbliver. Administratoren skal manuelt vælge den tabende side. I nedenstående tilfælde genstarter administratoren Broker 3. Alle meddelelser, som den ikke formåede at sende, går tabt.

Ris. 20. Administrator deaktiverer Broker 3.

Ris. 21. Administratoren starter Broker 3, og den slutter sig til klyngen og mister alle beskeder, der blev efterladt der.
Under tabet af forbindelse og efter dens genoprettelse var klyngen og denne kø tilgængelige for læsning og skrivning.
Autoheal-tilstand
Fungerer på samme måde som Ignorer-tilstand, bortset fra at klyngen selv automatisk vælger den tabende side efter en opdeling og genforbindelse. Den tabende side vender tilbage til klyngen tom, og køen mister alle meddelelser, der kun blev sendt til den side.
Sæt minoritetstilstand på pause
Hvis vi ikke ønsker at tillade logisk partitionering, så er vores eneste mulighed at nægte at læse og skrive på den mindre side efter klyngepartitionen. Når mægleren ser, at den er på den mindre side, sætter den driften på pause, det vil sige, at den lukker alle eksisterende forbindelser og nægter nye. En gang i sekundet tjekker den for genoprettelse af forbindelse. Når forbindelsen er genoprettet, genoptager den driften og slutter sig til klyngen.

Ris. 22. Tre forlag er tilknyttet tre mæglere. Internt dirigerer klyngen alle anmodninger til hovedkøen på Broker 2.
Derefter splittes Broker 1 og 2 fra Broker 3. I stedet for at promovere sit spejl til master, stopper Broker 3 og bliver utilgængelig.

Ris. 23. Broker 3 sætter driften på pause, afbryder forbindelsen til alle klienter og afviser forbindelsesanmodninger.
Når forbindelsen er genoprettet, vender den tilbage til klyngen.
Lad os se på et andet eksempel, hvor hovedkøen er på Broker 3.

Ris. 24. Hovedkø på Broker 3.
Så sker det samme tab af sammenhæng. Broker 3 holder pause, da den er på den mindre side. På den anden side ser noderne, at Broker 3 er faldet af, så det ældre spejl fra Brokers 1 og 2 er forfremmet til master.

Ris. 25. Skift til Broker 2, når Broker 3 ikke er tilgængelig.
Når forbindelsen er genoprettet, vil Broker 3 slutte sig til klyngen.

Ris. 26. Klyngen er vendt tilbage til normal drift.
Det vigtige at forstå her er, at vi får konsekvens, men vi kan også få tilgængelighed, hvis Vi vil med succes overføre kunder til størstedelen af sektionen. I de fleste situationer ville jeg personligt vælge Pause Minority-tilstand, men det afhænger virkelig af det specifikke tilfælde.
For at sikre tilgængelighed er det vigtigt at sikre, at klienter kan oprette forbindelse til noden. Lad os overveje vores muligheder.
Sikring af kundeforbindelse
Vi har flere muligheder for, hvordan man dirigerer klienter til hoveddelen af klyngen eller til arbejdende knudepunkter (efter en fejl i en knude) efter tab af forbindelse. Lad os først huske, at en bestemt kø er hostet på en bestemt node, men routing og politikker replikeres på tværs af alle noder. Klienter kan oprette forbindelse til enhver node, og intern routing vil dirigere dem, hvor de skal hen. Men når en node er sat på pause, afviser den forbindelser, så klienter skal oprette forbindelse til en anden node. Hvis knudepunktet er faldet af, er der ikke meget, han overhovedet kan gøre.
Vores muligheder:
- Der er adgang til klyngen via en belastningsbalancer, som blot går gennem noderne, og klienter prøver at oprette forbindelse igen, indtil det lykkes. Hvis en node er nede eller sat på pause, vil forsøg på at oprette forbindelse til den node mislykkes, men efterfølgende forsøg vil gå til andre servere (på en round robin-måde). Dette er velegnet til et kortvarigt tab af forbindelse eller en nedbrudt server, der hurtigt vil blive bragt op igen.
- Få adgang til klyngen gennem belastningsbalanceren, og fjern pausede/mislykkede noder fra listen, så snart de er opdaget. Hvis vi gør dette hurtigt, og hvis klienter er i stand til at prøve at oprette forbindelse igen, vil vi have konstant tilgængelighed.
- Giv hver klient en liste over alle noder, og klienten vælger tilfældigt en af dem, når den opretter forbindelse. Hvis den får en fejl, når den forsøger at oprette forbindelse, går den videre til den næste node på listen, indtil den opretter forbindelse.
- Fjern trafik fra et knudepunkt, der er nedbrudt/pause ved hjælp af DNS. Dette gøres ved hjælp af lille TTL.
Fund
RabbitMQ clustering har sine egne fordele og ulemper. De mest alvorlige ulemper er, at:
- Når de tilslutter sig en klynge, kasserer noder deres data;
- Blokering af synkronisering resulterer i, at køen ikke er tilgængelig.
Alle vanskelige beslutninger udspringer af disse to træk ved arkitektur. Hvis RabbitMQ kunne bestå data på tværs af cluster rejoins, ville synkronisering være hurtigere. Hvis det var i stand til ikke-blokerende synkronisering, ville det understøtte store køer bedre. Løsning af disse to problemer ville i høj grad forbedre RabbitMQs ydeevne som en fejltolerant og yderst tilgængelig beskedteknologi. Jeg vil tøve med at anbefale RabbitMQ med clustering i følgende situationer:
- Upålideligt netværk.
- Upålidelig opbevaring.
- Meget lange køer.
Med hensyn til indstillinger for høj tilgængelighed skal du overveje følgende:
ha-promote-on-failure=alwaysha-sync-mode=manualcluster_partition_handling=ignore(ellerautoheal)- vedvarende beskeder
- sørg for, at klienter opretter forbindelse til den aktive knude, når en knude går ned
For konsistens (datasikkerhed) skal du overveje følgende indstillinger:
- Udgiver bekræfter og manuelle anerkendelser på forbrugersiden
ha-promote-on-failure=when-synced, hvis udgivere kan prøve igen senere, og hvis du har meget pålidelig lagerplads! Ellers læg det=always.ha-sync-mode=automatic(men for store inaktive køer kan manuel tilstand være påkrævet; overvej også, om utilgængelighed vil resultere i mistede meddelelser)- Sæt minoritetstilstand på pause
- vedvarende beskeder
Vi har ikke dækket alle spørgsmålene om fejltolerance og høj tilgængelighed endnu; for eksempel hvordan man udfører administrative procedurer (såsom rullende opdateringer) sikkert. Vi skal også tale om føderation og Shovel-plugin.
Hvis jeg har overset noget andet, så lad mig det vide.
Se også min , hvor jeg laver kaos på en RabbitMQ-klynge ved hjælp af Docker og Blockade for at teste nogle af de meddelelsestabsscenarier, der er beskrevet i denne artikel.
Tidligere artikler i serien:
nr. 1 -
nr. 2 -
nr. 3 -
Kilde: www.habr.com
