Jaotatud lukustamine Redis'i abil

Tere, Habr!

TĂ€na pakume teie tĂ€helepanu tĂ”lget keerulisest artiklist jaotatud lukustuste rakendamisest Redis'i abil ning arutame Redis'i perspektiivit ka teema osas. AnalĂŒĂŒsitakse Martin Kleppmanni Redlocki algoritmi, kes on raamatu autor "Karmid rakendused", mis on esitatud siit.

Jaotatud lukustused on vÀga kasulik primitiiv, mida kasutatakse paljudes keskkondades, kus erinevad protsessid peavad tegutsema jagatud ressursside osas vastavalt vastastikuse vÀlistamise pÔhimÔttele.

On olemas hulk teeke ja postitusi, mis kirjeldavad, kuidas rakendada DLM-i (jaotatud lukustuste haldurit) Redis'i abil, kuid igas teegis kasutatakse oma lĂ€henemist ning antud garantiid on ĂŒsna nĂ”rgad vĂ”rreldes sellega, mida on vĂ”imalik saavutada veidi keerukama projekteerimise abil.

Selles artiklis proovime kirjeldada tinglikult kanonilist algoritmi, mis demonstreerib, kuidas rakendada jaotatud lukustusi Redis'i abil. RÀÀgime algoritmist nimega Redlock, see, see ja see algorithmi on ohut. Loodame, et kogukond analĂŒĂŒsib seda, annab tagasisidet ja kasutab seda keerukamate vĂ”i alternatiivsete projektide elluviimise lĂ€htepunktina.

Teostused

Enne algoritmi kirjelduse juurde asumist toome vÀlja mÔned lingid juba valmisteostele. Nendega saab tutvuda viidates.

Turvalisuse ja kÀttesaadavuse garantiid

Kavandame oma projekti, seadistades ainult kolm omadust, mis, nagu meie arvates, pakuvad minimaalset garantiid ja vÔimaldavad tÔhusat jaotatud lukustuse kasutamist.

  1. Turvalisuse omadus: Vastastikune vĂ€listamine. Igasuguses hetkes vĂ”ib vaid ĂŒks klient hoida lukku.
  2. KÀttesaadavuse omadus A: Vastastikuste lukustuste puudumine. LÔppkokkuvÔttes on alati vÔimalik saada lukku, isegi kui ressursi lukustanud klient ebaÔnnestub vÔi satub teisele kettasegmendile.
  3. KÀttesaadavuse omadus B: Rikete taluvus. Kui enamus Redis sÔlmedest töötab, saavad kliendid lukke omandada ja vabastada.

Miks taastumisest sÔltuva rakenduse tÔhusus ei ole sel juhul piisav
Et mĂ”ista, mida me kavatseme parandada, analĂŒĂŒsime praegust olukorda enamikus jaotatud lukustuse raamatukogudes, mis pĂ”hinevad Redis-il.

Ressursi blokeerimise kÔige lihtsam viis Redis'i abil on luua instantsis vÔtmed. Tavaliselt luuakse vÔti piiratud eluaega, mis saavutatakse Redis'is olemasoleva expires omaduse abil, seega vabastatakse see vÔti varem vÔi hiljem (meie loendi omadus 2). Kui kliendile on vajalik ressursi vabastamine, kustutab ta vÔtme.

Esmapilgul töötab see lahendus kenasti, kuid probleem on selles, et meie arhitektuuris tekib ĂŒhtne rikke punkt. Mis juhtub, kui peamine Redis'i instants ebaĂ”nnestub? Lisame siis alaminstantsi! Ja kasutame seda, kui peamine on kĂ€ttesaamatu. Kahjuks pole see variant elujĂ”uline. Selliselt mĂ€ngides ei suuda me Ă”igesti rakendada vastasseisu omadust, mis on vajalik meie ohutuse tagamiseks, kuna reproduktsioon Redis'is on asĂŒnkroonne.

Ilmselgelt tekib sellises mudelis vÔistlusolukord:

  1. Kliendi A blokeering omandatakse peamisel instantsil.
  2. Peamine ebaÔnnestub enne, kui vÔti on alaminstantsile edastatud.
  3. Alaminstants tÔuseb peamiseks.
  4. Kliendi B omandab sama ressursi blokeeringu, mis on juba A poolt blokeeritud. OHUTUSE RIKKUMINE!

MÔnikord on tÀiesti normaalne, et erilistes olukordades, nÀiteks vÀljajÀtmise korral, vÔivad paljud kliendid samaaegselt hoida lukku. Sellistel juhtudel vÔib rakendada replikatsioonipÔhist lahendust. Muudel juhtudel soovitame jÀrgida selles artiklis kirjeldatud lahendust.

Õige teostamine ĂŒhe instantsi puhul

Enne kui pĂŒĂŒame ĂŒletada ĂŒlalkirjeldatud ĂŒhe instantsi konfiguratsiooni puudusi, vaatame, kuidas Ă”igesti kĂ€ituda selles lihtsalt olukorras, kuna selline lahendus on tegelikult lubatud nendes rakendustes, kus vĂ”istlusolukord on aeg-ajalt lubatud, samuti seetĂ”ttu, et ĂŒhe instantsi lukku seadmise aluseks on see, mida kasutatakse siin kirjeldatud hajutatud algoritmis.

Luku omandamiseks teeme jÀrgmist:

SET resource_name my_random_value NX PX 30000

See kÀsk seab vÔtme ainult siis, kui see veel ei eksisteeri (valik NX), kehtivusaeg on 30000 millisekundit (valik PX). vÔtme vÀÀrtuseks on "myrandomvalue". See vÀÀrtus peab olema unikaalne kÔigi klientide ja kÔikide lukustamissoovide seas.
PĂ”himĂ”tteliselt kasutatakse juhuslikku vÀÀrtust ohutuks lukustuse vabastamiseks, kasutades skripti, mis teatab Redis'ile: eemalda vĂ”ti, ainult kui see eksisteerib, ja vÀÀrtus, mis on seal salvestatud – on tĂ€pselt see, mida oodati. See saavutatakse jĂ€rgmise Lua skripti abil:

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

See on oluline, et vÀltida lukustuse vabastamist, mille on teinud teine klient. NÀiteks vÔib klient omandada lukustuse, siis jÀÀda mingisugusesse operatsiooni kinni, mis kestab kauem kui esimese lukustuse kehtivusaeg (nii, et vÔtme kehtivusaeg on juba aegunud), ja hiljem kustutada lukustus, mille seadis mÔni teine klient.
Lihtsa DEL'i kasutamine ei ole ohutu, kuna klient vĂ”ib kustutada lukustuse, mille on seadnud teine klient. Seevastu, kasutades eeltoodud skripti, on iga lukustus „allkirjastatud“ juhuslikku stringiga, seega saab seda kustutada vaid see klient, kes selle varem seadis.

Milline peaks olema see juhuslik string? Arvan, et see peaks olema 20 baiti /dev/urandom'ist, kuid on olemas ka odavamaid viise, et luua piisavalt ainulaadne string teie ees seisvate eesmĂ€rkide jaoks. NĂ€iteks on vĂ”imalik RC4 kĂŒlvata /dev/urandom'ist ja seejĂ€rel selle pĂ”hjal genereerida pseudojuhuslik voog. Lihtsam lahendus hĂ”lmab Unix-aega mikrosekundilise tĂ€psusega pluss kliendi ID; see ei ole nii turvaline, kuid vastab tĂ”enĂ€oliselt enamikus kontekstidest esitatud ĂŒlesannetele.

Aeg, mida kasutame vÔtme eluea nÀitajana, nimetatakse "lukustuse kehtivuse ajaks". See vÀÀrtus on samaaegselt aeg, mille möödudes lukustus automaatselt vabastatakse, ja aeg, mille jooksul kliendil on vÔimalus tehingut teostada, enne kui teine klient saab omakorda selle ressursi lukustada, ilma et see rikuks tÔeliselt vastastikuse vÀlistamise garantiisid. Selline garantii on piiratud vaid kindla ajavahemikuga, mis algab lukustuse omandamise hetkest.

Nii et, oleme arutanud head moodust hankida ja vabastada lukku. System (kui tegemist on mittejaotatud sĂŒsteemiga, mis koosneb ĂŒhest ja alati kĂ€ttesaadavast instantsist) on turvaline. Laieneme sellele kontseptsioonile jaotatud sĂŒsteemis, kus selliseid garantiisid meil enam ei ole.

Redlock algoritm

Jaotatud versiooni algoritmist eeldatakse, et meil on N peamist Redis't. Need sĂ”lmed on tĂ€iesti sĂ”ltumatud ĂŒksteisest, seetĂ”ttu me ei kasuta replikatsiooni ega mingit muud varjatud koordineerimissĂŒsteemi. Oleme juba rÀÀkinud, kuidas turvaliselt hankida ja vabastada lukku ĂŒhes instantsis. Eeldame, et algoritm kasutab ĂŒhes instantsis töötades seda meetodit. Meie nĂ€idetes seame N-iks 5, mis on tĂ€iesti mĂ”istlik vÀÀrtus. SeetĂ”ttu peame kasutama 5 peamist Redis't erinevates arvutites vĂ”i virtuaalmasinates, et tagada nende pĂ”hiline sĂ”ltumatuse ĂŒksteisest.

Luku saamiseks tÀidab klient jÀrgmised toimingud:

  1. Saab praegust aega millisekundites.
  2. JĂ€rgneb ĂŒritusele kĂ”ikide N instantside lukustamine, kasutades kĂ”igil juhtudel sama vĂ”tme nime ja juhuslikke vÀÀrtusi. Etapi 2 jooksul, kui lukustame iga instantsi, kasutab klient, et see saada, viivitust, mis on piisavalt lĂŒhike vĂ”rreldes ajaga, mille jooksul lukk automaatselt eemaldatakse. NĂ€iteks kui lukustamise kestus on 10 sekundit, vĂ”ib viivitus olla vahemikus ~ 5-50 millisekundit. Seega vĂ€listatakse olukord, kus klient vĂ”ib pikka aega lukus olla, pĂŒĂŒdes ĂŒhendust katkestanud Redis sĂ”lmega: kui instants pole saadaval, pĂŒĂŒame vĂ”imalikult kiiresti ĂŒhendust vĂ”tta teise instantsiga.
  3. Kliendil on blokeeringu saamiseks arvutada, kui kaua aega on möödunud; selleks lahutab ta praegusest ajast ajamĂ€rgi, mis saadi esimese sammu kĂ€igus. Ainus hetk, mil blokeering on edukas, on siis, kui klient suudab blokeerida enamikku instantsidest (vĂ€hemalt 3), ja kogu aeg, mis kulus blokeeringu saamiseks, on lĂŒhem kui blokeeringu kehtivusaeg.
  4. Kui blokeering on saadud, on selle kehtivusaeg algne blokeeringu kestus miinuseks möödunud aeg, mis arvutati kolmandas sammus.
  5. Kui kliendil ei Ă”nnestu mingil pĂ”hjusel blokeeringut saada (kas ei suudeta blokeerida N/2+1 instantsi vĂ”i on blokeeringu kehtivusaeg negatiivne), siis ta pĂŒĂŒab kĂ”ik instantsid uuesti vabastada (ka need, mida ta arvas, et ei suutnud blokeerida).

Kas algoritm on asĂŒnkroonne?

See algoritm pĂ”hineb eeldusel, et kuigi kĂ”ikidel protsessidel ei ole sĂŒnkroonitud kelli, kulgeb iga protsessi kohaliku aja voog siiski ligikaudu ĂŒhes tempos ning viga on vĂ€ike vĂ”rreldes sellele ajale, mille möödudes lukustus automaatselt tĂŒhistatakse. See eeldus meenutab olukorda, mis on iseloomulik tavalistele arvutitele: igal arvutil on kohalik kell ja tavaliselt vĂ”ime loota, et erinevate arvutite ajavahe on vĂ€ike.

Selles etapis peame oma vastastikuse vÀlistamise reegli tÀpsemalt sÔnastama: vastastikune vÀlistamine on tagatud ainult siis, kui klient, kes lukustuse hoiab, lÔpetab tegevuse ajavahemikus, mille jooksul lukustus on kehtiv (see vÀÀrtus saadakse 3. sammus), miinus veel natuke aega (kokku mÔni millisekund, et kompenseerida ajavahe protsesside vahel).

Sarnaste sĂŒsteemide kohta, mis nĂ”uavad ajavahe koordineerimist, rÀÀgib jĂ€rgmine huvitav artikkel: Leases: tĂ”hus veakindel mehhanism jaotatud failide vahemĂ€lu jĂ€rjepidevuse jaoks.

Uuesti proovimine, kui ebaÔnnestub

Kui kliendil ei Ă”nnestu lukku saada, peab ta proovima uuesti, lĂ€bides juhusliku viivituse; see toimub selleks, et desĂŒnkroniseerida mitut klienti, kes ĂŒritavad sama ressursi lukku korraga saada (mis vĂ”ib viia „jaotatud mĂ”istuse” olukorrani, kus ei ole vĂ”itjat). Lisaks, mida kiiremini klient ĂŒritab enamikku Redis instantsidest lukku saada, seda kitsam on aken, kus jaotatud mĂ”istuse olukord vĂ”ib tekkida (ja seda vĂ€hem on vaja uuesti proovida). SeetĂ”ttu peaks klient ideaalis proovima samal ajal saata SET kĂ€sud N instantsile multiplexerimise abil.

Siin on oluline rĂ”hutada, kui tĂ€htis on, et kliendid, kes ei suutnud enamikku lukustustest osta, vabastaksid (osaliselt) ostetud lukustused, et ei peaks ootama vĂ”tme kehtivusaja lĂ”puni, enne kui lukustus ressursi ĂŒle saab taas ostetud (kuid kui toimub vĂ”rgu fragmentatsioon ja klient kaotab ĂŒhenduse Redis’i instantsidega, siis tuleb maksta karistus kĂ€ttesaadavuse rikkumise eest, samal ajal kui oodatakse vĂ”tme kehtivusaja lĂ”ppemist).

Lukustuse vabastamine

Lukustuse vabastamine on lihtne toiming, mis nÔuab lihtsalt, et vabastada kÔik instantsid, sÔltumata sellest, kas klient arvab, et ta suutis konkreetse instantsi edukalt lukustada.

Turvalisuse kaalutlused

Kas algoritm on turvaline? Proovime ette kujutada, mis juhtub erinevates stsenaariumides.

Esmaltame, et klient suudab saada enamus instantsidest blokeeritud. Iga instants sisaldab vĂ”tme, millel on kĂ”igil sama eluaeg. Siiski mÀÀrati iga vĂ”tme aeg erineval hetkel, mistĂ”ttu kehtivus lĂ”ppeb neil erinevalt. Kui esimene vĂ”ti loodi ajavahemikus, mis ei ole halvem kui T1 (aeg, mille valime enne esimesse serverisse ĂŒhendust), ja viimane vĂ”ti loodi ajavahemikus, mis ei ole halvem kui T2 (aeg, mil saime vastuse viimasest serverist), siis oleme kindlad, et esimene vĂ”ti, mille kehtivusaeg lĂ”peb, kehtib vĂ€hemalt MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT. KĂ”ik teised vĂ”tmed aeguvad hiljem, seega vĂ”ime olla kindlad, et kĂ”ik vĂ”tmed on samal ajal kehtivad vĂ€hemalt selle aja jooksul.

Ajal, mil enamik vÔtitest kehtib, ei saa teine klient blokeeringut omandada, kuna N/2+1 SET NX operatsioon ei saa lÔppeda edu saavutamisega, kui juba on olemas N/2+1 vÔtmed. SeetÔttu, kui blokeering on omandatud, ei ole vÔimalik seda samal hetkel uuesti omandada (see rikuks vastastikuse vÀlistuse omadust).
TĂ”si, me tahame veenduda, et mitu klienti, kes ĂŒritavad samal ajal blokeeringut omandada, ei saa samal ajal selles Ă”nnestuda.

Kui klient on enamiku instantsidest blokeerinud, kulutades selleks aega umbes vĂ”i rohkem, kui maksimaalne blokeeringu kestus, siis peab ta blokeeringut kehtetuks ja vabastab instantsid. SeetĂ”ttu peame arvestama vaid sellega, et kliendil Ă”nnestus enamus instantsidest blokeerida ajaga, mis on lĂŒhem kui kehtivusaeg. Sel juhul, mis puudutab eeltoodud argumenti, aja jooksul MIN_VALIDITY Ükski klient ei tohiks olla suuteline blokeeringut uuesti saama. SeetĂ”ttu saavad paljud kliendid blokeerida N/2+1 eksemplari samal ajal (mis lĂ”ppeb etapi 2 lĂ”petamise ajal), ainult juhul, kui enamikule blokeerimise aeg oli pikem kui TTL aeg, muutes blokeeringu kehtetuks.

Kas suudate esitada ametlikku turvalisuse tÔestust, tuua vÀlja sarnased olemasolevad algoritmid vÔi leida esitatud lahenduses vea?

Saadavuse kaalutlused

SĂŒsteemi k beschikbaarheid sĂ”ltub kolmest peamisest omadusest:

  1. Automaatne blokeeringu eemaldamine (kuna vÔtmete kehtivusaeg lÔppeb): lÔpuks saavad vÔtmed uuesti kÀttesaadavaks, et neid blokeeringuteks kasutada.
  2. Fakt, et kliendid aitavad tavaliselt teineteist, eemaldades blokeeringud, kui vajalik blokeering ei ole soetatud vÔi kui see on soetatud ja töö on lÔpetatud; seega on tÀiesti tÔenÀoline, et me ei pea ootama vÔtmete aegumist, et uuesti blokeeringut soetada.
  3. Kui klient peab lukustuse uuesti saama, ootab ta vÔrreldes enamiku lukustuste omandamiseks kuluvaga suhteliselt kauem. See vÀhendab olukorra tekkimise tÔenÀosust, kus aju on jagatud ressursside konkurentsis.

Kuid pideva saadavuse vÀhenemise eest tuleb maksta trahvi, mis vÔrdub TTL-i ajaga vÔrgu segmentides, seega vÔivad pidevad segmendid selle trahvi mÀÀramatuks muuta. See juhtub igal korral, kui klient omandab lukustuse ja siis katkestatakse teises segmendis enne, kui ta selle vabastada jÔuab.

PĂ”himĂ”tteliselt, kui vĂ”rgu segmente on lĂ”putult, vĂ”ib sĂŒsteem olla kĂ€ttesaamatu lĂ”pmatu aja.

TÔhusus, taastumine tÔrgetest ja fsync

Paljud kasutavad Redis't, kuna on vajalik tagada kĂ”rge tulude lukustamise serveri jĂ”udlus, viivituste tasemel, mis on vajalik lukustamise ja vabastamise kujundamiseks, samuti operatsioonide arvu osas, mida suudame sekundis teostada. Selle nĂ”ude tĂ€itmiseks on olemas suhtlemisstrateegia N Redis serveriga, et vĂ€hendada viivitust. See on multiplexerimise strateegia (vĂ”i nn „vaesestamise multiplexer”, mille korral socket seatakse mitteblokeerivasse reĆŸiimi, saadab kĂ”ik kĂ€sud ja loeb neid hiljem, arvestades, et ajakulu kliendi ja iga instantsiga on sarnane).

TÔsi, tuleb arvesse vÔtta ka pikaajaliste andmete sÀilitamise aspekti, kui soovime luua mudeli, millel on usaldusvÀÀrne taastamine tÔrgete korral.

TĂ”epoolest, et selgust tuua, oletame, et konfigureerime Redis ilma pikaajalise andmesĂ€ilitamiseta. Klient suudab lukustada 3 5 instantsist. Üks instantsidest, mille klient suudab lukustada, taaskĂ€ivitub ning selle hetkeni tekib taas 3 instantsi sama ressursi jaoks, mille me saame lukustada, ning teine klient vĂ”ib omakorda lukustada taaskĂ€itatud instantsi, rikkudes lukustuste eksklusiivsuse turvafunktsiooni.

Kui lubada edasiviivat andmesalvestust (AOF), paraneb olukord veidi. NĂ€iteks saab serverit suurendada, saates kĂ€su SHUTDOWN ja taaskĂ€ivitades selle. Kuna Redis'is on aeg tĂ”lgendatud nii, et see jĂ€tkub ka serveri vĂ€ljalĂŒlitamisel, ei ole kĂ”ik meie nĂ”udmised probleemiks. KĂ”ik on korras seni, kuni katkestus toimub korralikult. Aga mis juhtub elektrikatkestuste korral? Kui Redis on vaikimisi seadistatud nii, et fsync sĂŒnkroniseerib kettale iga sekundi jĂ€rel, on vĂ”imalik, et pĂ€rast taaskĂ€ivitamist jÀÀb meie vĂ”tme arvule puudu. Teoreetiliselt, kui me tahame tagada lukustuste turvalisuse igasuguse instantsi taaskĂ€ivitamise korral, peame lubama fsync=always andmesalvestuse seadetes. See tapab tĂ€ielikult jĂ”udluse, viies selle sellisele CP-sĂŒsteemide tasemele, mida traditsiooniliselt kasutatakse turvaliste jaotatud lukustuste rakendamiseks.

Kuid olukord on parem, kui esmapilgul tundub. PĂ”himĂ”tteliselt tagatakse algoritmi turvalisus, kuna pĂ€rast tĂ”rget taaskĂ€ivitatud instants ei osale enam ĂŒheski aktiivses lukustuses.

Selle tagamiseks on vajalik vaid tagada, et instants jÀÀks pĂ€rast tĂ”rget kĂ€ttesaamatuks ajaks, mis veidi ĂŒletab meie kasutatava maksimaalse TTL. Nii ootame kĂ”igi tĂ”rke ajal aktiivsete vĂ”tmete aegumist ja automaatset vabastamist.

Kasutades viivitusega taaskĂ€ivitusi, on pĂ”himĂ”tteliselt vĂ”imalik saavutada turvalisust ka siis, kui Redis ei sĂ€ilita midagi pikaajaliselt. Tuleb mĂ€rkida, et see vĂ”ib siiski pĂ”hjustada kergendusteenuse rikkumise trahvi. NĂ€iteks, kui enamik instantsidest tĂ”rgeb, muutub sĂŒsteem globaalselt kĂ€ttesaamatuks TTL aja jooksul (ja sellel ajal ei saa ĂŒhtegi ressurssi lukustada).

TÔstame algoritmi kÀttesaadavust: pikendame lukustust

Kui klientide töö koosneb vĂ€ikestest etappidest, siis on vĂ”imalik vĂ€hendada vaikimisi mÀÀratud lukustusperioodi aega ja rakendada lukustuste pikendamise mehhanismi. Üldiselt, kui klient on arvutuste tegemisega hĂ”ivatud ja lukustuse kestuse vÀÀrtus ohtlikult vĂ€heneb, vĂ”ib kĂ”ikidele instantsidele saata Lua skripti, mis pikendab vĂ”tme TTL-i, kui vĂ”tme olemasolu jĂ€tkub ja selle vÀÀrtus on endiselt juhuslik, nagu lukustuse soetamise hetkel.

Klient peaks lukustust uuesti omandatuks pidama ainult juhul, kui tal on Ônnestunud enamiku instantside lukustamine lukustuse kehtivuse jooksul.

TÔsi, tehniliselt see algoritm ei muutu, seetÔttu peab lukustuste korduvate katsete maksimaalne arv olema piiratud, vastasel juhul vÔivad kergesti rikkuda kÀttesaadavuse omadused.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster