Implementacja transakcji odpornych na awarie: praktyczne podejście z operacjami idempotentnymi
Transakcje wielofazowe zapewniają integralność danych w przypadku awarii, wykorzystując operacje idempotentne i dziennik stanów. Ta metoda jest odpowiednia dla dowolnych systemów zarządzania bazami danych, w tym MongoDB, i zapobiega utracie danych przy przerwach w dostawie prądu lub błędach systemowych.
Zasada działania transakcji wielofazowej
Transakcja jest dzielona na sekwencyjne etapy, z których każdy jest rejestrowany w osobnej tabeli TRANSACTIONS. Kluczowe statusy:
- blokowanie — blokada kont w celu wykluczenia równoległych operacji.
- zapisywanie salda — zapisywanie początkowych sald w dzienniku.
- główny — wykonanie głównej operacji przelewu środków.
- odblokowanie — odblokowanie kont po zakończeniu.
- zakończony — końcowy stan udanej transakcji.
Idempotentność operacji gwarantuje, że powtórne wykonanie etapu po awarii nie zniekształci danych. Na przykład, operacja A := B + 1 jest idempotentna, a A := A + 1 — nie, ale można ją rozłożyć na dwie idempotentne.
Etapy implementacji z przykładami kodu
Rozważmy przelew 250 tenge z konta „Assange” na konto „Agenta Y”. Początkowe dane w tabeli ACCOUNTS:
| ID | Nazwa | SALDO, ₸ | TR_ID |
|----|---------|----------|-------|
| 34 | Assange | 300 | null |
| 78 | Agent Y | 7 | null |
1. Blokowanie kont (STATUS='blocking')
Najpierw tworzony jest wpis w TRANSACTIONS:
| ID | FROM_ID | TO_ID | AMOUNT, ₸ | FROM_BALANCE | TO_BALANCE | STATUS |
|----|---------|-------|-----------|--------------|------------|----------|
| 172| 34 | 78 | 250 | null | null | blocking |
Blokowanie jest wykonywane za pomocą atomowych komend, na przykład w MongoDB:
db.accounts.updateOne(
{ ID: 34, TR_ID: null },
{ $set: { TR_ID: 172 } }
);
Jeśli komenda nie zmienia rekordu, blokowanie nie powiodło się — wymagane jest powtórzenie lub anulowanie. Aby uniknąć wzajemnych blokad, identyfikatory są sortowane przed przetworzeniem.
2. Zapisywanie sald (STATUS='save_balance')
Po blokowaniu kopiowane są początkowe salda do TRANSACTIONS — jest to operacja idempotentna:
db.transactions.updateOne(
{ ID: 172 },
{ $set: { FROM_BALANCE: 300, TO_BALANCE: 7, STATUS: 'save_balance' } }
);
3. Wykonanie przelewu (STATUS='main')
Główna operacja wykorzystuje zapisane salda dla idempotentności:
db.accounts.updateOne(
{ ID: 34, TR_ID: 172 },
{ $set: { BALANCE: 50 } } // 300 - 250
);
db.accounts.updateOne(
{ ID: 78, TR_ID: 172 },
{ $set: { BALANCE: 257 } } // 7 + 250
);
Status jest aktualizowany na 'unlock' tylko po pomyślnym zastosowaniu zmian.
4. Odblokowanie i zakończenie (STATUS='unlock')
Konta są odblokowywane z weryfikacją TR_ID:
db.accounts.updateOne(
{ ID: 34, TR_ID: 172 },
{ $set: { TR_ID: null } }
);
Po odblokowaniu obu kont status zmienia się na 'finished'.
Zalety i ograniczenia podejścia
Co jest ważne:
- Odporność na awarie: system odzyskuje się po wszelkich awariach bez utraty danych.
- Idempotentność: operacje można bezpiecznie powtarzać, co jest kluczowe dla systemów rozproszonych.
- Uniwersalność: metoda jest zastosowalna do różnych SZBD, nie tylko do MongoDB.
- Spójność: gwarantowana jest integralność danych na wszystkich etapach transakcji.
- Wydajność: wymaga dodatkowych wpisów w dzienniku, co może spowolnić operacje.
Ograniczenia obejmują zwiększoną złożoność implementacji i koszty utrzymania dziennika TRANSACTIONS. Jednak dla systemów finansowych lub krytycznych aplikacji te kompromisy są uzasadnione.
Zalecenia dla deweloperów
- Używaj atomowych operacji SZBD do blokowania i aktualizacji.
- Zaimplementuj mechanizm powtórzeń przy nieudanych blokowaniach.
- Testuj scenariusze awarii, w tym nagłe zatrzymania procesów.
- Optymalizuj sortowanie identyfikatorów, aby minimalizować zakleszczenia.
To podejście zapewnia niezawodność porównywalną z klasycznymi dwufazowymi zatwierdzeniami, ale z większą elastycznością dla systemów NoSQL.
— Editorial Team
Brak komentarzy.