Die Replikation unterstützt Fehlertoleranz und erhöht die Leistung für eine verteilte eXtreme-Scale-Topologie. Die Replikation wird durch die Zuordnung von BackingMaps zu einem MapSet aktiviert.
Ein MapSet ist eine Sammlung von Maps, die anhand eines Partitionsschlüssels kategorisiert werden. Dieser Partitionsschlüssel wird mit der folgenden Formel aus dem Schlüssel der jeweiligen Map abgeleitet: Hash Modulo Partitionsanzahl. Wenn eine Gruppe von Maps im MapSet den Partitionsschlüssel X hat, werden diese Maps in einer entsprechenden Partition X im Datengrid gespeichert. Wenn eine andere Gruppe den Partitionsschlüssel Y hat, werden alle Maps in Partition Y gespeichert usw. Die Daten in den Maps werden basierend auf der im MapSet definierten Richtlinie repliziert. Die Replikation findet in verteilten Topologien statt.
Den MapSets werden die Anzahl der Partitionen und eine Replikationsrichtlinie zugeordnet. Die Replikationskonfiguration für das MapSet gibt die Anzahl synchroner und asynchroner Replikat-Shards an, die das MapSet zusätzlich zum primären Shard haben muss. Sind beispielsweise ein synchrones und ein asynchrones Replikat vorhanden, wird für alle BackingMaps, die dem MapSet zugeordnet sind, jeweils automatisch ein Replikat-Shard in der Gruppe verfügbarer Container-Server für das Datengrid verteilt. Die Replikationskonfiguration kann Clients auch das Lesen von Daten aus synchron replizierten Servern ermöglichen. Auf diese Weise kann die Last der Leseanforderungen auf zusätzliche Server in eXtreme Scale verteilt werden. Die Replikation hat nur dann Auswirkung auf das Programmiermodell, wenn die BackingMaps vorher geladen werden.
void preloadMap(Session session, BackingMap backingMap) throws LoaderException;
Maps können in N Partitionen partitioniert werden. Deshalb können Maps auf mehrere Server verteilt werden, wobei jeder Eintrag mit einem Schlüssel gekennzeichnet wird, der nur in einem einzigen dieser Server gespeichert wird. Sehr große Maps können in eXtreme Scale verwaltet werden, weil die Anwendung nicht mehr durch die Heapspeichergröße einer einzigen JVM beschränkt ist, die alle Einträge einer Map enthält. Anwendungen, die den Preload-Prozess mit der Methode "preloadMap" der Schnittstelle "Loader" ausführen möchten, müssen den Teil der Daten angeben, die vorher geladen werden sollen. Es ist immer eine feste Anzahl an Partitionen vorhanden. Sie können diese Zahl anhand des folgenden Codebeispiels bestimmen:
int numPartitions = backingMap.getPartitionManager().getNumOfPartitions();
int myPartition = backingMap.getPartitionId();
Dieses Codebeispiel
zeigt, dass eine Anwendung den Teil der Daten angeben kann, der vorher aus der Datenbank geladen werden soll.
Anwendungen müssen diese Methoden auch dann verwenden, wenn die Map zunächst nicht partitioniert ist.
Diese Methoden bieten Flexibilität: Wenn die Map später von den Administratoren partitioniert wird, funktioniert der Loader weiterhin ordnungsgemäß.Die Anwendung muss Abfragen absetzen, um den Teil myPartition aus dem Back-End abzurufen. Wenn eine Datenbank verwendet wird, kann es unter Umständen einfacher sein, eine Spalte mit der Partitionskennung für einen bestimmten Datensatz zu haben, sofern es keine natürliche Abfrage gibt, mit der die Daten in der Tabelle einfach partitioniert werden können.
Die Preload-Implementierung kopiert Daten aus dem Back-End in die Map, indem sie mehrere Objekte in der Map in einer einzigen Transaktion speichert. Die optimale Anzahl der pro Transaktion zu speichernden Datensätze richtet sich nach mehreren Faktoren, einschließlich der Komplexität und der Größe. Wenn die Transaktion beispielsweise Blöcke mit mehr als 100 Einträgen enthält, nehmen die Leistungsgewinne ab, wenn Sie die Anzahl der Einträge erhöhen. Zur Bestimmung der optimalen Anzahl beginnen Sie mit 100 Einträgen, und erhöhen Sie dann die Anzahl, bis keine Leistungsgewinne mehr zu verzeichnen sind. Mit größeren Transaktionen kann eine bessere Replikationsleistung erzielt werden. Denken Sie daran, dass der Preload-Code nur im primären Shard ausgeführt wird. Die vorher geladenen Daten werden über das primäre Shard in allen Replikaten repliziert, die online sind.
Wenn die Anwendung ein MapSet mit mehreren Maps verwendet, hat jede Map einen eigenen Loader. Jeder Loader besitzt eine Preload-Methode. Alle Maps werden nacheinander von eXtreme Scale geladen. Es kann effizienter sein, den Preload-Prozess für die Maps so zu gestalten, dass eine einzige Map als Map bestimmt wird, in die die Daten vorher geladen werden. Dieser Prozess ist eine Anwendungskonvention. Beispiel: Die beiden Maps "department" (Abteilung) und "employee" (Mitarbeiter) könnten beide den Loader der Map "department" verwenden. Bei dieser Prozedur wird über Transaktionen gewährleistet, dass in dem Fall, dass eine Anwendung eine Abteilung abrufen möchte, sich die Mitarbeiter für diese Abteilung im Cache befinden. Wenn der Loader der Map "department" eine Abteilung vorher aus dem Back-End lädt, ruft er auch die Mitarbeiter für diese Abteilung ab. Das department-Objekt und die zugehörigen employee-Objekte werden dann der Map in einer einzigen Transaktion hinzugefügt.
Einige Kunden haben sehr große Datenmengen, die zwischengespeichert werden müssen. Das vorherige Laden dieser Daten kann sehr zeitaufwendig sein. Manchmal muss das vorherige Laden abgeschlossen sein, bevor die Anwendung online gehen kann. In diesem Fall können Sie von einem wiederherstellbaren vorherigen Laden profitieren. Angenommen, es gibt Millionen Datensätze, die vorher geladen werden müssen. Die Daten werden vorab in das primäre Shard geladen, und der Prozess scheitert bei Datensatz 800.000. Normalerweise löscht das als neue primäre Shard ausgewählte Replikat den Replikationsstatus und beginnt von vorne. eXtreme Scale kann eine Schnittstelle "ReplicaPreloadController" verwenden. Der Loader für die Anwendung muss auch die Schnittstelle "ReplicaPreloadController" implementieren. Das folgende Beispiel fügt dem Loader eine einzige Methode hinzu: Status checkPreloadStatus(Session session, BackingMap bmap);. Diese Methode wird von der Laufzeitumgebung von eXtreme Scale aufgerufen, bevor die Methode "preload" der Schnittstelle "Loader" aufgerufen wird. eXtreme Scale prüft das Ergebnis dieser Methode (Status), um das Verhalten festzulegen, wenn ein Replikat in ein primäres Shard hochgestuft werden muss.
Zurückgegebener Statuswert | Antwort von eXtreme Scale |
---|---|
Status.PRELOADED_ALREADY | eXtreme Scale ruft die Methode "preload" gar nicht auf, weil dieser Statuswert anzeigt, dass der Preload-Prozess für die Map bereits vollständig abgeschlossen ist. |
Status.FULL_PRELOAD_NEEDED | eXtreme Scale löscht die Map und ruft ganz regulär die Methode "preload" auf. |
Status.PARTIAL_PRELOAD_NEEDED | eXtreme Scale belässt die Map im aktuellen Zustand und ruft die Methode "preload" auf. Diese Strategie ermöglicht dem Loader der Anwendung, den Preload-Prozess an der Stelle fortzusetzen, an der er zuvor abgebrochen wurde. |
Es ist logisch, dass ein primäres Shard beim vorherigen Laden von Daten in die Map einen Status in einer Map im MapSet hinterlassen muss, das repliziert wird, so dass das Replikat den zurückzugebenden Status bestimmen kann. Sie können dazu eine zusätzliche Map verwenden, die z. B. den Namen RecoveryMap hat. Diese RecoveryMap muss zu demselben MapSet gehören, das vorher geladen wird, um sicherzustellen, dass die replizierte Map mit den vorher geladenen Daten konsistent ist. Eine empfohlene Implementierung folgt.
Während der Preload-Prozess die einzelnen Datensatzblöcke festschreibt, aktualisiert er im Rahmen dieser Transaktion auch einen Zähler oder Wert in der RecoveryMap. Die vorher geladenen Daten und die RecoveryMap-Daten werden automatisch in den Replikaten repliziert. Wenn das Replikat in ein primäres Shard hochgestuft wird, kann es anhand der RecoveryMap prüfen, was passiert ist.
Die RecoveryMap kann einen einzigen Eintrag mit dem Statusschlüssel enthalten. Wenn kein Objekt für diesen Schlüssel vorhanden ist, müssen Sie eine vollständige Operation preload (checkPreloadStatus returns FULL_PRELOAD_NEEDED) durchführen. Wenn ein Objekt für diesen Statusschlüssel vorhanden ist und der Wert COMPLETE lautet, ist der Preload-Prozess abgeschlossen, und die Methode checkPreloadStatus gibt PRELOADED_ALREADY zurück. Andernfalls zeigt das Wertobjekt an, wo der Preload-Prozess erneut gestartet werden muss, und die Methode checkPreloadStatus gibt PARTIAL_PRELOAD_NEEDED zurück. Der Loader kann den Wiederherstellungspunkt in einer Instanzvariablen speichern, so dass der Loader beim Aufruf der Methode "preload" diesen Ausgangspunkt kennt. Die RecoveryMap kann auch einen Eintrag pro Map enthalten, wenn jede Map gesondert vorher geladen wird.
Die Laufzeitumgebung von eXtreme Scale ist so konzipiert, dass festgeschriebene Daten beim Ausfall des primären Shards nicht verloren gehen. Im folgenden Abschnitt werden der verwendeten Algorithmen beschrieben. Diese Algorithmen gelten nur, wenn eine Replikationsgruppe die synchrone Replikation verwendet. Ein Loader ist optional.
Die Laufzeitumgebung von eXtreme Scale kann so konfiguriert werden, dass alle Änderungen in einem primären Shard synchron in den Replikaten repliziert werden. Wenn ein synchrones Replikat verteilt wird, erhält es eine Kopie der vorhandenen Daten im primären Shard. In dieser Zeit empfängt das primäre Shard weiterhin Transaktionen und kopiert sie asynchron in das Replikat. Das Replikat wird in dieser Zeit nicht als online eingestuft.
Wenn das Replikat denselben Stand wie das primäre Shard hat, wechselt das Replikat in den Peer-Modus, und die synchrone Replikation beginnt. Jede im primären Shard festgeschriebene Transaktion wird an die synchronen Replikate gesendet, und das primäre Shard wartet auf eine Antwort jedes Replikats. Eine synchrone Festschreibungsfolge mit einem Loader im primären Shard setzt sich aus den folgenden Schritten zusammen:
Schritt mit Loader | Schritt ohne Loader |
---|---|
Sperren für Einträge abrufen | Identisch |
Änderung mit Flush an den Loader übertragen | Nulloperation |
Änderungen im Cache speichern | Identisch |
Änderungen an Replikate senden und auf Bestätigung warten | Identisch |
Festschreibung im Loader über das TransactionCallback-Plug-in | Methode "commit" des Plug-ins wird zwar aufgerufen, führt aber keine Aktion aus |
Sperren für Einträge freigeben | Identisch |
Beachten Sie, dass die Änderungen an das Replikat gesendet werden, bevor sie im Loader festgeschrieben werden. Um zu bestimmen, wann die Änderungen im Replikat festgeschrieben werden, ändern Sie diese Folge. Initialisieren Sie während der Initialisierung die Transaktionslisten im primären Shard wie folgt:
CommitedTx = {}, RolledBackTx = {}
Für eine synchrone Commit-Verarbeitung verwenden Sie die folgende Folge:
Schritt mit Loader | Schritt ohne Loader |
---|---|
Sperren für Einträge abrufen | Identisch |
Änderung mit Flush an den Loader übertragen | Nulloperation |
Änderungen im Cache speichern | Identisch |
Änderungen mit einer festgeschriebenen Transaktion senden, Rollback der Transaktion an das Replikat durchführen und auf Bestätigung warten | Identisch |
Liste festgeschriebener und rückgängig gemachter Transaktionen löschen | Identisch |
Festschreibung im Loader über das TransactionCallBack-Plug-in | Methode des TransactionCallBack-Plug-ins wird weiterhin aufgerufen, führt aber gewöhnlich keine Aktion aus |
Bei erfolgreicher Festschreibung Transaktion der Liste festgeschriebener Transaktionen hinzufügen, andernfalls der Liste rückgängig gemachter Transaktionen hinzufügen | Nulloperation |
Sperren für Einträge freigeben | Identisch |
Für die Replikatverarbeitung verwenden Sie die folgende Folge:
Beachten Sie, dass im Replikat keine Loader-Interaktionen stattfinden, während sich das Replikat im Replikatmodus befindet. Das primäre Shard muss alle Änderungen mit Push über den Loader übertragen. Das Replikat überträgt keine Änderungen mit Push. Dieser Algorithmus hat den Nebeneffekt, dass das Replikat immer die Transaktionen hat, diese aber erst festgeschrieben werden, wenn die nächste primäre Transaktion den Festschreibungsstatus dieser Transaktionen sendet. Erst dann werden die Transaktionen im Replikat festgeschrieben oder rückgängig gemacht. Bis dahin sind die Transaktionen nicht festgeschrieben. Sie können einen Zeitgeber für das primäre Shard hinzufügen, der das Transaktionsergebnis nach kurzer Zeit (ein paar Sekunden) sendet. Dieser Zeitgeber verringert, schließt aber das Risiko veralteter Daten in diesem Zeitfenster nicht ganz aus. Veraltete Daten sind nur dann ein Problem, wenn der Lesemodus für Replikate verwendet wird. Andernfalls haben die veralteten Daten keine Auswirkungen auf die Anwendung.
Wenn das primäre Shard ausfällt, ist es wahrscheinlich, dass einige Transaktionen zwar im primären Shard festgeschrieben oder rückgängig gemacht wurden, aber die Nachricht mit diesen Ergebnissen nicht mehr an das Replikat gesendet werden konnte. Wenn ein Replikat als neues primäres Shard hochgestuft wird, ist eine der ersten Aktionen die Behandlung dieser Bedingung. Jede offene Transaktion wird erneut für die Map-Gruppe des neuen primären Shards ausgeführt. Wenn ein Loader vorhanden ist, wird die erste Transaktion an den Loader übergeben. Diese Transaktionen werden in strikter First In/First Out-Reihenfolge angewendet. Wenn eine Transaktion scheitert, wird sie ignoriert. Sind drei Transaktionen, A, B und C, offen, kann A festgeschrieben, B rückgängig gemacht und C ebenfalls festgeschrieben werden. Keine der Transaktionen hat Auswirkung auf die anderen. Gehen Sie davon aus, dass sie voneinander unabhängig sind.
Ein Loader kann eine geringfügig andere Logik verwenden, wenn er sich im Modus für Fehlerbehebung durch Funktionsübernahme und nicht im normalen Modus befindet. Der Loader kann problemlos feststellen, wann er sich im Modus für Fehlerbehebung durch Funktionsübernahme befindet, indem er die Schnittstelle "ReplicaPreloadController" implementiert. Die Methode "checkPreloadStatus" wird erst aufgerufen, wenn der Loader wieder aus dem Modus für Fehlerbehebung durch Funktionsübernahme in den normalen Modus wechselt. Wenn die Methode "apply" der Schnittstelle "Loader" vor der Methode "checkPreloadStatus" aufgerufen wird, handelt es sich deshalb um eine Wiederherstellungstransaktion. Nach dem Aufruf der Methode "checkPreloadStatus" ist die Fehlerbehebung durch Funktionsübernahme abgeschlossen.
eXtreme Scale sendet, sofern nicht anders konfiguriert, alle Lese- und Schreibanforderungen an den primären Server für eine bestimmte Replikationsgruppe. Der primäre Server muss alle Anforderungen von Clients bearbeiten. Sie können konfigurieren, dass Leseanforderungen auch an Replikate des primären Servers gesendet werden. Durch das Senden von Leseanforderungen an die Replikate kann die Last der Leseanforderungen auf mehrere Java Virtual Machines (JVM) verteilt werden. Die Verwendung von Replikaten für Leseanforderungen kann jedoch zu inkonsistenten Antworten führen.
Der Lastausgleich mit Replikaten wird gewöhnlich nur verwendet, wenn Clients Daten zwischenspeichern, die sich ständig ändern oder wenn die Clients pessimistisches Sperren verwenden.
Wenn sich die Daten ständig ändern und anschließend im nahen Cache des Clients ungültig gemacht werden, sollte der primäre Server daraufhin eine relativ hohe Rate von get-Anforderungen von Clients sehen. Im pessimistischen Sperrmodus ist kein lokaler Cache vorhanden, also werden alle Anforderungen an den primären Server gesendet.
Wenn die Daten relativ statisch sind oder der pessimistische Modus nicht verwendet wird, hat das Senden von Leseanforderungen an das Replikat keine erheblichen Auswirkungen auf die Leistung. Die Häufigkeit der get-Anforderungen von Clients mit Caches, die voll von Daten sind, ist nicht sehr hoch.
Wenn ein Client zum ersten Mal gestartet wird, ist sein naher Cache leer. Cacheanforderungen an den leeren Cache werden an den primären Server weitergeleitet. Nach und nach werden Daten in den Clientcache gestellt, so dass die Anforderungslast abnimmt. Wenn sehr viele Clients gleichzeitig gestartet werden, kann die Last erheblich und das Senden von Leseanforderungen an das Replikat eine angemessene Leistungsoption sein.
Mit eXtreme Scale können Sie eine Server-Map in einem oder mehreren Clients durch asynchrone Replikation replizieren. Ein Client kann über die Methode ClientReplicableMap.enableClientReplication eine lokale schreibgeschützte Kopie einer serverseitigen Map anfordern.
void enableClientReplication(Mode mode, int[] partitions,
ReplicationMapListener listener) throws ObjectGridException;
Der erste Parameter ist der Replikationsmodus. Dieser Modus kann eine fortlaufende Replikation oder eine Momentaufnahmenreplikation sein. Der zweite Parameter ist ein Bereich von Partitions-IDs, die die Partitionen darstellen, von denen Daten repliziert werden sollen. Wenn der Wert null oder ein leerer Bereich ist, werden die Daten von allen Partitionen repliziert. Der letzte Parameter ist ein Listener für den Empfang von Clientreplikationsereignissen. Weitere Einzelheiten hierzu finden Sie in den Beschreibungen der Schnittstellen "ClientReplicableMap" und "ReplicationMapListener" in der API-Dokumentation.
Nach dem Aktivieren der Replikation wird der Server gestartet, um die Map im Client zu replizieren. Irgendwann einmal ist der Client im Vergleich mit dem Server nur ein paar Transaktionen im Rückstand.