verteilte anwendungen teil 7: remote procedure call (rpc...

55
08.04.18 1 VA – SS 2018 - Teil 7/RPC II Verteilte Anwendungen Teil 7: Remote Procedure Call (RPC) Realisierung

Upload: others

Post on 09-Sep-2019

1 views

Category:

Documents


0 download

TRANSCRIPT

08.04.18 1VA – SS 2018 - Teil 7/RPC II

Verteilte Anwendungen

Teil 7: Remote Procedure Call (RPC)Realisierung

2VA – SS 2018 - Teil 7/RPC II

Literatur

[7-1] https://de.wikipedia.org/wiki/Stapelspeicher

[7-2] https://de.wikipedia.org/wiki/Reflexion_(Programmierung)

[7-3] http://openbook.rheinwerk-verlag.de/javainsel9/javainsel_25_001.htm

[7-4] http://openbook.rheinwerk-verlag.de/javainsel9/javainsel_11_002.htm

[7-5] http://www-inst.eecs.berkeley.edu/~cs162/sp06/hand-outs/p149-presser-linker-loader.pdf

[7-6] https://de.wikipedia.org/wiki/Linker_(Computerprogramm)

3VA – SS 2018 - Teil 7/RPC II

Übersicht

• Stubs und Marshalling

• Stackaufbau eines Programms

• Dynamische Aufrufe: Call

• Reflexion

• Parallele Verarbeitung im Server

4VA – SS 2018 - Teil 7/RPC II

Realisierung der RPCs

• Der Transfer der Kontrolle wird auf den Transfer von Daten zurück geführt.

• Die Realisierung der Umsetzung der Kontrolle nach Daten und umgekehrt erfolgt in einem Stub:

• Stub = Realisierung der Schnittstelle zum Netzwerk mit der Aufgabe der Durchführung von Remote Procedure Calls

Object1

Stub

BS

Object2

Stub

BSNetzTCP/IP

RPC-Protokoll

Datenfluss

Call, Kontrollfluss

OSI-Schichten 1 bis 4

5VA – SS 2018 - Teil 7/RPC II

Aufgaben der Stubs I

• Serialisieren = Konvertieren der internen Repräsentation von Daten in ein mit allen Kommunikationspartnern abgesprochenes Format, meist ASCII.

• Deserialisieren = Umgekehrter Vorgang des Serialisierens und damit Wiederherstellung der semantisch gleichen Repräsentation

• Bitte beachten: Semantisch gleich bedeutet nicht unbedingt dieselbe Bitkombination.

• Realisieren des Protokolls: Reihenfolge der Pakete, Formate von Paketen etc.

• Feststellen und Behandeln von Fehlern

• Anpassung unterschiedlicher unterliegender Schnittstellen, d.h. Stubs sind eigentlich Wrapper.

6VA – SS 2018 - Teil 7/RPC II

Aufgaben der Stubs II

• Marshalling ist ein anderer Begriff für das Serialisieren und Deserialisieren

• Externe Data Repräsentation (XDR) = Serialisierte Daten im verabredeten Datenformat zwischen den Kommuikationspartnern

Object1

StubXDR

Lokale Daten

Repräsentation

Object2

Stub

Lokale Daten

Repräsentation

BS BSNetz

RPC-Protokoll

BS = Betriebssystemmit dem Transportsystem

7VA – SS 2018 - Teil 7/RPC II

Serialisieren in Java und PHP

Java

• In Java geht die Serialisierung über das Interface java.io.serializable sowie über die I/O-Stream-Klassen.

• Siehe– http://docs.oracle.com/javase/6/docs/platform/serialization/spec/protocol.html

– https://docs.oracle.com/javase/tutorial/jndi/objects/serial.html

– http://openbook.rheinwerk-verlag.de/javainsel9/javainsel_17_010.htm

– http://www.tutorialspoint.com/java/java_serialization.htm

– http://www.vogella.com/tutorials/JavaSerialization/article.html

PHP

• In PHP erfolgt dies durch den Aufruf von serialize() und unserialize().

• Siehe– http://php.net/manual/de/function.serialize.php

– http://php.net/manual/de/language.oop5.serialization.php

– http://serialize.onlinephpfunctions.com/

8VA – SS 2018 - Teil 7/RPC II

Serialisierung I

Datentyp Beispiel für ein Format

int (8,16,32,64 bit) ASCII-Hex

subrange-int ASCII-Hex

float, double, extended Dezimal

char (1 Byte) ASCII-Hex

char (UNICODE) ASCII-Hex

String ASCII-Hex

boolean Dezimal

Aber wie sieht es aus mit Enumeration Types?Z.B. mit einem Paar [Symbol als String,Wert als int]

Aber wie sieht es aus mit BigInteger?

Und mit BigDecimal?

Und mit BCD-Arithmetik?

9VA – SS 2018 - Teil 7/RPC II

Serialisierung II – Schnittstelle einer Routine

• Die Liste der Parameter

• Die Liste der Resultate bzw. das Resultat

• Exception-Informationen

• Hier gibt es verschiedene Probleme, u.a.– Wie werden Referenzen (Pointer) behandelt?

– Werden Zeichenketten (Strings) kodiert?

– Werden Objekte übertragen, die ja Code enthalten?

• Wir gehen in dieser Lehrveranstaltung vereinfachend davon aus,– dass nur primitive Daten übertragen werden: int, float, char,

– dass nur Maschinen desselben Typs betroffen sind,

– und dass es keine Seiteneffekte der aufgerufenen Routinen gibt.

10VA – SS 2018 - Teil 7/RPC II

Serialisierung III – Pointer/Adressen

1) Einfach verbietenNachteil: wird häufig gebraucht, z.B. Output-Parameter in Routinen wie read(…)

2) Alle referenzierten Objekte müssen mit serialisiert werden.– Aber wie weit muss das gehen?

Liste von Objekten: die ganze Liste?

3) Alle referenzierten Objekte müssen jeweils lokal verfügbar sein bzw. gemacht werden, d.h. die Objekte müssen zwischen Knoten verschiebbar sein.

Drei Strategien für Zeiger:

Alle drei Strategien gibt es in der Praxis.Alle drei Strategien sind nicht schön.

11VA – SS 2018 - Teil 7/RPC II

Allgemeine Realisierung des Stubs auf der Client-Seite

(01)proc call(Name,ParamList) {(02) data:= serialize(ParamList)(03) msg:= createMSG(Name,data)(04) send(Server,msg)(05) result:= receive()(06) if result=error {(07) throw Exception(08) } else {(09) data:= unserialize(result)(10) return(data)(11) }

• Das ist eine allgemeine Call-Routine des klassischen synchronen RPCs.

• Im Fehlerfalle wird eine Exception geworfen.

• Dies hier stellt nur das Prinzip dar.

// Client Beispiel... avg:= call("avg",1,2,3)... print avg// avg = Durchschnitt// eine 2 wird ausgegeben

12VA – SS 2018 - Teil 7/RPC II

Bemerkungen

• In Zeile (3) wird eine Nachricht mit den serialisierten Informationen erstellt.

• Dieses Datenpaket ist eine Struktur mit zwei Werten: – Name der Routine

– Parameterliste

• In Zeile (4) wird das Paket gesendet und in (5) die Antwort empfangen.

• Zeilen (6) bis (8) zeigen die Fehlerbehandlung, d.h. das Weiterreichen einer Exception.

• Bitte beachten Sie, dass der Pseudocode sich auf eine allgemeine Routine bezieht, die alle möglichen Routinen entfernt aufrufen vermag.

• Der Name der aufzurufenden Routine wird als String übergeben.

13VA – SS 2018 - Teil 7/RPC II

Realisierung des Stubs auf der Server-Seite I

(01)proc processCalls {(02) do {(03) msg:= receive()(04) proc:= msg.Name(05) if exists(proc) {(06) data:= unserialize(msg.ParamList)(07) try(08) result:= call "proc"(data)(09) catch(10) result:= Error(11) } else {(12) result:= Error(13) }(14) data:= serialize(result)(15) reply(data)(16) }

• Das ist die Callee-Routine des synchronen RPCs.

• Auf der Server-Seite befindet sich eine Endlos-Schleife, die alle Requests abarbeitet und sonst nichts tut.

Das ist eine ArtHauptprogramm

14VA – SS 2018 - Teil 7/RPC II

Bemerkungen

• In Zeile (4) wird der Name der Routine extrahiert.

• In (5) wird anhand einer Tabelle geprüft, ob die betreffende Routine geladen ist bzw. existiert.

• Der Aufruf in (8) muss in einem TRY-Block erfolgen, da die aufgerufene Routine eine Exception werfen kann, die in in (10) weitergereicht wird.

• In (15) wird entweder das Ergebnis zurück geliefert oder eine Exception bzw. eine Fehlernummer.

• Bitte beachten Sie, dass der Pseudocode sich auf eine allgemeine Call-Routine bezieht, die alle möglichen Routinen lokal aufrufen vermag.

• Der Name der aufzurufenden Routine ist als String vorhanden.

15VA – SS 2018 - Teil 7/RPC II

Generieren des Stubs I

• Die skizzierten Stubs sind genereller Natur: sie werden ein einziges Mal programmiert und werden für alle Routinen benutzt.Das geht nur mit dynamisch parametrisierten Klassen oder in Skriptsprachen, wie z.B. PHP oder Python.

• Alternativ werden Stubs per Generator (IDL-Compiler) entsprechend der Signatur der Routinen individuell generiert. Dieser Code kann dann nur für diese eine Routine verwendet werden.

• Beispiel für Java:

16VA – SS 2018 - Teil 7/RPC II

Generieren des Stubs II

• Bei der Verwendung eines IDL-Compilers wird auf der Klientenseite für jede Routine ein eigener Stub generiert; dieser hat genau die in der IDL spezifizierten Parameter samt Typen.

• Auf der Seite des Servers erfolgt dies genauso, aber der Aufruf muss dann in einem großen Switch/Case erfolgen bzw. im Falle der Objekt-Orientierung durch Polymorphie (intern durch Zeiger und Tabellen).

17VA – SS 2018 - Teil 7/RPC II

Realisierung des Stubs auf der Server-Seite II

...(07) try(08) result:= call "proc"(data)(09) catch(10) result:= Error...

... (07) try(07a) switch proc(07b) case "Routine1": Routine1(data.p1,data.p2...);(07c) case "Routine2": Routine2(data.p1,data.p2...);(07d) ... (07e) end_switch(09) catch(10) result:= Error...

Die einzelnen Parameter müssenextrahiert und angegeben werden.

Dieser Teil wird vomIDL-Compiler generiert

18VA – SS 2018 - Teil 7/RPC II

Nachteile dieses Verfahrens

• Dieses Verfahren hat folgende Nachteile:– Kein dynamisches Laden möglich

– Starre und feste Anzahl von Routinen mit fester Parameteranzahl

• Es fehlt die Flexibilität der dynamischen Typen von Skript-Sprachen.

• So implementiert, können nur die im switch-Konstrukt explizit berücksichtigten Routinen aufgerufen werden.

• Was aber wenn ein allgemeiner Stub, der alle möglichen Routinen mit beliebigen Parametern aufrufen können soll, benutzt werden soll?

• Eine Lösung dazu wird nun präsentiert.

19VA – SS 2018 - Teil 7/RPC II

Flexibler Aufruf von Routinen I

• Über eine Nachricht wird der Name einer Routine dem Server übergeben.

• Der Server muss nun diese Routine aufrufen, d.h.

der Aufruf muss schon programmiert sein.

• Es geht also um das call:

proc processCalls do { pdu:= receive() proc:= pdu.Name if exists(proc) { data:= unserialize(pdu.ParamList) try result:= call "proc"(data) catch result:= Error } else { result:= Error } data:= serialize(result) reply(data) }

20VA – SS 2018 - Teil 7/RPC II

Aufbau des Datenbereichs eines Prozesses

Stack

Heap

Frei

BelegtdurchStack

BelegtdurchHeap

SP ->

Date

n-S

egm

ent

Richtung desWachsens desStacks

SP = Stackpointer(Register in der CPU)

• Die Aufgabe des Stacks besteht in der Bereitstellung und Vernichtung lokaler Variablen und Parameter.

• Die Aufgabe des Heaps besteht in der Bereitstellung und Vernichtung von langlebigen Objekten, z.B. Instanzen von Klassen

21VA – SS 2018 - Teil 7/RPC II

Aufruf einer Prozedur mit Parametern

Stack

SP ->

Parameter N

...

Parameter 2

Parameter 1

Return-Adr.

Situation direkt nach Aufruf einer RoutineS(Parameter1,Parameter2,…,ParameterN)

Mechanismus des Aufrufsder Prozedur S

22VA – SS 2018 - Teil 7/RPC II

Call "Name"(Parameterliste) I - Wiederholung

proc processCalls { do { pdu:= receive() proc:= pdu.Name if exists(proc) { data:= unserialize(pdu.ParamList) try result:= call "proc"(data) catch result:= Error } else { result:= Error } data:= serialize(result) reply(data) }

Das ist der Aufruf derRPC-Routine im Server

Hier wird eine Routineaufgerufen, dessen Namein einem String vorliegt.

Das ist die Seite des Servers

call("proc",data)

call() ist nun die allgemeine Aufrufroutine

23VA – SS 2018 - Teil 7/RPC II

Call "Name"(Parameterliste) II

Stack

SP ->

result

ref data

ref "proc"

Return-Adr.

Parameter N

...

Parameter 2

Parameter 1

Name(String)

Situation direkt nach Aufrufder Routine Namens call:

result:= call("proc",data)

• Auf dem Stack ist der Platz für das Resultat reserviert.

• Der 1. Parameter (proc) ist die Adresse eines Strings.

• Der 2. (data) ist die Adresse einer Tabelle mit den Parametern der Routine, deren Namen als String vorliegt.

• ref data bedeutet: Adresse-von(data)

Situation direkt nachdem Aufruf von call()

24VA – SS 2018 - Teil 7/RPC II

Call "Name"(Parameterliste) III

push(Array an der Param-Addr)

Stack

SP ->

result

ref data

Return-Adr.

Parameter N

...

Parameter 2

Parameter 1

Parameter N

...

Parameter 2

Parameter 1

Der 1. Schritt in der Routine callbesteht darin, die Liste derParameter (2. Parameter) aufden Stack zu kopieren.Das erfolgt über die push-Operation.

ref "proc"

25VA – SS 2018 - Teil 7/RPC II

Call "Name"(Parameterliste) IV

push(Return-Adresse)

Stack

SP ->

result

Return-Adr.

Parameter N

...

Parameter 2

Parameter 1

Return-Adr.

Dieser Block wirdnach oben kopiert

Der 2. Schritt in der Routine callbesteht darin, die Rückkehradresseauf den Stack zu bringen.

Der 3. Schritt besteht darin, den unteren Bereich so weit nach obenzu kopieren, dass er unterhalb desResultats steht.Damit wird der graue Bereichüberschrieben.

ref data

ref "proc"

26VA – SS 2018 - Teil 7/RPC II

Call "Name"(Parameterliste) V

push(Array an Param-Addr)push(Return-Adresse)

Routine:= reflection(Name)copy(Block,Stack)pop(unterer Block)goto Routine

Stack

result

Parameter N

...

Parameter 2

Parameter 1

Return-Adr.

Situation desStacks direkt

vor goto

Bestimmen derAdresse derRoutine mit demNamen "Name"

Sprung direkt indie Routine

SP ->

Durch dieses Kopieren ist genau die Strukturauf dem Stack entstanden, die erforderlich ist,um die Routine direkt aufzurufen.

27VA – SS 2018 - Teil 7/RPC II

Call "Name"(Parameterliste) VI

proc call(Name,ParameterListe) { push(Array an Param-Addr) push(Return-Adresse) Routine-Adresse:= reflection(Name) copy(Block,Stack) pop(unterer Block) goto Routine-Adresse}

• Es wird der aktuelle Aufruf von call auf dem Stack beseitigt.

• Stattdessen wird anhand der Parameter ein anderer Aufruf simuliert.

• Die Rückkehradresse dieses neuen Aufrufs ist die alte zum Aufrufer der call-Routine.

• Das Ganze lässt sich nur in Assembler realisieren.

28VA – SS 2018 - Teil 7/RPC II

Was macht reflection(Name)? I

• Reflexion = Möglichkeit, die eigenen Programmstrukturen zur Laufzeit selbst zu analysieren und zu benutzen

• Introspektion = Selbstanalyse = Reflexion

• Die Routine reflection(Name) durchsucht die Symboltabellen zur Laufzeit nach dem Namen der Routine und liefert deren Adresse bzw. Null.

29VA – SS 2018 - Teil 7/RPC II

Was macht reflection(Name)? II

Name Adresse

"blabla" ref blabla

"abc" ref abc

... ...

Code

Code vonblabla()

Code vonabc()

Hohe Adressen

Niedrige Adressen

• Die Symboltabellen beinhalten die Zuordnung zwischen dem Namen zur Compile-Zeit und der Adresse im Code.

• Diese Tabellen liegen zur Laufzeit im RAM vor und werden bei der Reflexion durchsucht.

30VA – SS 2018 - Teil 7/RPC II

Was macht reflection(Name)? III

• reflection(Name) durchsucht also die Tabelle mit allen geladenen Routinen.

• Wenn der gesuchte Eintrag gefunden ist, wird die dort gespeicherte Start-Adresse zurück geliefert.

• Diese Startadresse wird dann als Wert beim goto benutzt.

proc call(Name,ParameterListe) { push(Array an Param-Addr) push(Return-Adresse) Routine-Adresse:= reflection(Name) copy(Block,Stack) pop(unterer Block) goto Routine-Adresse}

31VA – SS 2018 - Teil 7/RPC II

Wie geht das mit dem call in PHP?

• In PHP kann über eine Variable mit dem Inhalt des Namens der Routine indirekt eine Routine aufgerufen werden.

• Die Konstruktion mit der Parameterliste – wie oben - setzt ja Assembler voraus, was hier nicht geht; daher wird die Parameterliste als Array übergeben.

• In der Deklaration der aufzurufenden Routine muss dies berücksichtigt werden.

$routine= "blabla";$result= $routine($params);

function blabla($params) { ... ... $params[0]... ... return(...); }

Aufruf Deklaration

32VA – SS 2018 - Teil 7/RPC II

Wie geht das Laden in PHP?

function load($name) { require_once($name);}

function __autoload($Klasse) { require("$Klasse.php");}

Routinen Klassen

Dass dies so überschaubar ist, wird aber durch Performanz-Verluste erkauft:Der Compiler, der Lader und der Binder sind immer vorhanden undlaufen permanent mit.

33VA – SS 2018 - Teil 7/RPC II

Zusammenfassung

• Mit dem vorgestellten Verfahren kann genau der PHP-Mechanismus realisiert werden, der oben vorgestellt wurde:

und im Prinzip arbeitet auch so der PHP-Prozessor....

$routine= "blabla";$result= $routine($params);

entspricht routine= "blabla";result= call(routine,params);

34VA – SS 2018 - Teil 7/RPC II

Parallele Verarbeitung des receive() im Server I

• Der Router empfängt die Nachricht, generiert einen Thread und gibt an diesen die Nachricht zur Bearbeitung weiter. Im Router befindet sich die obige Endlos-Schleife mit einem wartenden receive().

• Alle Requests werden parallel verarbeitet, sofern genügend Threads vorhanden sind – ansonsten wartet der Router auf einen frei werdenden Thread.

Router

Empfang vonNachrichten

ProcessRequest

ProcessRequest

ProcessRequest

...

Generierenvon Threads

"Worker"

"Worker"

"Worker"

35VA – SS 2018 - Teil 7/RPC II

Parallele Verarbeitung des receive() im Server II

(1) proc Router(2) do {(3) msg:= receive()(4) tid:= new processCalls(msg)(5) }

thread processCalls(msg) proc:= msg.Name if exists(proc) { data:= unserialize(msg.ParamList) try result:= call "proc"(data) catch result:= Error } else { result:= Error } data:= serialize(result) reply(data)

36VA – SS 2018 - Teil 7/RPC II

Erläuterungen

• In Zeile (3) wird wartend(!) eine Nachricht empfangen.

• Dann wird in Zeile (4) für diese Nachricht ein neuer Thread erzeugt.

• Den Code der Threads zeigt die rechte Seite.

• Der Router erzeugt immer Threads ohne dass er auf deren Beendigung wartet. Jeder Thread beendet sich nach dem reply().

• Dann kommt er in den Pool für freie Threads.

• Wenn nun in Zeile (4) ein neuer Thread erzeugt wird, dann erfolgt dies nur dann, wenn im Pool ein freier Thread vorhanden ist, ansonsten wird auf einen frei werdenden gewartet.

• Dieser Mechanismus wird hier nicht dargestellt, auch fehlt die initiale Erzeugung der Threads für den Pool.

• Die maximale Threadanzahl ist daher begrenzt.

37VA – SS 2018 - Teil 7/RPC II

Alternative Realisierung

• Der Router schreibt die empfangene Nachricht in eine Warteschlange.

• Ist sie voll, wartet der Router auf einen freien Platz.

• Umgekehrt holen sich alle Threads die Aufträge aus der Warteschlange und legen sich schlafen, falls diese leer ist.

• Alle Threads werden initial generiert.

Router

Empfang vonNachrichten

Thread1

...

enqueue()

Thread2

ThreadN

dequeue()"Worker"

"Worker"

"Worker"

38VA – SS 2018 - Teil 7/RPC II

Hinweise

• Durch die parallele Verarbeitung von Nachrichten entstehen kritische Abschnitte, wenn auf gemeinsame Ressourcen, z.B. Dateien, zugegriffen wird.

• Daher sind Synchronisationen, wie Semaphoren, in den Routinen erforderlich.

• Im Prinzip arbeiten so alle Server, u.a. auch der Apache.

39VA – SS 2018 - Teil 7/RPC II

Eine komplexere Aufrufstruktur I

• Eine Komponente kann beide Rollen einnehmen: Als Server wird eine Routine ausgeführt, die wiederum eine Routine eines anderen Servers als Klient aufruft.

• Beim klassischen wartenden RPC sollten keine Probleme auftreten, da k2a() und k3a() nicht parallel ablaufen.

• Alle Komponenten haben einen Router und können daher die Aufrufe parallel verarbeiten.

Komponente1

Komponente2

Komponente3

Call k2a()Call k3a()

Call k2b()

Rollen:ServerClient

Rollen:Server

Rollen:Client

40VA – SS 2018 - Teil 7/RPC II

Eine komplexere Aufrufstruktur II

• Bei asynchronen send()-Operationen entsteht innerhalb der Komponente 2 ein kritischer Abschnitt, denn k2a() und k2b() können in verschiedenen Threads parallel dort ausgeführt werden.

• Die letzten beiden Fälle gelten für Parallelität innerhalb einer verteilten Anwendung mit privaten Daten.

Komponente1

Komponente2

Komponente3

Call k2a()Call k3a()

Call k2b()

Rollen:ServerClient

Rollen:Server

Rollen:Client

41VA – SS 2018 - Teil 7/RPC II

Eine komplexere Aufrufstruktur III

• Was ist, wenn dieselbe Komponente von mehreren unabhängigen Anwendungen benutzt wird?

• Die Daten der Anwendung1 müssen von den Daten der Anwendung2 getrennt – besonders innerhalb von der Komponenten3 - werden.

Komponente1Komponente2

Komponente3

Call k2a()Call k2a()

Anwendung1 Anwendung2

42VA – SS 2018 - Teil 7/RPC II

Wie lässt sich das realisieren? I

• Es werden innerhalb einer Anwendung bei der erstmaligen Benutzung einer Komponente mit einer Art new() eine Anwendungs-lokale Instanz der betreffenden Komponente gebildet. Mit dieser Instanz werden auch anwendungslokale Daten erzeugt.

• Eine Anwendung besteht dann aus mehreren Instanzen von Komponenten auf mehreren Knoten.

• Oder anders formuliert: Jede laufende Anwendung ist eine Instanz einer Anwendungsdefinition.

• Alle Instanzen der Anwendung bekommen dieselbe eindeutige ID, so dass anhand dieser ID festgestellt werden kann, welche Instanz einer Komponente zu welcher Anwendungsinstanz gehört.

43VA – SS 2018 - Teil 7/RPC II

Wie lässt sich das realisieren? II

• Bei jedem RPC und jeder Nachricht wird die ID der Anwendung mitgeteilt, so dass der aufgerufene Server die richtige Instanz bestimmen und dieser die Nachrichten zur Verarbeitung übergeben kann.

• Falls keine Instanz zu der ID auf einem Knoten existiert, wird sie dynamisch erzeugt, um dann wie im vorherigen Schritt fortzufahren.

• Bei diesem Verfahren breitet sich die verteilte Anwendung schrittweise über das Netz aus.

• Es ist aber möglich, dass Instanzen von Komponenten in mehreren Anwendungen gemeinsam benutzt werden, dann entfällt das dynamische Generieren einer Komponenteninstanz.

Strategie 1: Dynamisches Erzeugen

44VA – SS 2018 - Teil 7/RPC II

Wie lässt sich das realisieren? III

• Zum Start einer Applikation läuft ein Konstruktor-Prozess (Booter), der alle Instanzen aller benutzten Komponenten auf den verteilten Knoten erzeugt. Erst dann läuft die Applikation los.

• Dieses Hochfahren (Boot) wird durch eine Datei gesteuert.

• Für die Applikation und für jede Komponente gibt es einen Management-Teil, der die jeweiligen Konstruktoren lokal aufruft.(Siehe dazu den Teil über die Container für Komponenten)

• Dieses Vorgehen hat den Nachteil, dass es nur starre Strukturen von Anwendungen erlaubt.

• Es hat aber den Vorteil, dass gemeinsam benutzte Komponenten leicht berücksichtigt werden können.

Strategie 2: Erzeugen durch einen Konstruktor/Booter

45VA – SS 2018 - Teil 7/RPC II

Eine komplexere Aufrufstruktur IV - Rekursion

• Beim klassischen wartenden RPC ohne Parallelität auf der Seite des Servers entsteht bei Rekursion ein Deadlock.

• Mit Parallelität benutzt jeder Aufruf einen anderen Thread, der bei Benutzung gemeinsamer Ressourcen in kritische Abschnitte eintreten.

• Damit entsteht eine Kette von einander abhängigen Threads über verschiedene Systeme verteilt.

Komponente1

Komponente2

Call k1a()

Call k2a()

Rollen:ServerClient

Rollen:ServerClient

46VA – SS 2018 - Teil 7/RPC II

Behandlung von Rekursion

• Einfach verbieten :-( Es ist sowieso eine Deadlock-Erkennung notwendig...

• Die Behandlung jeder Nachricht in einen eigenen Thread legen, wie beim nicht-wartenden Server. Es entstehen kritische Abschnitte.

Lösungen

Thread1App1

Thread2App1

ThreadNApp1

LokaleDaten

Globale Daten von App1

LokaleDaten

LokaleDaten

Thread1App2

Thread2App2

LokaleDaten

Globale Daten von App2

LokaleDaten

47VA – SS 2018 - Teil 7/RPC II

Also...

• Jede Instanz einer verteilten Applikation erhält eine eindeutige ID.

• Alle verteilten Komponenten dieser Instanz erhalten dieselbe ID.

• Diese ID entspricht einer Session bzw. einem Kontext, innerhalb dessen die RPCs und die Nachrichten verarbeitet werden.

• Jede Instanz muss hochgefahren (gebootet) werden.

• Jede Instanz muss heruntergefahren (shutdown) werden.

48VA – SS 2018 - Teil 7/RPC II

Instanzen ohne ID

• Wenn eine Instanz einer Komponente keine ID hat, ist sie auch keiner verteilten Anwendung zugeordnet.

• Derartige Instanzen können dann gemeinsam von allen Anwendungen benutzt werden.

• Jetzt wäre noch der Fall möglich, dass eine Komponenten-Instanz zu einer Teilmenge von Applikationen gehört; dieser Instanz wird dann eine Menge von Applikationen-IDs zugeordnet, die diese Komponente benutzen können.

• Zur Verwaltung, welche Instanz zu welcher Anwendung mit welcher ID gehört, sind Verzeichnisse notwendig. Die dafür notwendigen Namensdienste werden später behandelt.

49VA – SS 2018 - Teil 7/RPC II

Verteilte Anwendungen

ObjectID1

ObjectID1

ObjectID1

ObjectID1

ObjectID2

ObjectID2

ObjectID2

Object

ObjectID1+2

Anwendung ID1

Anwendung ID2

Zwei verteilte Anwendungen mit zwei gemeinsam benutzten Objekten.Alle Objekte sind Instanzen von Komponenten, die irgendwo im Netzresidieren.

Diese Komponente wird von allen Anwendungengemeinsam benutzt.

Diese Komponente wird nur von ID1 und ID2gemeinsam benutzt.

50VA – SS 2018 - Teil 7/RPC II

Leider fehlt noch etwas...

• Anhand der ID der Anwendung wird die Nachricht einer Instanz (Objekt) auf einem Server zugeordnet – schön.

• Aber bei Rekursion zwischen zwei Knoten kommt diese Zuordnung durcheinander, denn es müssen Daten den einzelnen Aufrufen zugeordnet werden.

• Dazu das folgende Szenario zwischen zwei Rechnern Alice und Bob – beide Objekte gehören zur selben Anwendung.

51VA – SS 2018 - Teil 7/RPC II

Das RPC-Protokoll bei Rekursion I

send() receive()

receive()

Request

reply()

send()receive()

reply()

Request

send() Request receive()

reply()receive()

receive()

Response

Alice Bob

Von welchemAufruf ist dasdas Ergebnis?

Response

Response

Die bunten Striche zeigen die drei Threads während ihres Ablaufs.Request: initiale Nachricht eines RPC, Response: Rückantwort eines RPCs

52VA – SS 2018 - Teil 7/RPC II

Das RPC-Protokoll bei Rekursion II

• Die Routine A auf Alice ruft die Routine B von Bob auf (grün).

• Während der Ausführung von B auf Bobs Rechner ruft Bob die Routine A von Alices Rechner auf (lila).

• Innerhalb von A auf Alices Rechner wird die Routine B von Bob aufgerufen (braun)

• Also liegt eine Rekursion vor: A() -> B(grün) -> A(lila) -> B(braun)

• Es wird also 2x B() auf Bob aufgerufen. Auf dem Rückweg von Bob zu Alice (Response) ist nicht klar zu welchem Thread das Ergebnis gehört,

• es sei denn, jeder Aufruf bekommt eine eindeutige ID; dann kann beim receive() eines Responses festgestellt werden, von welchem Aufruf dies das Ergebnis ist.

53VA – SS 2018 - Teil 7/RPC II

Das RPC-Protokoll bei Rekursion III

• Jeder RPC wird daher mit einer eindeutigen ID des Aufrufs versehen. Beim Empfang einer Nachricht wird diese dann dem richtigen Thread zugeordnet, wenn dort auf eine Antwort mit der betreffenden ID gewartet wird.

• Diese Routine heiße receiveReply().

• Jetzt könnte das send() auch immer ein no-wait-send() sein, während receive*()-Operationen immer wartend sind...

send()

receive()

reply()

receiveReply()

54VA – SS 2018 - Teil 7/RPC II

Die vielen IDs

• Nun kann auch ein Klient mehrere nicht-wartende send() ausführen und trotzdem die Antworten richtig anhand der ID seinen eigenen Routinen zuordnen.

• In jeder Nachricht befinden sich also zwei IDs:– ID der Anwendung (Adressierung der Instanzen)

– ID des Aufrufs (RPC) bzw. der Nachricht

• Es fehlt noch die ID der Epoche – hier wird davon ausgegangen, dass alle Beteiligten durchhalten.

• Falls dies nicht so ist, müssen Bereiche von Aufruf-IDs jeweils den Epochen zugeordnet werden.

55VA – SS 2018 - Teil 7/RPC II

Nach dieser Anstrengung etwas Entspannung....