Alcatraz

Aus Callooh Wiki
Zur Navigation springen Zur Suche springen

Architektur des Gesamtsystems

Technologie

Zur Kommunikation der Clients untereinander, sowie der Clients mit den Servern wird Java RMI eingesetzt. Die Kommunikation zwischen den Servern erfolgt ausschließlich via Spread, über serialisierte Objekte und Text Nachrichten in SpreadMessages.

Server Struktur

Ein Server fungiert als Koordinator (siehe #Koordinator und Election).

Anfragen von Spiele-Clients können an jeden der Server ergehen, allerdings müssen diese an den Koordinator weitergeleitet werden. Nur der Koordinator entscheidet ob eine Registrierung erfolgreich ist, und propagiert in diesem Fall die erfolgte Registrierung an alle Server. Dies entspricht einem Primary-Backup Protokoll.

Koordinator und Election

Variante 1: Spread

Der Koordinator ist bekannt - über die getMembers Methode des MembershipInfo Objekts kann die Liste der private Groups bezogen werden, der jeweils erste in dieser Liste ist der Koordinator. Sobald durch Änderung der Mitglieder ein neuer Koordinator "gewählt" wird, sendet der bisherige Koordinator diesem sein ServerState Objekt. Der neue Koordinator wird erst aktiv, sobald er das ServerState Objekt erhalten hat.

Diese Variante wird im Alcatraz Projekt eingesetzt

Variante 2: Bully Algorithmus

Sobald ein Server über ein zu konfigurierendes Zeitinterval hinweg keine Coordinator Message erhält, muss ein Ausfall des als Koordinator fungierenden Servers angenommen werden.

In diesem Fall sendet der Server, der diesen Ausfall bemerkt, einen ElectionRequest als Multicast Message an die Multicast Gruppe. (Der Versand des Requests sollte jedoch um ein zufälliges Intervall verzögert werden, und falls in diesem Interval bereits ein ElectionRequest eines anderen Servers eingeht, storniert werden)

Jeder Server hat lokal eine Referenz auf ein Coordinator Objekt. Ist diese Referenz null wird ebenfalls ein ElectionRequest ausgelöst, falls nicht innerhalb eines bestimmten Zeitraums eines ankommt.

Solange kein gültiges Coordinator Objekt als lokale Referenz vorhanden ist, dürfen Server keine Requests von Clients annehmen. Eventuell ist für diesen Fall ein temporärer Fehler über eine spezielle Exception an den Client zu melden.

Erhält ein Server einen ElectionRequest, antwortet er mit einem ElectionResponse an die MultiCast Gruppe. Die Identität des Response Objekts wird dabei mit den Werten der ElectionRequest Identität initialisiert.

Hat ein Server ein ElectionRequest Objekt und ElectionResponse Objekte erhalten (nach konfiguriertem ElectionTimout), wird ausgewertet welches der Response Objekte den höchsten uptime Wert aufweist. Der entsprechende Absender, identifiziert durch das hostname Attribut ist der neue Koordinator. Dieser sendet ein Coordinator Objekt an die Multicast Gruppe. Erhält ein Server ein Coordinator Objekt, obwohl er bereits eine gültige Referenz auf ein Coordinator Objekt besitzt, wird das zuletzt eingegangene Objekt als neuer Koordinator gesetzt. Weisen innerhalb einer Election mehrere Server den gleichen uptime Wert auf, inkrementieren die betroffenen Server diesen Wert um einen zufälligen Wert, und senden danach erneut eine ElectionResponse Objekt.

Synchronisation nach (Re-)Join

Wenn ein Server Objekt die Multicast Gruppe joint (hier wird durch Spread automatisch eine Membership-Message ausgelöst), sendet der Koordinator diesem ein ServerState Objekt. Diese beinhaltet unter anderem die PlayerList, eine Liste der Namen der in der Registry registrierten Objekte, den Koordinator-Host, die Signatur (ein Spread PrivateGroup Objekt), und die momentanen Game- und Player- IDs.

Der Server führt daraufhin folgenden Aktionen durch:

  • setzen des ServerState Objekts
  • starten des lokalen Registry
  • Synchronisieren lokalen Registry mit der Registry, die vom Koordinator verwaltet wird anhand der Liste der registrierten Objekte.
  • starten des Discovery Services (DSCResponder Objekt)

RMI Registry

  • Jeder Server hat eine lokale Registry.
  • Jeder Server initialisiert ein RegistryServer Objekt und registriert es bei seiner lokalen Registry.

Jedes RegistryServer Objekt hält eine Referenz auf ein ServerState Objekt, welches in weiterer Folge nur auf Anweisung des als Koordinator fungierenden Servers aktualisiert wird.

  • Jeder Server fungiert als Registry-Proxy für die Clients (siehe #RMI Proxy)

RMI Proxy

Jeder Server fungiert als RMI Proxy für einen Client. Jeder Bind-Request, der an einen der Registry-Proxies ergeht, wird an den Koordinator weitergeleitet. Dieser vergibt einen eindeutigen Objektnamen und sendet das zu registrierende Objekt gemeinsam mit dem Objektnamen an alle übrigen Server, die das Objekt unter diesem Namen in ihre lokalen RMI Registry importieren.

Die RMI-Proxy Funktionalität (bind-Methode) wird vom RegistryServer Objekt bereitgestellt.

In den meisten Fällen wird diese Proxy Funktionalität implizit vom Server aufgerufen, für den Client besteht bislang keine Notwendigkeit die bind-Methode explizit aufzurufen.

Ablauf

Ein RMI Proxy erstellt ein BindRequest Objekt, und sendet dieses via Multicast Message. Dieses Objekt enthält das zu bindende Objekt und eine systemweit eindeutige Objekt-ID (OID Objekt). Jeder Server speichert den BindRequest vorerst lokal in der liste der zu behandelnden Request Objekte. Der Koordinator erstellt ein BindOrder Objekt, das als Attribut neben der Objekt-ID auch ein Attribut mit der URI (RMI URL ohne Protokoll, Hostname und Port) des zu bindenden Objekts enthält. Das BindOrder Objekt wird via Multicast Message gesendet. Sobald Server eine BindOrder erhalten, registrieren sie das enthaltene Remote Objekt unter dem angegebenen Namen und löschen das gespeicherte Request Objekt.

Der Ablauf als Sequenz-Diagramm:

Lokalisieren und Kontaktieren eines Registry Servers

Clients senden ein UDP Packet an eine konfigurierbare IP Adresse, auf einen konfigurierbaren Port.

Server lauschen auf den definierten IP Adressen (und Ports). Diese Adresse ist optimalerweise (aber nicht notwendigerweise) eine Broadcast Adresse. Sobald ein Server ein solches DISCOVER Packet empfängt, sendet er die RMI URL zu seinem RegistryServer Objekt via UDP Packet an die Versender-IP-Adresse, auf einen konfigurierten Port, zurück.

Ablauf der Kommunikation zwischen den RegistryServern bzw. zwischen Coordinator und RegistryServer

Paket Diagramm

Klassen-Diagramme Server

Spezifikation des Ablaufs zwischen Client und Registrierungsserver zum Starten des Spiels

Es gibt mehrere Phasen:


Voraussetzung für diese Phasen ist, dass die Registrierungsserver online sind, und das RegistryServer Object in der RMI Registry registriert haben.

Registrierungs-Phase

Eine Registrierung eines Clients ist jederzeit möglich. In der Pre-Registrierungs-Phase ist jedoch der Timer, der den Ablauf der Registrierungs-Phase signalisiert noch nicht gestartet. Eine erfolge Registrierung in der Pre-Registrierungs-Phase startet die Registrierungs-Phase.

Clients versuchen, nachdem sie ein RegistryServer Object erhalten haben, sich über dieses zu registrieren.

  • Der Client erstellt ein lokales Registration Objekt.
  • vom Client wird die registerRequest Methode des RegistryServer Objekts aufgerufen. Diese Methode erhält als Parameter das Registration Objekt.
  • dieser Request wird an den Koordinator weitergeleitet
  • der Koordinator registriert das Registration Objekt in der RMI Registry und sendet Objekt und vergebenen Namen an die anderen Server.
  • der Koordinator prüft den Request nach folgenden Voraussetzungen:
    • ist der Name des Spielers noch frei
    • ist die Maximal-Anzahl der Spieler bereits erreicht
    • ist die Registrierungsphase bereits abgeschlossen
  • der Koordinator ruft die deny oder accept Methode des Registration Objects auf, und propagiert diese Entscheidung an die anderen Server

Die Registrierungsphase läuft ab wenn:

  • eine bestimmte Zeit vergangen ist
  • die maximale Spieranzahl erreicht ist

Sobald die Registrierungsphase abgelaufen ist, beginnt die Initialisierungs-Phase. Der Beginn dieser Phase wird den Clients vom Koordinator (über dem Aufruf der Methode startInitPhase am Request Objekt) mitgeteilt, und an die anderen Server propagiert.

Initialisierungs-Phase

  • Clients initialisieren ein GameClient Objekt
  • Clients übergeben das GameClient Objekt an die setGameClient Methode des RegistryServer Objekts. Der Server überprüft anhand der getPlayerName Methode des GameClient Objekts ob diese Aktion überhaupt erlaubt ist.
  • Clients erhalten GameClient Objekte ihrer Mitspieler über eine Methode des Registration Objekts, die vom Coordinator aufgerufen wird sobald alle aller Mitspieler registriert sind und ihren GameClient gesetzt haben.
  • Clients erhalten über die vom Coordinator aufgerufene Methode setPlayerID des GameClient Objekts ihre Player-ID (diese wird dem Alcatraz Objekt übergeben)
  • Mit dem Übertragen der GameClient Objekte endet die Aufgabe des Servers, und das Spiel beginnt

Gesamt-Ablauf als Sequenz-Diagramm


Spezifikation des Ablaufs zwischen Client und Client zum Austausch der Züge

  1. jeder Client erzeugt ein lokales at.falb.games.alcatraz.api.Alcatraz Object
  2. jeder Client erzeugt ein GameClient Objekt, das eine Referenz auf das Alcatraz Object erhält. Dieses Objekt kann als RMI fähiges Pendant zum MoveListener verstanden werden. Es implementiert eine moveDone() Methode, in der die doMove(..) Methode am Alcatraz Objekt aufgerufen wird (damit auch lokal am GUI der Zug des anderen Spielers sichtbar wird). Die moveDone() wird remote von den anderen Spielern aufgerufen.
  3. jeder Client erzeugt ein Objekt, das at.falb.games.alcatraz.api.MoveListener impementiert. In der Methode moveDone() erfolgt ein Aufruf der moveDone() Methoden der GameClient-Objekte der anderen Spieler.
  4. das GameClient Objekt wird dem RegistryServer übergeben.
  5. der Spieler erhält vom RegistryServer die GameClient Objekte der anderen Spieler
  6. der Spieler erhält vom RegistryServer die Player-ID, die die Reihenfolge der Spieler bestimmt.
  7. Am Alacatraz Objekt werden lokal die Methoden init() und start() aufgerufen, um das Spiel zu starten
  8. Während ein Client auf Züge der anderen Spieler wartet, prüft er nach einer gewissen Zeit, ob sein Nachbar (jeder GameClient achtet auf genau einen Nachbar-GameClient) noch erreichbar ist. Antwortet dieser über einen zu definierenden Zeitraum nicht, wird auf allen Remote GameClient Objekten die abort() Methode aufgerufen, und das Spiel daraufhin abgebrochen.
  9. Kann ein Client seinen Zug nicht an alle anderen GameClient Objekte propagieren, erfolgt nach einer gewissen Zeit ebenfalls ein Spiel Abbruch.

Klassendiagramm Client

Spezifikation der Mechanismen um Serverausfälle tolerieren zu können

  • jeder Server hat eine lokale RMI Registry. Die RMI Registries der Server sind synchron.
  • das RegistryServer Objekt ist redundant. Registrierungen eines Clients werden über ein mit Spread implementiertes Primary-Backup Protokoll repliziert.
  • Jeder Server hat eine lokale Kopie der unbehandelten Request Objekte. Wird ein Response Objekt erhalten, wird das entsprechenden Request Objekt aus der Liste entfernt. Neue Koordinatoren arbeiten die Liste mit den offenen Requests ab.
  • Ein Koordinator ist jederzeit vorhanden. Dieser sendet seinen State bei Bedarf als Multicast Message an alle Server.
  • Server, die der Multicast Gruppe beitreten (nach crash wieder verfügbar, oder neuer Server), synchronisieren die lokale RMI Registry mit jener des als Koordinator fungierenden Servers. Erst dann sind sie vollwertiges (und von Clients nutzbares) Mitglied der Multicast-Gruppe.

Remote Interface-Definition des Registrierungsservers (inkl. typisierten Methodenparametern)

public interface RegistryServer extends java.rmi.Remote {

    boolean registerRequest(Registration r) 
            throws java.rmi.RemoteException;
    
    void setGameClient(GameClient g, int payerID, int gameID)
            throws java.rmi.RemoteException;   

    boolean unregister(int playerID, int gameID)
            throws java.rmi.RemoteException;
}

Remote Interface-Definition des Game Clients (inkl. typisierten Methodenparametern)

Registration:

public interface Registration extends java.rmi.Remote, Serializable {
    String getPlayerName() throws java.rmi.RemoteException;
    void accept() throws java.rmi.RemoteException;
    void deny(String msg) throws java.rmi.RemoteException;
    void startInitPhase() throws java.rmi.RemoteException;
    void startGame(GameClient[] g) throws java.rmi.RemoteException;
    void setPlayerID(int id) throws java.rmi.RemoteException;
    int getPlayerID() throws java.rmi.RemoteException;
    void setGameID(int id) throws java.rmi.RemoteException;
    int getGameID() throws java.rmi.RemoteException;
}

GameClient:

public interface GameClient extends java.rmi.Remote {
    void moveDone(at.falb.games.alcatraz.api.Player player,
                  at.falb.games.alcatraz.api.Prisoner prisoner,
                  int rowOrCol,
                  int row,
                  int col) throws java.rmi.RemoteException;  

    /**
     * get the player name
     * @return String
     * @throws java.rmi.RemoteException
     */
    String getPlayerName() throws java.rmi.RemoteException; 

    /**
     * set player id
     * @param id
     * @throws java.rmi.RemoteException
     */
    void setPlayerID(int id) throws java.rmi.RemoteException; 
 
    /**
     * get player id
     * @return int
     * @throws java.rmi.RemoteException
     */
    int getPlayerID() throws java.rmi.RemoteException; 

    /**
     * check if client is alive
     * @return boolean
     * @throws java.rmi.RemoteException
     */
    boolean isAlive() throws java.rmi.RemoteException;

    /**
     * abort game
     * @param reason
     * @throws java.rmi.RemoteException
     */
    void abort(String reason) throws java.rmi.RemoteException;
}

Links

Alcatraz svn

Projekt Javadoc

Spread Javadoc

Alcatraz Javadoc

Protokoll der Präsentation vom 9.3.2011

Frage Froihofer: wodurch kommt der Name zustande, unter dem der Client in der Registry gebunden wird? (Folie 3)

  • AW: kommt noch

Frage Froihofer: RMI Registry außerhalb von Server und Client gezeichnet? (Folie 4)

  • AW: jeder Server schreibt lokal in sein Registry Server Object

Frage Froihofer: dh bei Ausfall des Koordinators ist der Client dafür zuständig, neu zu registrieren? Wer könnte feststellen dass er gecrashed ist?

  • AW: wenn der Client direkt auf den Koordinator geht, gibt es keine andere Möglichkeit. Ist einer dazwischen, könnte der einfach an den neuen Koordinator weiterleiten

Frage Froihofer: woher wissen wir (Interfaces), welche ID wir verwenden müssen?

  • Aw: die eindeutige Spieler ID wird von Registration Object zurück gegeben

Frage Froihofer: warum muss man das Registration Object in der Phase binden? Was steht in den Backup Messages?

  • AW... ausreichend offenbar :D

Frage Froihofer: warum 2 Methoden accept, deny und nicht gleich über das Registration Object weitergeben? Was passiert, wenn genau da das Netzwerk weg ist?

  • AW: er würde darüber nachdenken, es direkt ins Registration Object hineinzugeben, dann ist ein Request weniger.