Verdeelt Sperrung mat Redis

Hey Habr!

Haut bréngen mir Är Opmierksamkeet eng Iwwersetzung vun engem komplexen Artikel iwwer d'Ëmsetzung vun der verdeeler Sperrung mat Redis an invitéieren Iech iwwer d'Perspektive vu Redis als Thema ze schwätzen. Analyse vum Redlock Algorithmus a Fro vum Martin Kleppmann, Auteur vum Buch "Héich Last Uwendungen", ginn hei.

Verdeelt Sperrung ass e ganz nëtzlecht Primitiv benotzt a villen Ëmfeld wou verschidde Prozesser op gemeinsame Ressourcen op eng géigesäiteg exklusiv Manéier musse schaffen.

Et ginn eng Zuel vu Bibliothéiken a Posts dobaussen déi beschreiwen wéi een DLM (Distributed Lock Manager) mat Redis implementéiert, awer all Bibliothéik hëlt eng aner Approche an d'Garantien déi se ubidden sinn zimlech schwaach am Verglach mat deem wat mat e bësse méi sophistikéierten Design erreechbar ass.

An dësem Artikel wäerte mir probéieren e bedingt kanonesche Algorithmus ze beschreiwen deen weist wéi een verdeelt Sperrung mat Redis implementéiert. Mir schwätzen iwwer en Algorithmus genannt Redlock, et implementéiert e verdeelt Spär Manager an, eiser Meenung no, ass dësen Algorithmus méi sécher wéi déi üblech Single-Instance Approche. Mir hoffen, datt d'Gemeinschaft et analyséiert, Feedback gëtt a se als Startpunkt fir méi komplex oder alternativ Projeten benotzt.

Ëmsetzung

Ier Dir op d'Beschreiwung vum Algorithmus weider geet, gi mir e puer Linken op fäerdege Implementatiounen. Si kënne fir Referenz benotzt ginn.

Sécherheet an Disponibilitéit Garantien

Mir wäerten eisen Design mat just dräi Eegeschafte modelléieren, déi mir mengen déi Minimum Garantien ubidden fir effektiv verdeelt Sperrung ze benotzen.

  1. Sécherheet Propriétéit: Géigesäitege Ausgrenzung. Zu all Moment kann nëmmen ee Client de Spär halen.
  2. Disponibilitéit Property A: Keng Deadlocks. Et ass ëmmer méiglech schlussendlech e Sperr ze kréien, och wann de Client deen d'Ressource gespaart huet fällt oder op engem aneren Disksegment landen.
  3. Disponibilitéit Property B: Feeler Toleranz. Soulaang d'Majoritéit vun de Redis Knäppercher lafen, kënnen d'Clienten d'Schlëss kréien a befreien.

Firwat Implementatioun baséiert op Feeler Erhuelung ass net genuch an dësem Fall
Fir ze verstoen wat mir wäerte verbesseren, loosst eis den aktuellen Zoustand analyséieren mat de meeschte verdeelte Sperrbibliothéike baséiert op Redis.

Deen einfachste Wee fir eng Ressource mat Redis ze spären ass e Schlëssel an der Instanz ze kreéieren. Typesch gëtt e Schlëssel mat enger limitéierter Liewensdauer erstallt, dëst gëtt erreecht mat der Expires Feature, déi am Redis geliwwert gëtt, also fréier oder spéider gëtt dëse Schlëssel verëffentlecht (Eegeschaft 2 an eiser Lëscht). Wann de Client d'Ressource muss befreien, läscht de Schlëssel.

Op den éischte Bléck funktionnéiert dës Léisung ganz gutt, awer et gëtt e Problem: eis Architektur erstellt en eenzege Punkt vum Echec. Wat geschitt wann de Host Redis Instanz feelt? Loosst eis dann e Sklave addéieren! A mir wäerten et benotzen wann de Presentateur net verfügbar ass. Leider ass dës Optioun net liewensfäeg. Duerch dëst ze maachen, wäerte mir net fäeg sinn déi géigesäiteg Ausgrenzungseigenschafte richteg ëmzesetzen, déi mir brauchen fir Sécherheet ze garantéieren, well d'Replikatioun am Redis asynchron ass.

Selbstverständlech, an esou engem Modell geschitt e Rennen Zoustand:

  1. Client A kritt e Spär op de Meeschter.
  2. De Master feelt ier de Schlësselentrée un de Sklave transferéiert gëtt.
  3. De Follower gëtt zum Leader gefördert.
  4. Client B kritt e Spär op déiselwecht Ressource déi A scho gespaart huet. SECURITY VIOLATION!

Heiansdo ass et ganz normal datt a speziellen Ëmstänn, wéi zum Beispill e Feeler, vill Clienten d'Schloss gläichzäiteg halen. An esou Fäll kann eng Replikatioun-baséiert Léisung applizéiert ginn. Soss, recommandéiere mir d'Léisung an dësem Artikel beschriwwen.

Korrekt Ëmsetzung mat enger eenzeger Instanz

Ier Dir probéiert d'Mängel vun der uewen beschriwwener Single-Instanz Konfiguratioun ze iwwerwannen, loosst eis verstoen wéi Dir dësen einfache Fall richteg behandelt, well dës Léisung tatsächlech gëlteg ass an Uwendungen wou e Rennenbedingung vun Zäit zu Zäit akzeptabel ass, an och well d'Blockéierung op engem eenzeg Instanz déngt als Basis déi am verdeelt Algorithmus benotzt gëtt hei beschriwwen.

Fir e Schloss ze kréien, maacht dat:

SET resource_name my_random_value NX PX 30000

Dëse Kommando installéiert e Schlëssel nëmmen wann et net schonn existéiert (NX Optioun), mat enger Validitéit Period pa 30000 Millisekonnen (PX Optioun). De Schlëssel ass op "myrandomvalue" Dëse Wäert muss eenzegaarteg sinn iwwer all Clienten an all Spär Ufroen.
Prinzipiell gëtt e zoufällege Wäert benotzt fir d'Schloss sécher ze befreien, mat engem Skript deen Redis seet: nëmmen de Schlëssel erofhuelen wann et existéiert an de Wäert an deem gespäichert ass genau dat wat erwaart gouf. Dëst gëtt erreecht andeems Dir de folgende Lua Skript benotzt:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

Dëst ass wichteg fir ze verhënneren datt e Spär vun engem anere Client geläscht gëtt. Zum Beispill kann e Client e Sperre kréien, dann an enger Operatioun gespaart ginn, déi méi laang dauert wéi déi éischt Sperrung (sou datt de Schlëssel Zäit huet fir ofzelafen), a spéider de Spär ewechhuelen, deen en anere Client gesat huet.
Mat engem einfachen DEL ass onsécher well e Client e Spär vun engem anere Client ewechhuelen kann. Am Géigesaz, wann Dir den uewe genannte Skript benotzt, gëtt all Spär mat enger zoufälleger String "ënnerschriwwen", sou datt nëmmen de Client deen et virdru plazéiert huet et ewechhuelen kann.

Wat soll dës zoufälleg String sinn? Ech denken datt et 20 Bytes vun /dev/urandom sollt sinn, awer Dir kënnt manner deier Weeër fannen fir d'String eenzegaarteg genuch fir Är Zwecker ze maachen. Zum Beispill wier et gutt fir RC4 mat /dev/urandom ze säen an dann e pseudo-zoufälleg Stream dovunner ze generéieren. Eng méi einfach Léisung beinhalt eng Kombinatioun vun der Unix Zäit an der Mikrosekonne Resolutioun plus d'Client ID; et ass net esou sécher, awer et ass méiglecherweis op d'Aufgab an de meeschte Kontexter.

D'Zäit déi mir als Moossnam fir d'Liewensdauer vum Schlëssel benotzen, nennt sech d'"Spär Liewensdauer". Dëse Wäert ass souwuel d'Zäit ier d'Spär automatesch fräigelooss gëtt an d'Quantitéit vun der Zäit, déi e Client muss eng Operatioun ofgeschloss hunn, ier en anere Client dës Ressource ofschléissen kann, ouni tatsächlech géigesäitege Ausgrenzungsgarantie ze verletzen. Dës Garantie ass limitéiert nëmmen op eng gewëssen Zäitfenster, déi ufänkt vum Moment wou d'Schloss kaaft gëtt.

Also hu mir e gudde Wee diskutéiert fir e Schloss ze kréien an ze befreien. De System (wa mir vun engem net-verdeelte System schwätzen, deen aus enger eenzeger an ëmmer verfügbarer Instanz besteet) ass sécher. Loosst eis dëst Konzept op e verdeelt System ausdehnen, wou mir keng esou Garantien hunn.

Redlock Algorithmus

Déi verdeelt Versioun vum Algorithmus gëtt ugeholl datt mir N Redis Meeschter hunn. Dës Node si komplett onofhängeg vuneneen, also benotze mir keng Replikatioun oder all aner implizit Koordinatiounssystem. Mir hu schonn iwwerdeckt wéi een e Spär op enger eenzeger Instanz sécher erfaasst a befreit. Mir huelen et selbstverständlech datt den Algorithmus, wann Dir mat enger eenzeger Instanz schafft, genau dës Method benotzt. An eise Beispiller setzen mir N op 5, wat e raisonnabele Wäert ass. Also musse mir 5 Redis Masters op verschiddene Computeren oder virtuelle Maschinnen benotzen fir sécherzestellen datt se gréisstendeels onofhängeg vuneneen handelen.

Fir e Schloss ze kréien, mécht de Client déi folgend Operatiounen:

  1. Kritt déi aktuell Zäit a Millisekonnen.
  2. Sequentiell probéiert e Spär op all N Instanzen ze kréien, andeems de selwechte Schlësselnumm an zoufälleg Wäerter an alle Fäll benotzt. An der Stage 2, wann de Client e Spär op enger Per-Instanz Basis opstellt, benotzt de Client eng Verzögerung fir et ze kréien, dee kuerz genuch ass am Verglach mat der Zäit no där de Spär automatesch fräigelooss gëtt. Zum Beispill, wann d'Blockéierungsdauer 10 Sekonnen ass, da kéint d'Verzögerung am Beräich vun ~5-50 Millisekonnen sinn. Dëst eliminéiert d'Situatioun an där de Client fir eng laang Zäit blockéiert ka bleiwen fir e gescheitert Redis Node z'erreechen: wann d'Instanz net verfügbar ass, probéieren mir sou séier wéi méiglech mat enger anerer Instanz ze verbannen.
  3. Fir e Spär ze huelen, berechent de Client wéi vill Zäit vergaangen ass; Fir dëst ze maachen, subtracts et vum aktuellen Zäitwäert den Zäitstempel deen am Schrëtt kritt gouf 1. Wann an nëmmen wann de Client d'Spär op d'Majoritéit vun Instanzen kritt huet (op d'mannst 3), an d'Gesamtzäit huet et gedauert d'Spär kréien, manner wéi d'Spär Dauer, gëtt de Spär als kritt.
  4. Wann e Spär opkaf ass, gëtt d'Spärdauer als ursprénglech Sperrdauer minus déi vergaangen Zäit berechent am Schrëtt 3 ugeholl.
  5. Wann de Client et net fäerdeg bréngt d'Spär aus iergendengem Grond ze kréien (entweder et konnt N/2+1 Instanzen net spären, oder d'Spär Dauer war negativ), da wäert et probéieren all Instanzen opzemaachen (och déi, déi hien geduecht huet et net ze blockéieren ).

Ass den Algorithmus asynchron?

Dësen Algorithmus baséiert op der Virgab datt, obwuel et keng synchroniséiert Auer ass, op där all Prozesser funktionnéieren, d'Lokalzäit an all Prozess nach ëmmer am selwechte Tempo fléisst, an de Feeler ass kleng am Verglach mat der Gesamtzäit no där d'Spär ass. automatesch verëffentlecht. Dës Virgab ass ganz ähnlech zu der Situatioun typesch fir normal Computeren: all Computer huet eng lokal Auer, a mir kënnen normalerweis op der Tatsaach zielen, datt den Zäit Ënnerscheed tëscht verschiddene Computeren kleng ass.

Zu dësem Zäitpunkt musse mir eis géigesäiteg Ausgrenzungsregel méi virsiichteg formuléieren: géigesäiteg Ausgrenzung ass nëmme garantéiert wann de Client deen d'Schloss hält während der Zäit wou d'Schloss gëlteg ass (dëse Wäert kritt am Schrëtt 3), minus e bësse méi Zäit (am Ganzen e puer) Millisekonnen fir den Zäitdifferenz tëscht Prozesser ze kompenséieren).

De folgenden interessanten Artikel erzielt méi iwwer esou Systemer déi Koordinatioun vun Zäitintervaller erfuerderen: Leases: en efficace Feeler-tolerante Mechanismus fir verdeelt Fichier Cache Konsequenz.

Widderhuelen op Echec

Wann e Client et net fäerdeg bréngt e Spär ze kréien, muss et no enger zoufälleger Verspéidung nach eng Kéier probéieren; dëst gëtt gemaach fir verschidde Clienten ze desynchroniséieren déi probéieren e Spär op der selwechter Ressource zur selwechter Zäit ze kréien (wat kann zu enger "Split-Brain" Situatioun féieren an där et keng Gewënner gëtt). Zousätzlech, wat méi séier e Client probéiert e Spär op eng Majoritéit vu Redis Instanzen ze kréien, dest méi schmuel ass d'Fënster an där eng gespléckt Gehir Situatioun kann optrieden (an wat manner de Besoin fir Neiversichten ass). Dofir, am Idealfall, sollt de Client probéieren SET Kommandoen op N Instanzen gläichzäiteg mat Multiplexing ze schécken.

Et ass derwäert hei ze ënnersträichen, wéi wichteg et ass fir Clienten, déi d'Majoritéit vun de Schlässer net kréien, déi (deelweis) erfaasst Schlässer ze léisen, sou datt se net musse waarden bis de Schlëssel ofleeft, ier d'Schloss op der Ressource erëm ka kaaf ginn. (obwuel wann d'Netzfragmentatioun geschitt , an de Client verléiert de Kontakt mat de Redis Instanzen, da musst Dir eng Disponibilitéitsstrof bezuelen wärend Dir op de Schlëssel waart fir oflafen).

Loosst d'Spär

E Spär fräiginn ass eng einfach Operatioun déi einfach all Instanzen ophiewen erfuerdert, egal ob de Client schéngt eng bestëmmte Instanz erfollegräich gespaart ze hunn.

Sécherheet Considératiounen

Ass den Algorithmus sécher? Loosst eis probéieren eis virzestellen wat a verschiddene Szenarie geschitt.

Fir unzefänken, loosst eis unhuelen datt de Client e Spär op d'Majoritéit vun Instanzen konnt kréien. All Instanz enthält e Schlëssel mat der selwechter Liewensdauer fir all. Wéi och ëmmer, all eenzel vun dëse Schlësselen gouf zu enger anerer Zäit installéiert, sou datt se zu verschiddenen Zäiten oflafen. Awer wann den éischte Schlëssel gläichzäiteg net méi schlëmm wéi T1 installéiert gouf (d'Zäit déi mir wielen ier Dir den éischte Server kontaktéiert), an de leschte Schlëssel gouf op enger Zäit net méi schlëmm wéi T2 installéiert (d'Zäit wou d'Äntwert kritt gouf) vum leschte Server), da si mir zouversiichtlech datt den éischte Schlëssel am Set deen ofleeft op d'mannst iwwerlieft MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT. All aner Schlësselen wäerten spéider oflafen, sou datt mir sécher kënne sinn datt all Schlëssel gläichzäiteg fir op d'mannst dës Kéier gëlteg sinn.

Wärend der Zäit wou déi meescht Schlëssele gëlteg bleiwen, kann en anere Client d'Schloss net kréien, well N/2+1 SET NX Operatiounen net geléngen wann N/2+1 Schlësselen scho existéieren. Dofir, wann e Schloss opkaf ass, ass et onméiglech et am selwechte Moment erëm ze kréien (dëst géif de géigesäitege Ausgrenzungseigendom verletzen).
Wéi och ëmmer, mir wëllen dofir suergen datt verschidde Clienten déi probéieren e Spär zur selwechter Zäit ze kréien net gläichzäiteg geléngen.

Wann de Client d'Majoritéit vun Instanzen fir ongeféier oder méi wéi déi maximal Spär Dauer gespaart huet, wäert et d'Spär als ongëlteg betruechten an d'Instanzen opmaachen. Dofir musse mir nëmmen de Fall berücksichtegen an deem de Client et fäerdeg bruecht huet d'Majoritéit vun de Fäll an enger Zäit manner wéi den Verfallsdatum ze blockéieren. An dësem Fall, iwwer déi uewe genannten Argument, während der Zäit MIN_VALIDITY kee Client däerf fäeg sinn d'Spär erëm ze kréien. Dofir kënne vill Clienten N / 2 + 1 Instanzen an der selwechter Zäit spären (wat um Enn vun der Etapp 2 ophält) nëmmen wann d'Zäit fir d'Majoritéit ze spären méi grouss war wéi d'TTL Zäit, wat d'Spär ongëlteg mécht.

Kënnt Dir e formelle Beweis vu Sécherheet ubidden, existéierend ähnlech Algorithmen uginn oder e Feeler an der uewen fannen?

Accessibilitéit Considératiounen

System Disponibilitéit hänkt vun dräi Haaptcharakteristiken of:

  1. Automatesch Spären fräiginn (wéi d'Schlësselen oflafen): Schlësselen wäerte schliisslech erëm verfügbar sinn fir fir Spären ze benotzen.
  2. D'Tatsaach, datt d'Clienten normalerweis géigesäiteg hëllefen andeems d'Schlässer ofgeschaaft ginn, wann de gewënschte Spär net erfonnt gouf, oder kaaft gouf an d'Aarbecht fäerdeg ass; also et ass wahrscheinlech datt mir net musse waarden bis d'Schlësselen oflafen fir d'Schloss erëm ze kréien.
  3. D'Tatsaach datt wann e Client muss erëm probéieren e Sperr ze kréien, waart et op eng relativ méi laang Zäit wéi d'Period déi néideg ass fir déi meescht Spären ze kréien. Dëst reduzéiert d'Wahrscheinlechkeet datt eng gespléckt Gehir Situatioun entstinn wann Dir fir Ressourcen konkurréiert.

Wéi och ëmmer, et gëtt eng Disponibilitéitsstrof gläich wéi den TTL vun den Netzwierksegmenter, also wann et kontinuéierlech Segmenter sinn, kann d'Strof onbestëmmt sinn. Dëst geschitt wann e Client e Sperr kritt an dann an en anert Segment reift ier e se fräigelooss huet.

Prinzipiell, onendlech kontinuéierlech Netzwierksegmenter, kann e System fir eng onendlech Zäit net verfügbar bleiwen.

Leeschtung, Failover an fsync

Vill Leit benotzen Redis well se eng héich Sperrserverleistung brauchen wat d'Latenz ugeet fir Spären ze kréien an ze befreien, an d'Zuel vun den Acquisitioune / Verëffentlechungen déi pro Sekonn ofgeschloss kënne ginn. Fir dës Ufuerderung z'erreechen, gëtt et eng Strategie fir mat N Redis Serveren ze kommunizéieren fir latency ze reduzéieren. Dëst ass eng Multiplexingstrategie (oder "Aarme Mann Multiplexing", wou de Socket an net-blockéierend Modus gesat gëtt, all Kommandoen schéckt, a spéider d'Befehle liest, unzehuelen datt d'Ronnreeszäit tëscht dem Client an all Instanz ähnlech ass) .

Wéi och ëmmer, mir mussen och d'Considératioun berücksichtegen, déi mat laangfristeg Datelagerung assoziéiert, wa mir probéieren e Modell mat zouverléissege Erhuelung vu Feeler ze kreéieren.

Prinzipiell, fir d'Thema ze klären, loosst eis unhuelen datt mir Redis konfiguréieren ouni laangfristeg Datelagerung. De Client verwalt 3 vun 5 Instanzen ze blockéieren. Ee vun den Instanzen, déi de Client et fäerdeg bruecht huet ze blockéieren, gëtt nei gestart, an am Moment sinn et erëm 3 Instanzen fir déiselwecht Ressource, déi mir kënne blockéieren, an en anere Client kann am Tour déi nei gestart Instanz blockéieren, wat d'Sécherheetseigenschaft verletzt iwwerhëlt Exklusivitéit vun spären.

Wann Dir Daten viraus aktivéiert (AOF), wäert d'Situatioun liicht verbesseren. Zum Beispill kënnt Dir e Server förderen andeems Dir de SHUTDOWN Kommando schéckt an et nei start. Zënter Verfallsoperatioune bei Redis semantesch ëmgesat ginn sou datt d'Zäit weider fléisst och wann de Server ausgeschalt ass, sinn all eis Ufuerderunge gutt. Dëst ass normal soulaang e normale Shutdown assuréiert ass. Wat maachen am Fall vu Stroumausfall? Wann Redis par défaut konfiguréiert ass, mat fsync Synchroniséierung op Disk all Sekonn, dann ass et méiglech datt mir no engem Restart net eise Schlëssel hunn. Theoretesch, wa mir Spär Sécherheet während all Instanz Neistart wëllen garantéieren, solle mir aktivéieren fsync=always an den Astellunge fir laangfristeg Datelagerung. Dëst wäert d'Performance komplett ëmbréngen, bis op den Niveau vun de CP Systemer déi traditionell benotzt gi fir sécher verdeelt Spären ëmzesetzen.

Awer d'Situatioun ass besser wéi et op den éischte Bléck schéngt. Prinzipiell ass d'Sécherheet vum Algorithmus bewahrt, well wann d'Instanz no engem Ausfall nei gestart gëtt, ass se net méi un all Spär matmaachen, deen am Moment aktiv ass.

Fir dëst ze garantéieren, brauche mir just sécherzestellen datt no engem Feeler d'Instanz net verfügbar bleift fir eng Zäit méi wéi déi maximal TTL déi mir benotzen. Op dës Manéier wäerte mir waarden bis den Verfallsdatum an d'automatesch Verëffentlechung vun alle Schlësselen déi aktiv waren am Moment vum Echec.

Mat verspéiten Neistarten ass et am Prinzip méiglech Sécherheet ze erreechen och wann keng laangfristeg Persistenz zu Redis ass. Notéiert awer datt dëst zu enger Geldstrof kënnt fir d'Verletzung vun der Accessibilitéit. Zum Beispill, wann d'Majoritéit vun Instanzen feelen, gëtt de System fir den TTL weltwäit net verfügbar (a keng Ressource kann während dëser Zäit blockéiert ginn).

Mir erhéijen d'Disponibilitéit vum Algorithmus: mir verlängeren d'Blockéierung

Wann d'Aarbecht vun de Clienten aus klenge Schrëtt besteet, ass et méiglech d'Default-Schlossdauer ze reduzéieren an e Mechanismus fir d'Verlängerung vun de Spären ëmzesetzen. Am Prinzip, wann de Client beschäftegt ass mat Informatik an de Sperrverfallswäert geféierlech niddereg ass, kënnt Dir e Lua Skript un all Instanzen schécken, déi den TTL vum Schlëssel verlängeren, wann de Schlëssel nach ëmmer existéiert a säi Wäert nach ëmmer e zoufällege Wäert ass, deen de Schlëssel kritt. Spär opkaf.

E Client soll nëmmen e Spär als erëmgewielt betruechten wann et et fäerdeg bruecht huet d'Majoritéit vun Instanzen bannent der Validitéitsperiod ze spären.

Richteg, technesch ännert den Algorithmus net, sou datt d'maximal Unzuel vu widderholl Versiche fir Spären ze kréien muss limitéiert sinn, soss ginn d'Accessibilitéitseigenschaften verletzt.

Source: will.com

Setzt e Commentaire