R-21-07.doc

(227 KB) Pobierz
PLP_Rozdział_21

Rozdział 21. Implementacja CORBA z pośrednikiem ORBit

Jakiś czas temu grupa osób biorących udział w projekcie GNOME szukała pośrednika ORB. Sprawdzono kilka rozwiązań, spośród których tylko jedno korzystało z języka C, a reszta — z C++.

Opracowany w firmie Xerox pakiet ILU (Inter Language Unification) zawierał usługi CORBA i obsługiwał sporo języków programowania, w tym również język C. W roku 1998 licencja tego programu budziła jednak wątpliwości, zaś firma Xerox nie udzielała wystarczających wyjaśnień, które pozwoliłyby na użycie ILU.

Oprócz tego w pakiecie ILU występował inny problem: nie spełniał on dokładnie specyfikacji CORBA, ponieważ zastosowano w nim własny język definicji interfejsu o nazwie ISL (podobny do IDL) oraz kilka specyficznych protokołów, co zmuszało do dodatkowych przekształceń przy próbie użycia z aplikacją CORBA. Jest to bardzo interesujące pole do doświadczeń z metodami zbliżonymi do CORBA, ale nie jest to dokładnie ta architektura.

Z powodu tych ograniczeń licencyjnych Dick Porter i Elliot Lee zbudowali najpierw parser IDL, rozpoczynając tym samym tworzenie pośrednika ORBit będącego implementacją architektury CORBA. Kontynuują oni swoje prace aż do dzisiaj.

Obecnie wyjaśniono już zagadnienia licencyjne dotyczące ILU, ale w ciągu tego czasu ORBit całkiem dobrze się rozwinął. Zaletą tego pośrednika są niewielkie rozmiary i to, że został zaprojektowany tylko jako implementacja architektury CORBA. ORBit koncentruje się więc na usługach tylko jednego rodzaju, w odróżnieniu od ILU.

CORBA w aplikacji obsługującej wypożyczalnię płyt DVD

Celem tego rozdziału jest pokazanie, że można zbudować aplikację korzystającą z architektury CORBA, dokonując niewielkich modyfikacji w pierwotnym interfejsie graficznym. Zalety zastosowania CORBA w porównaniu z „monolitycznym” podejściem stosowanym do tej pory w naszej aplikacji są następujące:

q      Aplikacja umożliwi równoczesne połączenie wielu użytkowników do jednego źródła danych bez konieczności stosowania dodatkowego programu zarządzającego relacyjną bazą danych — dzięki temu serwer aplikacji stanie się tym właśnie programem.

q      Klienty nie muszą same troszczyć się o synchronizację dostępu do danych.

Systemy klient-serwer o konstrukcji plikowej tradycyjnie wymagają wiele wysiłku poświęconego na zapewnienie odpowiedniego blokowania plików (tak, aby użytkownicy nie niszczyli sobie nawzajem danych). Najnowocześniejsza wersja bazy danych Berkeley o nazwie Sleepycat DB zawiera przeznaczony specjalnie do tego celu proces lock server. Nieustannie krytykuje się też NFS za brak odpowiedniego mechanizmu blokowania.

Nasza aplikacja do obsługi wypożyczalni płyt DVD nie musi posługiwać się rozproszonym mechanizmem blokowania, ponieważ dostęp do danych odbywa się tylko z jednego miejsca —z serwera dostarczającego interfejsy. Ponieważ PostgreSQL również zawiera takie funkcje zarządzania danymi, to w większych systemach może być potrzebne porównanie, które podejście jest skuteczniejsze. Prawdopodobnie najlepszy okaże się sposób pośredni, czyli wykorzystanie systemu zarządzania bazą danych oraz jakiegoś systemu transakcyjnego. Producenci potwierdzili to doświadczalnie, próbując utworzyć systemy porównań wydajności, ponieważ często posługują się zarówno relacyjnymi bazami danych (np. Oracle) równolegle z monitorowaniem oddzielnego przetwarzania transakcji (np. BEA Tuxedo).

Klient DVD

Jest to aplikacja GNOME omawiana już w wielu postaciach. Zmiany w tym programie w większości dotyczą API określającego dostęp do danych i podanego w pliku flatfile.c. Zmodyfikowany program ma nazwę corbaclient.c, co wskazuje na wprowadzenie metod używanych w architekturze CORBA zamiast lokalnego dostępu do plików.

Oprócz tego wprowadzono kilka innych zmian: dodano pomocnicze pliki dvd.c, dvd-common.c i dvd-stubs.c utworzone przez orbit-idl w celu obsługi operatorów CORBA, dodano także fragment kodu obsługujący żądania dostępu do usług CORBA w pliku main.c.

Serwer DVD

Ten program napisany w języku Python działa głównie jako serwer CORBA, zawierając funkcje wykonujące różne operacje zadeklarowane w interfejsach.

Serwer przechowuje dane w plikach bazy danych zbliżonej do Berkeley DB. Na każdy z interfejsów MEMEBERSHIP, TITLING, DISKS, RESERVATIONS i RENTALS przypada po jednym pliku. Dane są przechowywane w postaci napisów, co nie jest w pełni zgodne ze sposobem wymaganym w CORBA do zapisu odwołań obiektowych jako IOR. Program w roli klienta CORBA umożliwia obsługę logów, śledząc przychodzące żądania.

Serwer obsługi logów

Ten element monitoruje działanie serwera DVD. W naszej aplikacji system obsługi logów jest używany głównie jako narzędzie pomocnicze do wyszukiwania błędów i nie umożliwia przejścia na obsługę komunikatów lokalnych podczas awarii sieci.

W systemach bardziej zaawansowanych serwer obsługi logów (ang. logging server) może być najważniejszym narzędziem zabezpieczenia, dzięki któremu będą gubione transakcje. Może on także udostępniać repozytorium danych przydatne np. po awarii, gdy trzeba sprawdzić kto, co i gdzie zrobił.

Dzięki zastosowaniu serwera obsługi logów można uzyskać zwiększenie pewności działania. W systemach zarządzania bazami danych stosuje się metodę zwaną „zapisem logu transakcji” (ang. transaction logging). Każda zmiana dokonana w bazie danych jest wpisywana do logu transakcji w kolejności chronologicznej. Jeżeli baza uszkodzi się, np. podczas awarii sprzętu, to można ją odtworzyć, korzystając z wcześniejszej kopii i logu transakcji, na podstawie którego odtwarzane są czynności wykonane w bazie do momentu awarii.

W wersji bardziej rozbudowanej można wprowadzić kolejny zestaw interfejsów, łącznie z procesem logowania wymagającym potwierdzania autentyczności. Łatwo można dodać interfejs, który sterowałby dostępem do bazy danych na podstawie informacji podawanych przez użytkownika podczas logowania. Istnieje tzw. interfejs Co-Security zdefiniowany w ramach usług zabezpieczania, zapewniający całkiem sprawny mechanizm potwierdzania autentyczności. Niestety, nie jest on dostępny w żadnej z bezpłatnych implementacji architektury CORBA.

Serwer weryfikacji

Jedną z ważniejszych zalet stosowania relacyjnej bazy danych takiej jak np. PostgreSQL jest to, że zapewnia ona możliwość wprowadzania relacji między fragmentami danych oraz środki kontrolujące integralność tych relacji. Na przykład relacja klucza obcego może być użyta jako zabezpieczenie, że płyta DVD będzie wypożyczona tylko tym klientom wypożyczalni, którzy są wobec niej niezadłużeni. Podobnie jak w wypadku zagadnień blokowania dostępu do plików, w wypadku niewielkiej aplikacji można wymusić odpowiednią relację w bazie danych albo weryfikację i zarządzanie relacjami pozostawić serwerowi.

Przy większych aplikacjach obsługujących mocno obciążone bazy danych można podwyższyć wydajność, przenosząc część czynności weryfikacyjnych do usług CORBA, które działają jeszcze przed podjęciem próby modyfikacji bazy danych. Jeżeli wszystkie weryfikacje będą się odbywać w systemie zarządzającym relacyjną bazą danych, to każda nieudana próba modyfikacji bazy powoduje jej dodatkowe obciążenie (należy bowiem rozpocząć modyfikację, wykryć i zasygnalizować okoliczności powstania błędu, a następnie anulować zmiany). W odróżnieniu od takiego sposobu działania systemu, wychwytywanie błędów jeszcze przed dostępem do bazy znacznie zmniejsza liczbę anulowanych transakcji.

Kod klienta

Pierwsza zmiana kodu źródłowego występuje w main.c:

 

int main (int argc, char *argv[])

{

GnomeClient *client;

 

#ifdef ENABLE_NLS

   bindtextdomain (PACKAGE, PACKAGE_LOCALE_DIR);

   textdomain (PACKAGE);

#endif

  user = NULL;

  passwd = NULL;

  /* Nowy fragment kodu dla aplikacji CORBA! */

  ConnectORB(argc, argv);  /* Podłączenie do serwera DVD */

 

  gnome_init_with_popt_table("dvdstore", VERSION, argc, argv,

                              options, 0, NULL);

  /* ... i tak dalej ... */

}

 

Kod serwera obsługi logów

Serwer obsługujący logi dostarcza strukturalną informację o zapisach w logach, korzystając z następującego IDL:

 

module LOG {

  struct loginfo {

    string hostname;

    string userid;

    string application;

    string messagetype;

    string shortmessage;

  };

  interface LOG {

    void addlog (in loginfo info);

  };

};

Interfejsowi temu celowo nadajemy prostą postać, ponieważ nie chcemy dawać mu zbyt wielu szans na błędne działanie:

 

#!/usr/bin/env python

# $ID$

import CORBA, sys, regex, string, random, time

from string import split, strip, joinfields

from time import localtime, strftime, time

# Tutaj są funkcje wspomagające interfejs LOG

class Log:   # interface

    def addlog (self, info):

logfile = open("./logs.log", "a")

logfile.write(joinfields([info.hostname,

      strftime("%Y/%m/%d %H:%M:%S %Z",

        localtime(time())),

      info.userid, info.application,

      info.messagetype, info.shortmessage], "|"))

logfile.write("\n")

logfile.close()

 

CORBA.load_idl("logger.idl")

orb = CORBA.ORB_init((), CORBA.ORB_ID)

poa = orb.resolve_initial_references("RootPOA")

 

servant = POA.LOG.LOG(Log())

poa.activate_object(servant)

ref = poa.servant_to_reference(servant)

open("./logger.ior", "w").write(orb.object_to_string(ref))

 

print "Done initialization: Proceed!"

poa.the_POAManager.activate()

orb.run()

Występuje tu tylko jeden interfejs zawierający jedną funkcję. W zupełności wystarcza to do przekazania komunikatu do logu.

Plik logu jest otwierany i zamykany przy każdym wpisie, co może być przyczyną niewielkiego spadku wydajności, ale jednocześnie zapewnia zapisanie wszystkich modyfikacji na dysk (co jest szczególnie pożądane podczas awarii). Oznacza to, że jeżeli proces zarządzający logiem zmieni nazwę tego pliku, np. w celu usunięcia starszych zapisów, to serwer nie musi zajmować się tą czynnością.

Kod serwera DVD

Serwer DVD został napisany w języku Python z zastosowaniem odwzorowania ORBit-Python (http://orbit-python.sault.org/).

Rozpoczynamy od definicji zestawu funkcji pomocniczych na potrzeby wewnętrzne serwera. Należą do nich definicje repozytoriów danych, zachowujących za pomocą bsddb.btopen dane na dysku w postaci drzew B-Tree. Funkcje korzystające z tych baz danych wymagają wymuszonych modyfikacji danych na dysku za pomocą wywołań takich jak SHMEMBERS.sync:

 

.#!/usr/bin/env python

 

import CORBA, sys, string, random, time, bsddb, os

from string import split, strip, joinfields

from random import randint

from time import localtime, strftime, time

 

###  Podłączenie uzupełniających się tablic do plików

SHDISKS=bsddb.btopen("disks.db", 'c')

SHMEMBERS=bsddb.btopen("members.db", 'c')

SHRENTALS=bsddb.btopen("rentals.db", 'c')

SHRESERVATIONS=bsddb.btopen("reservations.db", 'c')

SHTITLES=bsddb.btopen("titles.db", 'c')

Funkcje należące do klasy SETUPCOUNTERS umożliwiają tworzenie nowych wpisów w tabelach przechowujących dane płyt, klientów wypożyczalni oraz tytułów. Pokazuje to, że implementacja nie ogranicza się tylko do korzystania z zestawu operacji wyliczonych w IDL. W IDL definiuje się interfejsy publiczne, zaś serwer zawiera kilka interfejsów prywatnych, wykorzystywanych na jego własne potrzeby.

 

### Teraz funkcje pomocnicze...

class setupCounters:

   def maxforall(self):

       self.maxfordisks()

       self.maxformembers()

       self.maxfortitles()

   def maxfordisks(self):

       if DBMAX.has_key("disks"):

            max = DBMAX["disks"]

       else:

            max = 1

       try:

           i=SHDISKS.first()

           while i != None:

               iint = string.atoi(i)

               if iint > max:

                   max=iint+1

               i=SHDISKS.next(i)

       except:

           DBMAX["disks"] = max

   def maxformembers(self):

       if DBMAX.has_key("members"):

           max = DBMAX["members"]

       else:

           max = 1   

       try:

           i=SHMEMBERS.first()

           while i != None:

               iint = string.atoi(i)

               if iint > max:

                   max=iint+1

               i=SHMEMBERS.next(i)

       except:

           DBMAX["members"] = max

   def maxfortitles(self):

       if DBMAX.has_key("titles"):

           max = DBMAX["titles"]

       else:

           max = 1

       try:

           i=SHTITLES.first()

           while i != None:

               iint = string.atoi(i)

               if iint > max:

                   max=iint+1

               i=SHTITLES.next(i)

       except:

           DBMAX["titles"] = max

Funkcja logit jest wykorzystywana do zamaskowania operacji, które są wykonywane przez serwer obsługujący logi, co upraszcza działanie całości:

 

   uname = os.uname()

   hostname = uname[1]

   def logit(type, info):

       try:

           LOGORB.addlog(LOG.loginfo(hostname=hostname,

                                     userid="%d" % os.getuid(),

                                     application="dvd-server",

                               ...

Zgłoś jeśli naruszono regulamin