8.1 Идентификационни номера на транзакции
Означава се като XID or TxID (ако има разлика, кажете ми). Времевите клейма могат да се използват като TxID, което може да изиграе голяма роля, ако искаме да възстановим всички действия до няHowъв момент във времето. Проблемът може да възникне, ако клеймото за време не е достатъчно детайлно - тогава транзакциите могат да получат същия идентификатор.
Следователно най-надеждният вариант е да се генерират уникални UUID prod IDs. В Python това е много лесно:
>>> import uuid
>>> str(uuid.uuid4())
'f50ec0b7-f960-400d-91f0-c42a6d44e3d0'
>>> str(uuid.uuid4())
'd15bed89-c0a5-4a72-98d9-5507ea7bc0ba'
Има също опция за хеширане на набор от данни, дефиниращи транзакция, и използване на този хеш като TxID.
8.2 Повторни опити
Ако знаем, че дадена функция or програма е идемпотентна, това означава, че можем и трябва да опитаме да повторим нейното извикване в случай на грешка. И просто трябва да сме подготвени за факта, че няHowва операция ще даде грешка - като се има предвид, че съвременните applications се разпространяват в мрежата и хардуера, грешката трябва да се разглежда не като изключение, а като норма. Грешката може да възникне поради срив на сървъра, мрежова грешка, задръстване на отдалечено приложение. Как трябва да се държи нашето приложение? Точно така, опитайте да повторите операцията.
Тъй като едно парче code може да каже повече от цяла page с думи, нека използваме един пример, за да разберем How в идеалния случай трябва да работи наивният механизъм за повторен опит. Ще демонстрирам това с помощта на библиотеката Tenacity (тя е толкова добре проектирана, че дори и да не планирате да я използвате, примерът трябва да ви покаже How можете да проектирате механизма за повторение):
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)
> За всеки случай ще кажа: \@retry(...) е специален синтаксис на Python, наречен "декоратор". Това е просто функция retry(...), която обгръща друга функция и прави нещо преди or след нейното изпълнение.
Както виждаме, повторните опити могат да бъдат проектирани креативно:
- Можете да ограничите опитите по време (10 секунди) or брой опити (5).
- Може да бъде експоненциално (т.е. 2 ** няHowво нарастващо число n). or по няHowъв друг начин (например фиксирано), за да се увеличи времето между отделните опити. Експоненциалният вариант се нарича "колапс на задръстванията".
- Можете да опитате отново само за определени видове грешки (IOError).
- Опитите за повторен опит могат да бъдат предшествани or завършени от някои специални записи в дневника.
Сега, след като завършихме курса за млад боец и знаем основните градивни елементи, от които се нуждаем, за да работим с транзакции от страна на приложението, нека се запознаем с два метода, които ни позволяват да внедряваме транзакции в разпределени системи.
8.3 Разширени инструменти за любителите на транзакциите
Ще дам само доста общи определения, тъй като тази тема е достойна за отделна голяма статия.
Двуфазен ангажимент (2 бр.) . 2pc има две фази: фаза на подготовка и фаза на ангажиране. По време на фазата на подготовка всички микроуслуги ще бъдат помолени да се подготвят за някои промени в данните, които могат да бъдат напequalsи атомарно. След като всички те са готови, фазата на ангажиране ще направи действителните промени. За координиране на процеса е необходим глобален координатор, който заключва необходимите обекти - тоест те стават недостъпни за промени, докато координаторът не ги отключи. Ако конкретна микроуслуга не е готова за промени (например не отговаря), координаторът ще прекрати транзакцията и ще започне процеса на връщане назад.
Защо този протокол е добър? Осигурява атомарност. Освен това гарантира изолация при писане и четене. Това означава, че промените в една транзакция не са видими за другите, докато координаторът не извърши промените. Но тези свойства имат и недостатък: тъй като този протокол е синхронен (блокиращ), той забавя системата (въпреки факта, че самото RPC повикване е доста бавно). И пак има опасност от взаимно блокиране.
Сага . В този модел разпределена транзакция се изпълнява от асинхронни локални транзакции във всички свързани микроуслуги. Микроуслугите комуникират помежду си чрез автобус за събития. Ако някоя микроуслуга не успее да завърши своята локална транзакция, други микроуслуги ще извършат компенсиращи транзакции, за да отменят промените.
Предимството на Saga е, че няма блокирани обекти. Но има, разбира се, и недостатъци.
Saga е трудна за отстраняване на грешки, особено когато има много включени микроуслуги. Друг недостатък на модела Saga е, че му липсва изолация за четене. Тоест, ако свойствата, посочени в ACID, са важни за нас, тогава Saga не е много подходящ за нас.
Какво виждаме от описанието на тези две техники? Фактът, че в разпределените системи отговорността за атомарността и изолацията се носи от приложението. Същото се случва при използване на бази данни, които не предоставят гаранции на ACID. Тоест неща като разрешаване на конфликти, връщане назад, ангажименти и освобождаване на пространство падат върху плещите на разработчика.
8.4 Как да разбера кога имам нужда от гаранции на ACID?
Когато има голяма вероятност определен набор от потребители or процеси да работят едновременно с едни и същи данни .
Извинете за простащината, но типичен пример са финансовите транзакции.
Когато редът, в който се изпълняват транзакциите, има meaning.
Представете си, че вашата компания е на път да премине от месинджъра FunnyYellowChat към месинджъра FunnyRedChat, защото FunnyRedChat ви позволява да изпращате gifs, но FunnyYellowChat не може. Но вие не просто променяте месинджъра - вие прехвърляте кореспонденцията на вашата компания от един месинджър в друг. Вие правите това, защото вашите програмисти са бor твърде мързеливи да documentират програми и процеси някъде централно и instead of това са публикували всичко в различни канали в месинджъра. Да, и вашите търговци публикуваха подробностите за преговорите и споразуменията на същото място. Накратко, целият живот на вашата компания е там и тъй като никой няма време да прехвърли всичко на услуга за documentиране, а търсенето на месинджъри работи добре, вие решихте instead of да разчиствате руините просто да копирате всички съобщения до ново местоположение. Редът на съобщенията е важен
Между другото, за кореспонденция в месинджър редът обикновено е важен, но когато двама души пишат нещо в един и същ чат едновременно, тогава като цяло не е толкова важно чие съобщение ще се появи първо. Така че за този конкретен сценарий ACID няма да е необходим.
Друг възможен пример е биоинформатиката. Изобщо не разбирам това, но предполагам, че редът е важен при дешифрирането на човешкия геном. Чух обаче, че биоинформатиците обикновено използват някои от своите инструменти за всичко - може би имат свои собствени бази данни.
Когато не можете да дадете на потребител or да обработите остарели данни.
И отново - финансови транзакции. Честно казано, не можах да се сетя за друг пример.
Когато предстоящите транзакции са свързани със значителни разходи. Представете си проблемите, които могат да възникнат, когато лекар и медицинска сестра едновременно актуализират досие на пациента и изтрият промените на другия, тъй като базата данни не може да изолира транзакциите. Здравната система е друга област, освен финансите, където гаранциите на ACID обикновено са критични.
8.5 Кога нямам нужда от ACID?
Когато потребителите актуализират само част от личните си данни.
Например, потребител оставя коментари or лепкави бележки на уеб page. Или редактира лични данни в личен акаунт при доставчик на всяHowви услуги.
Когато потребителите изобщо не актуализират данни, а само допълват с нови (append).
Например приложение за бягане, което запазва данни за вашите бягания: колко сте бягали, за колко време, маршрут и т.н. Всяко ново изпълнение е нови данни, а старите изобщо не се редактират. Може би въз основа на данните получавате анализи - и само базите данни NoSQL са добри за този сценарий.
Когато бизнес логиката не определя необходимостта от определен ред, в който се извършват транзакциите.
Вероятно за Youtube блогър, който събира дарения за производството на нов материал по време на следващото предаване на живо, не е толкова важно кой, кога и в Howъв ред му е хвърлил пари.
Когато потребителите ще останат на една и съща уеб page or прозорец на приложение за няколко секунди or дори minutesи и следователно по няHowъв начин ще видят остарели данни.
Теоретично това са всяHowви онлайн новинарски медии or същия Youtube. Или "Хабр". Когато за вас няма meaning, че непълните транзакции могат временно да се съхраняват в системата, можете да ги игнорирате без ниHowви щети.
Ако събирате данни от много източници и данни, които се актуализират с висока честота - например данни за заетостта на местата за паркиране в град, които се променят поне на всеки 5 minutesи, тогава на теория това няма да е голям проблем за вас, ако в даден момент сделката за някой от паркингите няма да се осъществи. Въпреки че, разбира се, зависи Howво точно искате да направите с тези данни.
GO TO FULL VERSION