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.