Dziś jest piątek, 12 marca 2010 roku (z kalendarza...)

Transakcje rozproszone cz. 2

W ubiegłym tygodniu omówiliśmy sobie, czym są transakcje rozproszone, do czego mogą być wykorzystywane oraz zaznajomiliśmy się z koncepcją ich działania. Ponieważ prace posunęły się naprzód, czas omówić krótko budowę systemu do realizowania internetowych połączeń transakcyjnych oraz przyjrzeć się projektowi API.

Projekt implementacji

Zdecydowałem się, że system transakcyjny będzie zbudowany jako biblioteka udostępniająca API do nawiązywania połączeń z innymi procesami poprzez gniazda i komunikacji za ich pośrednictwem. Zestaw funkcji po części będzie wzorowany na standardowych funkcjach ich obsługi, lecz rozbudowany o operacje dotyczące samej transakcji. Dane przesyłane między procesami będą przesyłane jako część wiadomości protokołu transakcyjnego.

Biblioteka transakcji sama w sobie będzie dostarczać rejestr transakcji, lecz stworzenie mechanizmów zapisu danych musi pozostać już w gestii programisty, gdyż muszą one zostać dopasowane do konkretnych potrzeb, a po drugie nie jest to już tematem projektu :). Ponadto doszedłem do wniosku, że dobre API w istocie łatwiej jest zaprojektować dla transakcji hierarchicznych, stopień skomplikowania podnosi się tylko nieznacznie, a zyskujemy naprawdę dużo dodatkowych możliwości.

Pojęcia biblioteki

W obrębie transakcji musimy wyróżnić zawsze koordynatora transakcji oraz pozostałych uczestników, którzy z kolei mogą być koordynatorami transakcji podrzędnych. Sama idea działania transakcji rozproszonych narzuca nam konieczność stworzenia hierarchii, jednak pamiętajmy, że istnieje ona tylko na potrzeby przebiegu transakcji. Same procesy mogą równie dobrze tworzyć zdecentralizowane środowisko bez wyróżnionego głównego nadzorcy.

Aby zrozumieć budowę interfejsu programistycznego, musimy wprowadzić sobie dwa pojęcia. Pierwsze z nich to transakcja. Jest to pewien przedział czasowy, w ramach którego proces wykonuje zestaw operacji, kontrolowany przez bibliotekę. Obsługa transakcji składa się z trzech faz:

  1. Tworzenie - od zera (proces zostaje koordynatorem głównym) lub dołączenie do transakcji zainicjowanej przez inny proces.
  2. Operacje - proces wykonuje tutaj swoje operacje, komunikując się z innymi procesami.
  3. Zatwierdzanie - proces oddaje swój głos oraz otrzymuje odpowiedź, czy transakcję ma zatwierdzić czy odrzucić.

Zatem pod pojęciem transakcji rozumiemy środowisko klienta, który wykonuje w nim pewne operacje. Ważne jest, aby pamiętać, że taki klient nie może samodzielnie podłączyć się do już trwającej transakcji, lecz musi zostać zaproszony do niej przez jakiegoś koordynatora.

Podczas fazy akcji, proces może otwierzyć kanał do kolejnego procesu, przyłączając go do swojej transakcji i stając się dla niego koordynatorem. Kanał służy do dwukierunkowej komunikacji między koordynatorem i innym procesem w środowisku transakcyjnym, a ponadto umożliwia ubicie transakcji i wysłanie prośby o zagłosowanie. W przypadku transakcji zagnieżdżonych, są one faktycznie zatwierdzane dopiero w momencie zatwierdzenia transakcji głównej. Biblioteka w sposób przezroczysty dba o przekazanie odpowiednich informacji do głównego koordynatora.

Interfejs programistyczny

Funkcje obsługi transakcji:

  1. trans_create - tworzy nową transakcję.
  2. trans_join - tworzy nową transakcję poprzez dołączenie do już istniejącej.
  3. trans_set_cancel_func - rejestruje funkcję anulowania operacji.
  4. trans_set_stage - pozwala ustalić flagę etapu transakcji dla funkcji anulowania operacji.
  5. trans_get_state - zwraca aktualny stan transakcji.
  6. trans_channels_reset - resetuje wskaźnik listy otwartych kanałów.
  7. trans_channels_next - przechodzi do kolejnego kanału na liście i zwraca go.
  8. trans_send - wysyła odpowiedź do koordynatora (tylko w przypadku dołączenia)
  9. trans_rcv - odbiera dane od koordynatora (tylko w przypadku dołączenia)
  10. trans_ready - czeka na możliwość oddania głosu.
  11. trans_commit - oddaje głos "Zatwierdź".
  12. trans_rollback - oddaje głos "Odrzuć".
  13. trans_close - zamyka środowisko transakcji.

Funkcje obsługi kanału:

  1. trans_channel_open - otwiera kanał do podanego procesu.
  2. trans_channel_send - wysyła dane kanałem do procesu.
  3. trans_channel_rcv - odbiera dane od procesu.
  4. trans_channel_vote - prosi proces o zagłosowanie i oczekuje na odpowiedź.
  5. trans_channel_vote_send - głosowanie asynchroniczne - wysyłanie prośby.
  6. trans_channel_vote_rcv - głosowanie asynchroniczne - odbiór odpowiedzi.
  7. trans_channel_abort - wymusza anulowanie transakcji.
  8. trans_channel_close - zamyka kanał.

Dodatkowe funkcje:

  1. trans_is_transactional - sprawdza, czy odebrane przez gniazdo dane są częścią protokołu obsługi transakcji.
  2. trans_process - dodatkowe przetwarzanie odebranych przez gniazdo danych.

Przykład użycia

Poniżej przedstawiam przykład użycia podanych funkcji w pseudokodzie:

/* koordynator transakcji */
 
struct trans_ctx *trans = trans_create();
 
do_some_operations();
 
struct trans_channel *channel = trans_channel_open(trans, "127.0.0.1", 12345);
trans_channel_send(channel, "foo", 4);
 
char answer[256];
trans_channel_rcv(channel, (void*)&answer, 256);
 
do_some_ops();
 
/* prosimy uczestnika o oddanie glosu */
if(trans_channel_vote(channel))
{
  printf("Uczestnik zatwierdzil.\n");
}
/* sami oddajemy glos, a poniewaz jestesmy koordynatorem,
 w tym miejscu sprawdzane sa tez glosy uczestnikow i wybierana
 jest wlasciwa odpowiedz. */
trans_ready(trans);
if(trans_commit(trans))
{
  printf("Zatwierdzam!\n");
}
else
{
  printf("Nie zatwierdzam!\n");
}
trans_close(trans);

Uczestnik transakcji może wyglądać następująco:

/* niech tu beda dane odebrane z gniazda */
void *received_data;
int type
if(type = trans_is_transactional(data, 512))
{
  if(type == TRANS_INVITATION)
  {
    struct trans_ctx *trans = trans_join(data);
    
    char data[256];
    trans_rcv(trans, (void*)&data, 256);
 
    do_some_ops();
 
    trans_send(trans, "bar", 4);
    trans_ready(trans);
    if(trans_commit(trans))
    {
      printf("Git.\n");
    }
    else
    {
      printf("Fail.\n");
    }
    trans_close(trans);
  }
  else
  {
    trans_process(data);
  }
}

Zakończenie

Podane przykłady mają na razie charakter poglądowy, jednak można już teraz zauważyć, że protokół nie jest w pełni odzwierciedlony w postaci API i część jego zadań będzie wykonywać się automatycznie. W chwili obecnej trwa faza implementacji całego interfejsu. W kolejnym odcinku postaram się bliżej przybliżyć sam protokół.

Powrót

Skomentuj

NickInformacja
E-mailNa wypadek potrzeby kontaktu z autorem (niepublikowany)
BlogNie zapomnij o http://
LayoutNapisz tu, czy widzisz dzienny czy nocny layout.
WpisFormatowanie wiki

Na Zyxist.com panuje swoboda wyrażania opinii oraz krytyki pod dowolnym adresem. Jedyny warunek: musi być ona kulturalna i rzeczowa. Na chamstwo, prostactwo lub jawne obrażanie kogokolwiek nie ma tu miejsca i takie komentarze są bardzo szybko usuwane. Jeśli zamierzasz polemizować z treścią wpisu, wpierw uważnie ją przeczytaj.

© Tomasz "Zyx" Jędrzejewski 2005 - 2010 | Wykonanych zapytań: 1 | Serwer wirtualny zapewnia