CodeGym/Kursy Java/All lectures for PL purposes/Jak zaimplementować ACID w aplikacji: praktyka

Jak zaimplementować ACID w aplikacji: praktyka

Dostępny

8.1 Identyfikatory transakcji

Jest oznaczony jako XID lub TxID (jeśli jest różnica, powiedz mi). Znaczniki czasu mogą być używane jako TxID, które mogą przydać się w rozdaniu, jeśli chcemy przywrócić wszystkie akcje do pewnego momentu. Problem może pojawić się, jeśli znacznik czasu nie jest wystarczająco szczegółowy - wtedy transakcje mogą uzyskać ten sam identyfikator.

Dlatego najbardziej niezawodną opcją jest generowanie unikalnych identyfikatorów UUID prod. W Pythonie jest to bardzo proste:

>>> import uuid 
>>> str(uuid.uuid4()) 
'f50ec0b7-f960-400d-91f0-c42a6d44e3d0' 
>>> str(uuid.uuid4()) 
'd15bed89-c0a5-4a72-98d9-5507ea7bc0ba' 

Istnieje również opcja haszowania zestawu danych definiujących transakcję i używania tego skrótu jako TxID.

8.2 Ponowne próby

Jeśli wiemy, że dana funkcja lub program jest idempotentny, oznacza to, że możemy i powinniśmy spróbować powtórzyć jego wywołanie w przypadku błędu. I po prostu musimy być przygotowani na to, że niektóre operacje dadzą błąd - biorąc pod uwagę, że nowoczesne aplikacje są dystrybuowane przez sieć i sprzęt, błąd należy traktować nie jako wyjątek, ale jako normę. Błąd może wystąpić z powodu awarii serwera, błędu sieci, przeciążenia aplikacji zdalnej. Jak powinna zachowywać się nasza aplikacja? Zgadza się, spróbuj powtórzyć operację.

Ponieważ jeden fragment kodu może powiedzieć więcej niż cała strona słów, użyjmy jednego przykładu, aby zrozumieć, jak powinien działać naiwny mechanizm ponawiania prób. Zademonstruję to za pomocą biblioteki Tenacity (jest tak dobrze zaprojektowana, że ​​nawet jeśli nie planujesz jej używać, przykład powinien pokazać, jak zaprojektować mechanizm powtarzania):

import logging
import random
import sys
from tenacity import retry, stop_after_attempt, stop_after_delay, wait_exponential, retry_if_exception_type, before_log

logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
logger = logging.getLogger(__name__)

@retry(
	stop=(stop_after_delay(10) | stop_after_attempt(5)),
	wait=wait_exponential(multiplier=1, min=4, max=10),
	retry=retry_if_exception_type(IOError),
	before=before_log(logger, logging.DEBUG)
)
def do_something_unreliable():
	if random.randint(0, 10) > 1:
    	raise IOError("Broken sauce, everything is hosed!!!111one")
	else:
    	return "Awesome sauce!"

print(do_something_unreliable.retry.statistics)

> Na wszelki wypadek powiem: \@retry(...) to specjalna składnia Pythona zwana „dekoratorem”. Jest to po prostu funkcja retry(...), która zawija inną funkcję i robi coś przed lub po jej wykonaniu.

Jak widać, ponowne próby można zaprojektować kreatywnie:

  • Możesz ograniczyć próby według czasu (10 sekund) lub liczby prób (5).
  • Może być wykładniczy (to znaczy 2 ** jakaś rosnąca liczba n ). lub w inny sposób (na przykład naprawiono), aby wydłużyć czas między oddzielnymi próbami. Wariant wykładniczy nazywa się „załamaniem zatorów”.
  • Możesz spróbować ponownie tylko w przypadku niektórych typów błędów (IOError).
  • Ponowne próby mogą być poprzedzone lub zakończone specjalnymi wpisami w dzienniku.

Teraz, gdy mamy już za sobą kurs młodego wojownika i znamy podstawowe elementy budulcowe, których potrzebujemy do pracy z transakcjami po stronie aplikacji, zapoznajmy się z dwiema metodami, które pozwalają nam realizować transakcje w systemach rozproszonych.

8.3 Zaawansowane narzędzia dla miłośników transakcji

Podam tylko dość ogólne definicje, ponieważ temat ten jest wart osobnego, obszernego artykułu.

Zatwierdzenie dwufazowe (2 szt.) . 2pc ma dwie fazy: fazę przygotowawczą i fazę zatwierdzenia. Podczas fazy przygotowania wszystkie mikrousługi zostaną poproszone o przygotowanie się na pewne zmiany danych, które można wykonać atomowo. Gdy wszystkie będą gotowe, faza zatwierdzania wprowadzi faktyczne zmiany. Do koordynowania procesu potrzebny jest globalny koordynator, który blokuje potrzebne obiekty – czyli stają się one niedostępne dla zmian, dopóki koordynator ich nie odblokuje. Jeśli dana mikrousługa nie jest gotowa na zmiany (np. nie odpowiada), koordynator przerwie transakcję i rozpocznie proces wycofywania zmian.

Dlaczego ten protokół jest dobry? Zapewnia atomowość. Dodatkowo gwarantuje izolację podczas pisania i czytania. Oznacza to, że zmiany w jednej transakcji nie są widoczne dla innych, dopóki koordynator nie zatwierdzi zmian. Ale te właściwości mają również wadę: ponieważ ten protokół jest synchroniczny (blokujący), spowalnia system (pomimo faktu, że samo wywołanie RPC jest dość wolne). I znowu istnieje niebezpieczeństwo wzajemnego blokowania się.

Saga . W tym wzorcu transakcja rozproszona jest wykonywana przez asynchroniczne transakcje lokalne we wszystkich powiązanych mikrousługach. Mikroserwisy komunikują się ze sobą za pośrednictwem magistrali zdarzeń. Jeśli jakakolwiek mikrousługa nie zakończy swojej transakcji lokalnej, inne mikrousługi wykonają transakcje kompensacyjne, aby cofnąć zmiany.

Zaletą Sagi jest to, że żadne obiekty nie są blokowane. Ale są oczywiście wady.

Saga jest trudna do debugowania, zwłaszcza gdy zaangażowanych jest wiele mikrousług. Inną wadą wzorca Saga jest brak izolacji odczytu. Oznacza to, że jeśli właściwości wskazane w ACID są dla nas ważne, to Saga nie jest dla nas odpowiednia.

Co widzimy z opisu tych dwóch technik? Fakt, że w systemach rozproszonych odpowiedzialność za atomowość i izolację spoczywa na aplikacji. To samo dzieje się w przypadku korzystania z baz danych, które nie zapewniają gwarancji ACID. Oznacza to, że rzeczy takie jak rozwiązywanie konfliktów, wycofywanie zmian, zatwierdzanie i zwalnianie miejsca spadają na barki programisty.

8.4 Skąd mam wiedzieć, kiedy potrzebuję gwarancji ACID?

Gdy istnieje duże prawdopodobieństwo, że określony zestaw użytkowników lub procesów będzie jednocześnie pracował na tych samych danych .

Przepraszam za banał, ale typowym przykładem są transakcje finansowe.

Gdy kolejność wykonywania transakcji ma znaczenie.

Wyobraź sobie, że Twoja firma ma zamiar przejść z komunikatora FunnyYellowChat na komunikator FunnyRedChat, ponieważ FunnyRedChat umożliwia wysyłanie gifów, ale FunnyYellowChat nie. Ale nie tylko zmieniasz komunikatora - przenosisz korespondencję swojej firmy z jednego komunikatora do drugiego. Robisz to, ponieważ twoi programiści byli zbyt leniwi, aby dokumentować programy i procesy gdzieś centralnie, a zamiast tego publikowali wszystko różnymi kanałami w komunikatorze. Tak, a Twoi handlowcy publikowali szczegóły negocjacji i uzgodnień w tym samym miejscu. Krótko mówiąc, całe życie Twojej firmy jest tam, a ponieważ nikt nie ma czasu na przekazanie całości do serwisu na dokumentację, a wyszukiwanie komunikatorów działa dobrze, postanowiłeś zamiast sprzątać gruzy po prostu skopiować wszystkie wiadomości do nowej lokalizacji. Kolejność wiadomości jest ważna

Nawiasem mówiąc, w przypadku korespondencji w komunikatorze kolejność jest ogólnie ważna, ale gdy dwie osoby piszą coś na tym samym czacie w tym samym czasie, to generalnie nie jest tak ważne, czyja wiadomość pojawi się pierwsza. Tak więc w tym konkretnym scenariuszu ACID nie byłby potrzebny.

Innym możliwym przykładem jest bioinformatyka. Zupełnie tego nie rozumiem, ale zakładam, że przy rozszyfrowywaniu ludzkiego genomu ważna jest kolejność. Słyszałem jednak, że bioinformatycy na ogół używają niektórych swoich narzędzi do wszystkiego - być może mają własne bazy danych.

Kiedy nie możesz przekazać użytkownikowi lub przetworzyć nieaktualnych danych.

I znowu - transakcje finansowe. Szczerze mówiąc, nie mogłem wymyślić innego przykładu.

Gdy oczekujące transakcje wiążą się ze znacznymi kosztami. Wyobraź sobie problemy, które mogą się pojawić, gdy lekarz i pielęgniarka jednocześnie aktualizują dane pacjenta i wzajemnie usuwają zmiany, ponieważ baza danych nie może wyodrębnić transakcji. System opieki zdrowotnej to kolejny obszar, poza finansami, w którym gwarancje ACID mają zwykle krytyczne znaczenie.

8.5 Kiedy nie potrzebuję ACID?

Gdy użytkownicy aktualizują tylko niektóre swoje prywatne dane.

Na przykład użytkownik zostawia komentarze lub notatki na stronie internetowej. Lub edytuje dane osobowe na koncie osobistym u dostawcy jakichkolwiek usług.

Gdy użytkownicy w ogóle nie aktualizują danych, a jedynie uzupełniają o nowe (append).

Na przykład działająca aplikacja, która zapisuje dane o twoich biegach: ile przebiegłeś, o której godzinie, trasa itp. Każdy nowy przebieg to nowe dane, a stare nie są w ogóle edytowane. Być może na podstawie danych otrzymujesz dane analityczne — i tylko bazy danych NoSQL są dobre w tym scenariuszu.

Gdy logika biznesowa nie określa potrzeby określonej kolejności wykonywania transakcji.

Zapewne dla youtubera, który podczas kolejnej transmisji na żywo zbiera datki na produkcję nowego materiału, nie jest tak ważne, kto, kiedy iw jakiej kolejności rzucił mu pieniądze.

Kiedy użytkownicy pozostaną na tej samej stronie internetowej lub oknie aplikacji przez kilka sekund lub nawet minut, a zatem w jakiś sposób zobaczą nieaktualne dane.

Teoretycznie są to dowolne internetowe media informacyjne lub ten sam Youtube. Lub „Habr”. Gdy nie ma dla Ciebie znaczenia, że ​​niekompletne transakcje mogą być tymczasowo przechowywane w systemie, możesz je zignorować bez żadnych szkód.

Jeśli agregujesz dane z wielu źródeł i to dane, które są aktualizowane z dużą częstotliwością – np. dane o zajętości miejsc parkingowych w mieście, które zmieniają się co najmniej co 5 minut, to w teorii nie będzie to dużym problemem dla Ciebie, jeśli w którymś momencie transakcja za jeden z parkingów nie dojdzie do skutku. Chociaż oczywiście zależy to od tego, co dokładnie chcesz zrobić z tymi danymi.

Komentarze
  • Popularne
  • Najnowsze
  • Najstarsze
Musisz się zalogować, aby dodać komentarz
Ta strona nie ma jeszcze żadnych komentarzy