Distribuerte Java-objekter

 

Ingress: Med objekter rundt i nettverket kan vi lage distribuerte applikasjoner nesten "usynlig". Men det er flere måter å få det til på...

 

Anders Fongen

 

Objektorientert programmering betyr bl.a. at vi kapsler dataene våre inn i regler for hvordan de skal behandles. Det er ikke mulig å bruke dataene på annen måte enn hva disse reglene tillater. I distribuerte applikasjoner blir det ønskelig å lage og behandle dataene rundt i et nettverk, og det med den samme programmeringsmodellen som vi har benyttet for de samlede (sentraliserte) applikasjonene.

 

Vi har en tid hatt teknologi som kalles "Remote Procedure Call" (RPC). RPC gir programmeren en mulighet for å kalle prosedyrer på andre maskiner i nettverket. Disse prosedyrene tilbyr gjerne en eller annen form for behandling av data, og dermed også den form for innkapsling vi er vant til å se fra modulære programmeringsspråk som Modula-2 og Ada.

 

Med en objektorientert programmeringsmodell, som den vi benytter oss av når vi programmerer i Java-språket, ønsker vi også en distribusjon, men da på en slik måte at vi også tar vare på de andre egenskapene ved objektorientert programmering, nemlig instansiering, arvelighet og flertydighet. Med andre ord, vi ønsker å programmere som før, med minst mulig begrensninger som følge av at vi ønsker å fordele objektene våre på mange maskiner.

 

Vi skal i denne artikkelen se på hvilke produkter som en Java-programmerer kan benytte seg av for å dele opp et Java-program mellom flere maskiner. Vi skal i første omgang titte på "Remote Method Invocation".

 

Remote Method Invocation

 

RMI er noe som er kommet inn i Java versjon 1.1 (JDK 1.1), og som lar Java-objekter sende meldinger til hverandre gjennom et nettverk. Gjennom å utnytte såkalte "Interface"-definisjoner i Java kan et objekt oppfylle de egenskapene som er nødvendig for at meldingen skal kunne omgjøres til en form som tillater transport i nettverket (i avsnittet "Parameteroverføring" ser vi på de spesielle problemstillingene knyttet til dette).

 

Steg for steg fungerer RMI slik:

 

1. Det objektet som vi ønsker skal være et "tjenerobjekt" (dvs. et objekt som er lagret på en maskin og skal motta meldinger fra objekter på andre maskiner og gi returverdier tilbake) bygges inn i et lite java-program som oppretter (instansierer) dette objektet og registrerer det i en enkel navnetjeneste som kalles for rmiregistry.

 

2. Det objektet som skal være "klientobjektet", må programmeres slik at det spør rmiregistry om en referanse til dette objektet. Med denne referansen "castet" over til riktig klasse kan det behandles akkurat som et lokalt objekt. For å kunne utføre denne "castingen" trenges en "Interface"-modul inneholder syntaktiske deklarasjoner for metodene til tjener-objektet. Denne modulen kalles vi et "Remote Interface".

 

3. Tjener-objektet må være lokalt representert av en "Client Stub", som er en Java-klasse med nøyaktig det samme grensesnittet som tjener-objektet. Alle operasjoner som klientobjektet gjør mot tjenerobjektet resulterer i kall til Client Stub, som i sin tur konverterer kallene til nettverksoperasjoner mot tjenerobjektet.

 

Med RMI kan vi programmere en java-applikasjon på en slik måte at det "nesten" blir usynlig for klienten om objektene ligger lokalt eller spredt rundt i nettverket. Med forbehold om instansiering (oppretting) av objekter og visse typer parameteroverføring, er et RMI-objekt helt likt et lokalt objekt i forhold til egenskaper som flertydighet, spilloppsamling (garbage collection) og callback-meldinger.

 

Et klientobjekt må anrope navnetjenesten etter det navnet som tjenerobjektet har registrert seg under. I retur fra dette kallet får klienten et objekt (som må "castes" til riktig klasse), og kan deretter sende meldinger til objektet akkurat som om det var et lokalt objekt.

 

På tjenersiden kan et objekt erklære seg som et "Remote object" ved at klassen den tilhører arver klassen UnicastRemoteObject og implementerer deklarasjonen i Remote Interface. Videre må objektet som nevnt "registrere seg" i en den navnetjeneste som følger med RMI-komponentene (rmiregistry).

 

rmic - RMI Compiler

 

I tillegg til kompilering av de Java-modulene som vi selv skriver, trenger vi i tillegg en klassefil for "Client Stub", altså den modulen som befinner seg hos klienten og "utgir seg for å være" tjenerobjektet. Dette skal ha nøyaktig det samme grensesnittet som tjenerobjektet, men inneholder masse mystisk kode som du neppe vil ønske å studere i detalj, enda mindre skrive selv.

 

rmic er et program som spiser klassefilen for tjenerobjektet, og lager en "Client Stub" på basis av opplysningene der. Den må lastes opp i klientens virtuelle Java-maskin under utføring. Dessuten lager rmic en "Skeleton", som er en tjener-komponent som sørger for å gjøre nettverkstransaksjoner om til meldinger til tjenerobjektet.

 

Legg merke til at Interface-deklarasjonen ikke er input til rmic, kun klassefilen til tjenerobjektet er det. Interface-deklarasjonen inngår kun som grunnlag for typesjekking av operasjonene som klientobjektet skal gjøre mot tjenerobjektet (eller Client Stub).

 

CORBA og Java

 

Remote Method Invocation representerer en enkel og effektiv implementasion av distribuerte objekter med god "skjuling" av distribusjonen. RMI har derimot noen ulemper:

 

1. Kun objekter implementert med Java-språket kan kommunisere med hverandre.

 

2. Det er ikke mulig for et klientobjekt å instansiere et tjenerobjekt, kun å sende meldinger til et objekt som allerede er opprettet (klart vi kan be et slikt tjenerobjekt opprette nye objekter på klientens vegne og returnere en referanse til det nye tjenerobjektet, men da introduserer vi forskjeller på hvordan lokale objekter og tjenerobjekter blir behandlet).

 

Dersom vi altså ønsker muligheten for å opprette tjenerobjekter selv, og vi ønsker arkitekturnøytrale objekter, kan vi se på hvordan distribusjonsmodellen CORBA (Common Object Request Broker Architecture) kan benyttes fra Java. CORBA beskriver hvordan objekter i et nettverk kan lagres, navngis og brukes, og har regler for hvordan parametre og returverdier skal sendes over nettverket.

 

CORBA er en ganske omfattende standard, som vi ikke skal omtale i noen særlig utstrekning i denne artikkelen (Corba ble særskilt omtalt i PC World Nettverk nr.3/96). Vi skal derimot se på hvordan Java-objekter kan opprette og sende meldinger til CORBA-objekter, dvs. objekter som kan være laget med mange typer programmeringsspråk og ligge lagret på maskintyper som aldri har hørt om Java. CORBA-objekter kan også være laget med Java og være tjenere for alle slags klientobjekter som benytter CORBA som distribusjonsmodell.

 

CORBA-objekter må ha en "oppskrift" på hvordan de skal brukes. En slik oppskrift må skrives i et språk som alle kan forstå, og CORBA-modellen har noe som de kaller IDL - Interface Definition Language. Dette er et deklarasjonsspråk som minner mye om C++, og som beskriver metodenavn, bruk av parametere og returverdier. En slik oppskrift kaller vi for en "IDL-deklarasjon".

 

For at et Java-objekt skal kunne sende meldinger til et COBRA-objekt, må CORBA-objektet se ut som et Java-objekt for klienten. Vi må også skjule det faktum at CORBA-objektet befinner seg i den andre enden av en nettverksforbindelse. Metoden som brukes er helt tilsvarende både RPC og RMI. Fjernobjektet representeres lokalt av en "client stub". Hvordan lages en "client stub"? Jo, ved å lage en Java-skrevet deklarasjon basert på opplysningene i IDL-deklarasjonen.

 

Implementasjoner av Java/CORBA

 

De som vedtar standardene for CORBA-modellen heter OMG, "Object Management Group". OMG har mer enn 500 medlemsbedrifter, og arbeider ganske seint. Interessen for en kopling mellom Java og CORBA er derimot meget stor, og flere Java-orienterte bedrifter har laget sine egne utkast og kjørbare prototyper. Disse har vært mulig å laste ned fra Internet og eksperimentere med, men det har selvfølgelig vært vanskelig for brukere å starte ordinært utviklingsarbeid basert på et standardiseringsforslag.

 

Mens OMG sov på saken har disse Java-orienterte bedriftene samordnet sine utkast, og viktige aktører som Visigenic, Sun, Orbix, IBM, Oracle og Netscape har stillet seg bak et felles forlag overfor OMG. Så, i mai 1997 har OMG vedtatt dette forlaget som en standard for kopling mellom Java og CORBA, så nå er det bare å sette igang med å lage CORBA-objekter basert på Java.

 

Nei, forresten, det er foreløpig den såkalte "mappingen" fra IDL til Java-deklarasjoner som er vedtatt. Det betyr at brukerne nå kan skrive klient-objekter i Java, fordi det er entydig bestemt hvordan et Java-objekt skal skrives for å utnytte et gitt CORBA-objekt. Tilsvarende rmic finner vi nå et program (f.eks. kalt idlgen) som omsetter en IDL-deklarasjon til en Interface-modul og en ClientStub i Java. Det som fortsatt gjenstår er hvordan CORBA-objekter som er implementert med f.eks. Orbix sitt CORBA-produkt skal snakke med et Java-objekt som har valgt å bruke klassebiblioteket for CORBA fra Sun. Det har seg nemlig slik at alle disse leverandørene har sin egen versjon av den nettverksprotokollen som går mellom CORBA-objektene. Denne protokollen kalles ORB, "Object Request Broker", og hver leverandør har hver sin (noen har til og med to!) ORB-protokoll.

 

OMG adresserte dette problemet for noen år siden, og kom frem til en standard protokoll som skulle styre dataoverføringen mellom ulike CORBA-systemer. Denne protokollen kalles IIOP, "Internet Inter-ORB Protocol" og er del av CORBA 2-spesifikasjonen.

 

Noen av CORBA/Java-produktene vi har funnet har støtte for IIOP, og de andre lover det i fremtiden. Uten at vi selv har følt dette behovet på kroppen er vi ganske sikre på at et åpent marked av CORBA-objekter i fri flyt på Internet vil kreve at alle sider av objektdistribusjonen er produkt- og arkitekturnøytral. Vi tror derfor at IIOP er en nødvendig del av systemet, og at du må lære deg å programmere for objektdistribusjon med IIOP.

 

Vi kan også nevne at Orbix sitt CORBA-produkt for Java, kalt OrbixWeb, kan kombineres med et annet produkt, "Orbix for Windows" for å lage en bro mellom CORBA og DCOM (Microsofts's distribusjonsmodell). Både CORBA og DCOM er viktige modeller, så slike broprodukter blir viktig "lim" i den virkelige verden.

 

Eksistenskontroll

 

Det er en del problemstillinger som er felles for alle distribuerte objektmodeller. Ett av dem er "eksistenskontroll", dvs. kontroll med hvilke objekter som er ute av bruk og kan fjernes. I et samlet system er det "garbage collector" som holder rede på bruken av objekter gjennom en "bruksteller", som viser hvor mange klienter som et objekt har. Når dette tallet reduseres til null, kan objektet fjernes. Dette er en modell som fungerer greit i et samlet system som også opptrer samlet ved en systemkrasj: Vi antar at enten er hele systemet operativt, ellers er hele systemet krasjet og må omstartes i sin helhet.

 

Dette er ingen riktig forutsetning i et distribuert system, hvor vi godt kan tenke oss at én av to klienter er krasjet, mens tjeneren og den andre klienten fortsatt er i drift. Da trenger vi en måte for å fortelle tjeneren at objektet nå bare har én klient, men vi kan ikke basere oss på at den krasjede klienten skal delta i noen form for protokoll for at dette skal lykkes. De fleste distribuerte systemer løser dette med "hjerteslag" i nettverksforbindelsen mellom klienten og tjeneren, som er små meldinger i nettverket. Dersom disse hjerteslagene opphører over en lengre periode, er klienten å betrakte som "død", og brukstellerne blir endret i henhold til dette. Problemet med "hjerteslag" er at det fyller nettverket med unyttig trafikk, og begrenser skalerbarheten i store nettverk (tenk deg 100 millioner objekter i Internet som alle skal ha hjerteslag hvert 10.sekund).

 

Parameteroverføring

 

Når et objekt sender en melding til et annet lokalt objekt, kan overføringen av parametre og returverdier skje på to måter, nemlig ved å overføre selve parameterverdien (pass-by-copy) eller en referanse til minnelokasjonen hvor parameterverdien finnes (pass-by-reference). I det første tilfellet er det i en distribuert objektmodell mulig å overføre parameterverdien gjennom nettverket, også fordi en slik parameter kun overfører data én vei.

 

Under overføring må alle dataverdier oversettes til en "universell" form, fordi det blir feil om vi overfører bit for bit direkte mellom ulike maskinarkitekturer. Avsender og mottager må derfor være enige om at "dette er bokstaven A", og så konvertere dataene mellom sitt eget lagringsformat og den universelle formen.

 

I tilfellet med pass-by-reference oppstår det to problemer; Det ene er at en slik parameter overfører data begge veier, slik at den kan kreve to-veis trafikk i nettverket dersom parameteren blir endret hos tjeneren. Det andre er at det ikke er mulig å overføre kun en peker til en minnelokasjon, fordi vi må vite hva slags data som det pekes til for at konvertering mellom intern og universell form skal skje riktig. For å sikre at kun "lovlige" parameterklasser blir overført, har CORBA og RMI litt forskjellig løsning: CORBA tillater kun ferdigdefinerte parameterklasser i IDL-spesifikasjonen, mens RMI forlanger at alle objekter som skal brukes som parametre implementerer grensesnittet "Serializable", noe som er tilfellet med standardklassene, men må skrives inn i tilfellet med egendefinerte objektklasser.

 

Dessuten kan størrelsen på parametrene skape problemer; om vi overfører en parameter som er et array på 400 kBytes ved hjelp av en peker, er dette en liten operasjon i et samlet system, fordi det kun er en peker til dataene som blir overført, ikke hele arrayet som sådan. I et distribuert system, derimot, må hele denne datastrukturen kopieres over, deretter kopieres tilbake dersom den blir endret. Vi kan derfor skape store kostnader ved distribusjon om vi ikke tar hensyn til de spesielle egenskapene ved parameteroverføringen.

 

Brannvegger

 

Vi forestiller oss gjerne distribuerte objekter rundt i Internet, kanskje ett som lagres på en stor databasemaskin og som tilbyr oppslag i f.eks. telefonkatalogen basert på en tilhørende betalingsordning. Dette høres kjekt ut, men det krever at den nettverksprotokollen som skal brukes faktisk kan ta seg frem i Internet, og det er, som vi skal se, ingen selvfølge.

 

Internet og private IP-nettverk er ofte koplet sammen gjennom en brannvegg, og denne brannveggen kan typisk ha som policy at den kun slipper gjennom trafikk som er "kjent", dvs. terminaltrafikk, filoverføring, Web-sider og litt til. Andre tjenester som benytter faste TCP portnummer kan slippes gjennom, men ofte vil bedriften ha innvendinger mot det.

 

Dersom du sitter på et privat nettverk og har skrekkelig lyst på et objekt på utsiden av brannveggen, kan du finne ut hvilket portnummer som denne ORB-protokollen bruker og mase deg til et hull i brannveggen for dette formålet, men det vil være lettere for deg dersom du bare kunne ta objektet i bruk uten å endre konfigurasjonen av brannveggen.

 

Og dersom du lager et tjenerobjekt som du ønsker å selge til resten av verden gjennom Internet, blir du enda mer avhengig av at bruken av objektet ditt ikke forutsetter noen endringer hos klientens brannvegg.

 

Hvordan du kan få dette til? Ved å "innkapsle" ORB-protokollen i en kjent Internet-protokoll, f.eks. HTTP. Selv har vi testet litt på CORBA-objekter på den andre siden av en brannvegg, og har sett at enkelte CORBA-produkter slipper gjennom en brannvegg, andre ikke. RMI kan konfigureres til å benytte HTTP-protokollen for å bli "brannvegg-vennlig", men da tar meldingene minst 10 ganger lengre tid. Allikevel, en ORB som tar hensyn til brannvegger og kan komme seg gjennom dem ved å bruke f.eks. HTTP, anser vi som nødvendig dersom man har ambisjoner om å distribuere objekter i stor spredning.

 

 

 

Sitatboks: ORB-protkollen må være "brannvegg-vennlig"

 

Programlistinger: Viser de tre Java-modulene som er nødvendig for å distribuere objekter, her kalt Counter.java, CounterImpl.java og CounterClient.java.