Įspėjimai, kurie suveikia vieną kartą, o ne kartą kiekviename serveryje
Vienas iš mūsų operacinių įspėjimų — „žemas kreditų likutis“ publikuotai kūrėjo programėlei — turi pasiekti mus daugiausiai kartą per dieną. Vieną popietę jis atėjo penkis kartus. Priežastis: kiekviena mūsų API kopija savo atmintyje prisiminė, kokius įspėjimus jau išsiuntė, todėl du serveriai siuntė tą patį du kartus, o kiekvienas diegimas tą atmintį ištrindavo ir leisdavo jiems siųsti iš naujo. Tas pats modelis lėmė, kad fone dirbantis darbas atlikdavo darbą du kartus. Dabar abu koordinuojasi per bendrą saugyklą, o abu saugiai nutrūksta, jei ta saugykla nepasiekiama.
Simptomas
Mythex siunčia komandai įspėjimus, kai kažkas reikalauja dėmesio: tarnyba nebeveikia, darbas užstrigo, kūrėjo kreditų likutis mažas, kol jo programėlė publikuota. Kiekvienas įspėjimas turi dedupe langą — laikotarpį, per kurį tas pats įspėjimas siunčiamas tik vieną kartą. Žemo kreditų likučio įspėjimui tas langas yra 24 valandos.
Penki to paties įspėjimo egzemplioriai vienoje popietėje reiškė, kad langas neveikė. Dar blogiau — jie ateidavo poromis, maždaug po 11 minučių.
Kodėl tai nutiko
Mūsų įspėjimų paketas laikė dedupe atmintyje: lentelę „įspėjimo raktas → kada paskutinį kartą išsiųstas“ veikiančiame procese. Tai veikia, kai yra vienas procesas, kuris niekada neperkraunamas. Gamyboje nei vieno, nei kito nėra:
- Du API replikai. Gamyboje veikia dvi API kopijos. Kiekviena turėjo savo lentelę, todėl kiekviena išsiuntė įspėjimą vieną kartą. Štai poros.
- Diegimai. Kiekvienas diegimas pakeičia abi replikas naujais procesais su tuščiomis lentelėmis. Po diegimo įspėjimas išsiųstas vėl, tarsi niekada nebūtų buvęs išsiųstas.
Tarpas tarp dviejų įspėjimų kiekvienoje poroje — ~11 minučių — atsirado dėl darbo, kuris sukelia šį įspėjimą. Jis veikia abiejose replikose ir paima užraktą, todėl replikos jį laimi pakaitomis, ir kiekviena, sulaukusi savo eilės, įspėjimą sukeldavo.
Tame pačiame kode buvo ir antra, tylesnė klaida. Kad lentelė nebeaugtų amžinai, ji pati išsivalydavo, kai turėdavo 500 raktų — ištrindavo kiekvieną įrašą senesnį nei 15 minučių, nepaisant jo lango. Didelės apkrovos metu 24 valandų dedupe galėjo trukti ketvirtį valandos.
Pataisymas
Vienas bendras įrašas kiekvienam įspėjimui
Prieš siųsdama įspėjimą, API dabar pažymi to įspėjimo dedupe raktą Redis — bendroje atminties saugykloje, prie kurios prisijungia abi replikos ir kuri neperkraunama, kai perkraunama API. Pažymėjimas yra viena operacija „nustatyk šį raktą tik jei jo nėra ir nustatyk galiojimo laiką pagal langą“. Ta replika, kuri pirma pažymi, išsiunčia įspėjimą; kita, rasdama raktą užimtą, tyli. Naujas procesas po diegimo randa raktą vis dar ten.
Raktas ir langas ateina iš tos pačios taisyklės, kurią naudoja įspėjimų paketas — dabar bendros tarp abiejų sluoksnių, todėl jie negali nesutarti, kas laikoma „tuo pačiu įspėjimu“.
Gedimas atviras, o ne uždaras
Svarbiausias sprendimas — ką daryti, kai Redis nepasiekiamas. Nusprendėme įspėjimą vis tiek išsiųsti. Jei Redis neveikia arba neatsako per 1,5 sekundės, įspėjimas išeina. Dublikatas — triukšmas. Prarastas įspėjimas — kad kažkas nebuvo informuotas.
P0 įspėjimai, patys rimčiausi, dedupe visai nesulaikomi.
Kiekvienas įrašas turi savo langą
Įspėjimų paketo atmintyje dedupe dabar kiekvienas įrašas saugo, kada baigiasi jo langas, o išvalymas pašalina tik tuos įrašus, kurių langas jau pasibaigęs. 24 valandų raktas išlieka 24 valandas, nepriklausomai nuo to, kiek kitų įspėjimų atkeliauja.
Ta pati klaida kitur
Tas pats modelis buvo padvigubinęs ir fono darbą: suderinimo darbą, kuris palaiko mūsų darbo erdvių būsenos įrašą suderintą su smėlio dėžių teikėju. Kiekvienas perėjimas išvardija kiekvieną smėlio dėžę paskyroje — gamyboje tūkstančius, apie penkias sekundes — ir kiekvieną veikiantį ar pristabdytą projektą. Abi replikos jį vykdė kas dvi minutes.
Jo rašymai idempotentiški — jų vykdymas du kartus duoda tą patį rezultatą kaip vieną kartą — todėl nieko nesugedo. Tiesiog darbo buvo dvigubai daugiau. Dabar kiekvienas dviejų minučių langas Redis pažymimas tuo pačiu būdu, kaip mūsų naudojimo matuokliai jau pažymėdavo savo, ir darbą vykdo tik replika, kuri pažymėjo langą. Jei Redis nepasiekiamas, perėjimas vis tiek vykdomas: darbo atlikimas du kartus nekenkia, o jo neatlikti — kenkia.
Kaip tai testavome
Testai atkartoja tikrą sąranką. Jie du kartus įkelia įspėjimų modulį kaip dvi atskiras „replikas“, nukreipia abi į vieną bendrą saugyklą, iš kiekvienos kelia tą patį įspėjimą, tada įkelia trečią naują kopiją, kuri atstovauja „po diegimo“, ir kelia jį dar kartą. Išsiunčiamas tiksliai vienas įspėjimas. Kiti testai tikrina, kad kito kūrėjo įspėjimas vis tiek išeina, kad P0 išsiunčiamas iš abiejų replikų ir kad įspėjimas vis tiek išsiunčiamas, kai saugykla neveikia arba užstringa ilgiau nei laiko riba. Išvalymo pataisai yra atskiras testas: 24 valandų raktas išgyvena, kai po 20 minučių atkeliauja 600 kitų įspėjimų.
Suderinimo darbui — du replikos, tikrinančios tuo pačiu metu, plius trečias taktas tame pačiame lange — duoda vieną sąrašą.
Ką iš to pasiėmėme
- Atmintyje saugoma būsena yra vieno proceso. Viskas, kas turi būti teisinga tarp serverių — „išsiųsta vieną kartą“, „atlikta vieną kartą“ — turi gyventi bendroje vietoje.
- Diegimai yra perkrovimai. Būsena, kuri išlieka tik iki kito perkrovimo, neišlieka įprastą darbo dieną.
- Nuspręskite, kaip elgtis gedimo atveju. Įspėjimams bendros saugyklos nebuvimas turi reikšti daugiau įspėjimų, o ne mažiau.
- Ieškokite modelio, ne tik atvejo. Įspėjimas buvo garsus atvejis; dvigubas suderinimo darbas buvo ta pati klaida, tyliai kainavusi darbą.
Nieko iš to nematyti iš Mythex vidaus. Daugiau apie tai, kaip užtikriname platformos stabilumą, žr. kaip pasiekėme nulinius 502 klaidų diegimo metu ir trūkstantį swap failą. Jei naujokas idėjai, kad veikia daugiau nei viena paslaugos kopija, mūsų vadovas Kas yra serveris? Paprastas vadovas programų kūrėjams pateikia pagrindus.