Redis istifadə edərək paylanmış kilidləmə

Hey Habr!

Bu gün biz Redis-dən istifadə edərək paylanmış kilidləmənin həyata keçirilməsi haqqında mürəkkəb məqalənin tərcüməsini diqqətinizə çatdırırıq və sizi Redis-in perspektivləri haqqında bir mövzu kimi danışmağa dəvət edirik. Kitabın müəllifi Martin Kleppmanndan sözügedən Redlock alqoritminin təhlili "Yüksək Yük Tətbiqləri", verilmişdir burada.

Paylanmış kilidləmə, müxtəlif proseslərin paylaşılan resurslar üzərində qarşılıqlı eksklüziv şəkildə işləməli olduğu bir çox mühitlərdə istifadə olunan çox faydalı primitivdir.

Redis-dən istifadə edərək DLM-in (Paylanmış Kilid Meneceri) necə tətbiq olunacağını təsvir edən bir sıra kitabxanalar və yazılar var, lakin hər bir kitabxana fərqli yanaşma tətbiq edir və onların təmin etdiyi zəmanətlər bir az daha mürəkkəb dizaynla əldə edilə bilənlərlə müqayisədə olduqca zəifdir.

Bu yazıda Redis-dən istifadə edərək paylanmış kilidləmənin necə həyata keçiriləcəyini nümayiş etdirən şərti kanonik alqoritmi təsvir etməyə çalışacağıq. adlı bir alqoritm haqqında danışacağıq Redlock, paylanmış kilid meneceri tətbiq edir və fikrimizcə, bu alqoritm adi tək instansiya yanaşmasından daha təhlükəsizdir. Ümid edirik ki, icma onu təhlil edəcək, rəy bildirəcək və ondan daha mürəkkəb və ya alternativ layihələr üçün başlanğıc nöqtəsi kimi istifadə edəcək.

Tətbiq

Alqoritmin təsvirinə keçməzdən əvvəl biz hazır tətbiqlərə bir neçə keçid veririk. Onlar istinad üçün istifadə edilə bilər.

Təhlükəsizlik və Əlçatımlılıq Zəmanətləri

Biz dizaynımızı paylanmış kilidləmədən səmərəli istifadə etmək üçün lazım olan minimum zəmanətləri təmin edən üç xüsusiyyətlə modelləşdirəcəyik.

  1. Təhlükəsizlik mülkiyyəti: Qarşılıqlı istisna. İstənilən vaxt yalnız bir müştəri kilidi saxlaya bilər.
  2. Mövcudluq Əmlakı A: Çıxış yoxdur. Resursu kilidləyən müştəri uğursuz olsa və ya başqa disk seqmentinə düşsə belə, nəticədə kilid əldə etmək həmişə mümkündür.
  3. Mövcudluq xüsusiyyəti B: Xətalara Dözümlülük. Redis qovşaqlarının əksəriyyəti işlədiyi müddətdə müştərilər kilidləri əldə edə və buraxa bilərlər.

Niyə uğursuzluğun bərpasına əsaslanan tətbiq bu vəziyyətdə kifayət deyil
Nəyi təkmilləşdirəcəyimizi başa düşmək üçün Redis-ə əsaslanan ən çox yayılmış kilidləmə kitabxanaları ilə işlərin cari vəziyyətini təhlil edək.

Redis-dən istifadə edərək resursu kilidləməyin ən sadə yolu misalda açar yaratmaqdır. Tipik olaraq, açar məhdud istifadə müddəti ilə yaradılır, buna Redis-də təqdim olunan müddəti bitmək xüsusiyyətindən istifadə etməklə nail olunur, ona görə də gec-tez bu açar buraxılır (siyahımızda 2-ci xüsusiyyət). Müştəri resursu buraxmalı olduqda, açarı silir.

İlk baxışdan bu həll olduqca yaxşı işləyir, lakin bir problem var: bizim arxitekturamız tək bir uğursuzluq nöqtəsi yaradır. Host Redis nümunəsi uğursuz olarsa nə baş verir? O zaman bir qul əlavə edək! Aparıcı əlçatmaz olarsa, ondan istifadə edəcəyik. Təəssüf ki, bu seçim mümkün deyil. Bununla biz təhlükəsizliyi təmin etmək üçün lazım olan qarşılıqlı istisna xassəsini düzgün həyata keçirə bilməyəcəyik, çünki Redis-də replikasiya asinxrondur.

Aydındır ki, belə bir modeldə yarış vəziyyəti baş verir:

  1. Müştəri A master üzərində bir kilid əldə edir.
  2. Açar girişi köləyə keçməzdən əvvəl master uğursuz olur.
  3. İzləyici liderliyə yüksəlir.
  4. Müştəri B A-nın artıq kilidlədiyi eyni resursda kilid əldə edir. TƏHLÜKƏSİZLİK POZUNDU!

Bəzən nasazlıq kimi xüsusi hallarda bir çox müştərinin kilidi eyni anda saxlaya bilməsi tamamilə normaldır. Belə hallarda replikasiyaya əsaslanan həll tətbiq oluna bilər. Əks təqdirdə, bu məqalədə təsvir olunan həlli tövsiyə edirik.

Tək bir nümunə ilə düzgün icra

Yuxarıda təsvir edilən tək instansiya konfiqurasiyasının çatışmazlıqlarını aradan qaldırmağa cəhd etməzdən əvvəl gəlin bu sadə işi necə düzgün idarə edəcəyimizi anlayaq, çünki bu həll həqiqətən yarış şəraitinin zaman-zaman məqbul olduğu tətbiqlərdə etibarlıdır, eləcə də yarışın bloklanması səbəbindən. tək instansiya burada təsvir edilən paylanmış alqoritmdə istifadə olunan əsas kimi xidmət edir.

Kilid əldə etmək üçün bunu edin:

SET resource_name my_random_value NX PX 30000

Bu əmr açarı yalnız mövcud olmadıqda quraşdırır (NX seçimi), etibarlılıq müddəti 30000 millisaniyə (PX seçimi). Açar " olaraq təyin edilibmyrandomvalue" Bu dəyər bütün müştərilər və bütün kilid sorğuları arasında unikal olmalıdır.
Əsasən, kilidi təhlükəsiz şəkildə buraxmaq üçün təsadüfi bir dəyər istifadə olunur, bir skript Redis-ə deyir: açarı yalnız mövcud olduqda çıxarın və orada saxlanılan dəyər gözlənilən kimidir. Bu, aşağıdakı Lua skriptindən istifadə etməklə əldə edilir:

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

Bu, başqa bir müştəri tərəfindən saxlanılan kilidin çıxarılmasının qarşısını almaq üçün vacibdir. Məsələn, müştəri bir kilid əldə edə bilər, sonra ilk kiliddən daha uzun müddət davam edən bəzi əməliyyatlarda kilidlənə bilər (belə ki, açarın istifadə müddəti bitsin) və daha sonra başqa bir müştərinin yerləşdirdiyi kilidi çıxarın.
Sadə DEL-dən istifadə etmək təhlükəlidir, çünki müştəri başqa müştərinin saxladığı kilidi çıxara bilər. Bunun əksinə olaraq, yuxarıdakı skriptdən istifadə edərkən, hər bir kilid təsadüfi bir sətirlə "imzalanır", buna görə də yalnız onu əvvəllər yerləşdirmiş müştəri onu silə bilər.

Bu təsadüfi sətir nə olmalıdır? Düşünürəm ki, bu /dev/urandom-dan 20 bayt olmalıdır, lakin siz öz məqsədləriniz üçün sətri kifayət qədər unikal etmək üçün daha ucuz yollar tapa bilərsiniz. Məsələn, RC4-ü /dev/urandom ilə səpmək və ondan psevdo-təsadüfi axın yaratmaq yaxşı olardı. Daha sadə həll mikrosaniyəlik ayırdetmədə unix vaxtının və müştəri ID-sinin birləşməsini əhatə edir; o qədər də təhlükəsiz deyil, lakin çox güman ki, əksər kontekstlərdə tapşırığa uyğundur.

Açarın ömrünün ölçüsü kimi istifadə etdiyimiz vaxta "kilid ömrü" deyilir. Bu dəyər həm kilidin avtomatik buraxılmasından əvvəl keçən vaxtın miqdarıdır, həm də müştərinin digər müştəri öz növbəsində qarşılıqlı istisna təminatlarını pozmadan həmin resursu kilidləməzdən əvvəl əməliyyatı tamamlamalı olduğu vaxtdır. Bu zəmanət yalnız kilidin alındığı andan başlayan müəyyən bir zaman pəncərəsi ilə məhdudlaşır.

Beləliklə, kilidi əldə etmək və buraxmaq üçün yaxşı bir yolu müzakirə etdik. Sistem (əgər biz tək və həmişə mövcud olan nümunədən ibarət paylanmamış sistemdən danışırıqsa) təhlükəsizdir. Gəlin bu konsepsiyanı paylanmış sistemə qədər genişləndirək, burada bizim belə təminatlarımız yoxdur.

Redlock alqoritmi

Alqoritmin paylanmış versiyası bizim N Redis ustamızın olduğunu güman edir. Bu qovşaqlar bir-birindən tamamilə müstəqildir, ona görə də biz replikasiyadan və ya hər hansı digər gizli koordinasiya sistemindən istifadə etmirik. Biz artıq bir instansiyada kilidi necə təhlükəsiz əldə etməyi və buraxmağı əhatə etdik. Biz alqoritmin bir instansiya ilə işləyərkən məhz bu üsuldan istifadə edəcəyini təbii qəbul edirik. Nümunələrimizdə N-i 5-ə təyin etdik, bu məqbul bir dəyərdir. Beləliklə, müxtəlif kompüterlərdə və ya virtual maşınlarda 5 Redis master-dan istifadə etməli olacağıq ki, onların bir-birindən böyük ölçüdə müstəqil hərəkət etməsini təmin edək.

Kilid əldə etmək üçün müştəri aşağıdakı əməliyyatları yerinə yetirir:

  1. Cari vaxtı millisaniyələrlə alır.
  2. Ardıcıl olaraq bütün hallarda eyni açar adından və təsadüfi dəyərlərdən istifadə edərək bütün N nümunələrində kilid əldə etməyə çalışır. Mərhələ 2-də müştəri hər bir nümunə əsasında kilid qurduqda, müştəri onu əldə etmək üçün kilidin avtomatik buraxıldığı vaxtla müqayisədə kifayət qədər qısa olan gecikmədən istifadə edir. Məsələn, bloklama müddəti 10 saniyədirsə, gecikmə ~5-50 millisaniyə diapazonunda ola bilər. Bu, uğursuz Redis node-a çatmağa çalışan müştərinin uzun müddət bloklanmış vəziyyətdə qala biləcəyi vəziyyəti aradan qaldırır: nümunə əlçatan deyilsə, biz mümkün qədər tez başqa bir instansiyaya qoşulmağa çalışırıq.
  3. Kilidi götürmək üçün müştəri nə qədər vaxt keçdiyini hesablayır; Bunu etmək üçün o, faktiki vaxt dəyərindən 1-ci addımda əldə edilmiş vaxt damğasını çıxarır. Müştəri əksər hallarda (ən azı 3) kilidi əldə edə bilsəydi və bunun üçün lazım olan ümumi vaxtı çıxarır. kilidi əldə etmək, kilidləmə müddətindən daha az, kilid əldə edilmiş sayılır.
  4. Əgər kilid əldə edilibsə, kilidin müddəti 3-cü addımda hesablanmış keçmiş vaxtdan çıxılmaqla orijinal kilid müddəti kimi qəbul edilir.
  5. Müştəri hansısa səbəbdən kilidi əldə edə bilmirsə (ya N/2+1 instansiyalarını kilidləyə bilməyib, ya da kilidin müddəti mənfi olub), onda o, bütün instansiyaları (hətta blok edə bilməyəcəyini düşündüklərini də) açmağa çalışacaq. ).

Alqoritm asinxrondurmu?

Bu alqoritm belə bir fərziyyəyə əsaslanır ki, bütün proseslərin işləyəcəyi sinxronlaşdırılmış saat olmasa da, hər bir prosesdə yerli vaxt hələ də təxminən eyni sürətlə axır və xətanın kilidin bağlandığı ümumi vaxtla müqayisədə kiçikdir. avtomatik buraxılır. Bu fərziyyə adi kompüterlər üçün xarakterik olan vəziyyətə çox bənzəyir: hər bir kompüterin yerli saatı var və biz adətən müxtəlif kompüterlər arasında vaxt fərqinin kiçik olmasına arxalana bilərik.

Bu nöqtədə, biz qarşılıqlı istisna qaydamızı daha diqqətlə tərtib etməliyik: qarşılıqlı istisna yalnız kilidi tutan müştəri kilidin etibarlı olduğu müddətdə (bu dəyər 3-cü addımda əldə edilir) çıxdıqda, mənfi bir müddət daha (cəmi bir neçə dəfə) təmin edilir. proseslər arasındakı vaxt fərqini kompensasiya etmək üçün millisaniyələr).

Aşağıdakı maraqlı məqalədə vaxt intervallarının koordinasiyasını tələb edən sistemlər haqqında daha çox məlumat verilir: İcarəyə verilir: paylanmış fayl keşinin ardıcıllığı üçün səmərəli nasazlığa dözümlü mexanizm.

Uğursuzluqla yenidən cəhd edin

Müştəri kilid əldə edə bilmədikdə, təsadüfi gecikmədən sonra yenidən cəhd etməlidir; bu, eyni resursda eyni vaxtda kilid əldə etməyə çalışan bir neçə müştərinin sinxronizasiyasını aradan qaldırmaq üçün edilir (bu, qaliblərin olmadığı "bölünmüş beyin" vəziyyətinə gətirib çıxara bilər). Əlavə olaraq, müştəri Redis instansiyalarının əksəriyyətində kilid əldə etməyə nə qədər tez cəhd etsə, bölünmüş beyin vəziyyətinin baş verə biləcəyi pəncərə bir o qədər dar olar (və təkrar cəhdlərə ehtiyac o qədər az olar). Buna görə də, ideal olaraq, müştəri multipleksdən istifadə edərək eyni vaxtda N instansiyaya SET əmrlərini göndərməyə çalışmalıdır.

Kilidlərin əksəriyyətini əldə edə bilməyən müştərilər üçün (qismən) əldə edilmiş kilidləri buraxmağın nə qədər vacib olduğunu burada vurğulamağa dəyər ki, onlar resursdakı kilidi yenidən əldə etməzdən əvvəl açarın müddətinin bitməsini gözləməli olmasınlar. (baxmayaraq ki, şəbəkə parçalanması baş verərsə və müştəri Redis instansiyaları ilə əlaqəni itirirsə, açarın müddətinin bitməsini gözləyərkən əlçatanlıq üçün cərimə ödəməlisiniz).

Kilidi buraxın

Kilidin buraxılması sadə əməliyyatdır və müştərinin müəyyən bir nümunəni uğurla kilidləyib-bilməməsindən asılı olmayaraq, sadəcə olaraq bütün instansiyaların kilidini açmağı tələb edir.

Təhlükəsizlik Mülahizələri

Alqoritm təhlükəsizdirmi? Fərqli ssenarilərdə nə baş verdiyini təsəvvür etməyə çalışaq.

Başlamaq üçün, tutaq ki, müştəri əksər hallarda kilid əldə edə bildi. Hər bir nümunədə hamı üçün eyni ömür müddəti olan bir açar olacaq. Lakin bu açarların hər biri fərqli vaxtda quraşdırılıb, ona görə də onların müddəti müxtəlif vaxtlarda bitəcək. Ancaq ilk açar T1-dən pis olmayan bir zamanda (ilk serverlə əlaqə saxlamazdan əvvəl seçdiyimiz vaxt) quraşdırılıbsa və sonuncu açar T2-dən daha pis olmayan bir zamanda quraşdırılıbsa (cavabın alındığı vaxt). sonuncu serverdən), onda biz əminik ki, dəstdə müddəti bitən ilk açar ən azı sağ qalacaq. MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT. Bütün digər açarların müddəti daha sonra başa çatacaq, ona görə də əmin ola bilərik ki, bütün açarlar ən azı bu dəfə eyni vaxtda etibarlı olacaq.

Əksər açarların etibarlı olduğu müddət ərzində başqa müştəri kilidi əldə edə bilməyəcək, çünki N/2+1 düymələri artıq mövcud olduqda N/2+1 SET NX əməliyyatları uğur qazana bilməz. Buna görə də, bir kilid əldə edildikdən sonra, onu eyni anda yenidən əldə etmək mümkün deyil (bu, qarşılıqlı istisna mülkiyyətini pozar).
Bununla belə, biz əmin olmaq istəyirik ki, eyni anda kilid əldə etməyə çalışan bir neçə müştəri eyni anda uğur qazana bilməz.

Müştəri nümunələrin əksəriyyətini maksimum kilidləmə müddətinə təxminən və ya daha çox müddətə kilidləyibsə, o, kilidi etibarsız hesab edəcək və nümunələrin kilidini açacaq. Buna görə də, biz yalnız müştərinin istifadə müddətinin bitmə tarixindən az bir zamanda əksər hallarda bloklamağı bacardığı halı nəzərə almalıyıq. Bu halda, yuxarıdakı arqumentlə əlaqədar olaraq, müddət ərzində MIN_VALIDITY heç bir müştəri kilidi yenidən əldə edə bilməməlidir. Buna görə də, bir çox müştərilər N/2+1 instansiyalarını eyni vaxtda (2-ci mərhələnin sonunda başa çatır) yalnız çoxluğun kilidləmə vaxtı TTL vaxtından çox olduqda kilidləyə biləcək, bu da kilidi etibarsız edir.

Təhlükəsizliyin rəsmi sübutunu təqdim edə, mövcud oxşar alqoritmləri göstərə və ya yuxarıda bir səhv tapa bilərsinizmi?

Əlçatanlıq Mülahizələri

Sistemin mövcudluğu üç əsas xüsusiyyətdən asılıdır:

  1. Kilidləri avtomatik buraxın (açarların müddəti bitdikcə): Açarlar nəhayət kilidlər üçün istifadə olunmaq üçün yenidən əlçatan olacaq.
  2. İstənilən kilid alınmadıqda və ya alındıqda və iş başa çatdıqda müştərilərin adətən qıfılları çıxararaq bir-birinə kömək etmələri; ona görə də çox güman ki, kilidi yenidən əldə etmək üçün açarların müddətinin bitməsini gözləməli olmayacaqıq.
  3. Fakt budur ki, müştəri kilid əldə etmək üçün yenidən cəhd etməli olduqda, əksər kilidləri əldə etmək üçün tələb olunan müddətdən nisbətən daha uzun müddət gözləyir. Bu, resurslar üçün rəqabət apararkən beyinə bölünmüş vəziyyətin yaranma ehtimalını azaldır.

Bununla belə, şəbəkə seqmentlərinin TTL-nə bərabər əlçatanlıq cəzası var, ona görə də bitişik seqmentlər varsa, cəza qeyri-müəyyən ola bilər. Bu, müştəri kilid əldə etdikdən sonra onu buraxmadan başqa bir seqmentə keçəndə baş verir.

Prinsipcə, sonsuz bitişik şəbəkə seqmentləri nəzərə alınmaqla, sistem sonsuz müddət ərzində əlçatmaz qala bilər.

Performans, uğursuzluq və fsync

Bir çox insanlar Redis-dən istifadə edirlər, çünki kilidləri əldə etmək və buraxmaq üçün tələb olunan gecikmə və saniyədə tamamlana bilən əldəetmələrin/buraxılışların sayı baxımından yüksək kilid server performansına ehtiyac duyurlar. Bu tələbi yerinə yetirmək üçün gecikməni azaltmaq üçün N Redis serverləri ilə əlaqə qurmaq strategiyası mövcuddur. Bu, multipleksləşdirmə strategiyasıdır (və ya "kasıbın multipleksasiyası", burada rozetka bloklanmayan rejimə qoyulur, bütün əmrləri göndərir və müştəri ilə hər bir nümunə arasında gediş-gəliş vaxtının oxşar olduğunu fərz edərək əmrləri daha sonra oxuyur) .

Bununla belə, uğursuzluqlardan etibarlı şəkildə bərpa olunan bir model yaratmağa çalışırıqsa, uzunmüddətli məlumatların saxlanması ilə bağlı nəzərə alınmalıdır.

Əsasən, məsələyə aydınlıq gətirmək üçün tutaq ki, biz Redis-i heç bir uzunmüddətli məlumat saxlama olmadan konfiqurasiya edirik. Müştəri 3 instansiyadan 5-nü bloklamağı bacarır. Müştərinin bloklaya bildiyi instansiyalardan biri yenidən işə salınır və bu anda eyni resurs üçün yenidən 3 instansiya mövcuddur ki, biz onları bloklaya bilərik və başqa bir müştəri də öz növbəsində təhlükəsizlik xüsusiyyətini pozaraq yenidən işə salınmış nümunəni bloklaya bilər. qıfılların eksklüzivliyini qəbul edir.

Əgər məlumatı qabaqcadan (AOF) aktiv etsəniz, vəziyyət bir qədər yaxşılaşacaq. Məsələn, siz SHUTDOWN əmrini göndərərək və onu yenidən işə salmaqla serveri təşviq edə bilərsiniz. Redis-də sona çatma əməliyyatları semantik olaraq elə həyata keçirildiyindən, hətta server söndürüldükdə də vaxt axmağa davam edir, bütün tələblərimiz qaydasındadır. Normal bağlanma təmin edildiyi müddətcə bu normaldır. Elektrik kəsilməsi halında nə etməli? Redis standart olaraq, hər saniyə diskdə fsync sinxronizasiyası ilə konfiqurasiya edilibsə, o zaman yenidən başladıqdan sonra açarımız olmaya bilər. Teorik olaraq, hər hansı bir nümunənin yenidən başlaması zamanı kilid təhlükəsizliyinə zəmanət vermək istəyiriksə, aktivləşdirməliyik fsync=always uzunmüddətli məlumatların saxlanması üçün parametrlərdə. Bu, paylanmış kilidləri etibarlı şəkildə həyata keçirmək üçün ənənəvi olaraq istifadə olunan CP sistemlərinin səviyyəsinə qədər performansı tamamilə öldürəcək.

Amma vəziyyət ilk baxışdan göründüyündən yaxşıdır. Prinsipcə, alqoritmin təhlükəsizliyi qorunur, çünki nasazlıqdan sonra nümunə yenidən işə salındıqda, o, artıq hazırda aktiv olan heç bir kiliddə iştirak etmir.

Bunu təmin etmək üçün, sadəcə olaraq, nasazlıqdan sonra nümunənin istifadə etdiyimiz maksimum TTL-dən bir qədər artıq müddət ərzində əlçatmaz qalmasını təmin etməliyik. Beləliklə, uğursuzluq zamanı aktiv olan bütün açarların son istifadə tarixini və avtomatik buraxılmasını gözləyəcəyik.

Gecikmiş yenidən başlamalardan istifadə edərək, Redis-də uzunmüddətli davamlılıq olmadıqda belə təhlükəsizliyə nail olmaq prinsipcə mümkündür. Bununla belə, nəzərə alın ki, bu, əlçatanlığın pozulmasına görə cərimə ilə nəticələnə bilər. Məsələn, nümunələrin əksəriyyəti uğursuz olarsa, sistem TTL üçün qlobal olaraq əlçatmaz olacaq (və bu müddət ərzində heç bir resurs bloklana bilməz).

Alqoritmin mövcudluğunu artırırıq: bloklamağı genişləndiririk

Müştərilər tərəfindən görülən işlər kiçik addımlardan ibarətdirsə, standart kilidləmə müddətini azaltmaq və kilidlərin uzadılması mexanizmini həyata keçirmək mümkündür. Prinsipcə, əgər müştəri hesablama işləri ilə məşğuldursa və kilidin bitmə dəyəri təhlükəli dərəcədə aşağıdırsa, açar hələ də mövcuddursa və onun dəyəri hələ də təsadüfi bir dəyərdirsə, açarın TTL-ni genişləndirən bütün instansiyalara Lua skripti göndərə bilərsiniz. kilid alınıb.

Müştəri yalnız etibarlılıq müddəti ərzində instansiyaların əksəriyyətini kilidləməyi bacarıbsa, yenidən əldə ediləcək kilidi nəzərdən keçirməlidir.

Düzdür, texniki alqoritm dəyişmir, buna görə də kilidləri əldə etmək üçün təkrarlanan cəhdlərin maksimum sayı məhdudlaşdırılmalıdır, əks halda əlçatanlıq xüsusiyyətləri pozulacaq.

Mənbə: www.habr.com

Добавить комментарий