21 Stimmen

Java-Lösungen für verteilte Transaktionen und/oder gemeinsam genutzte Daten im Cluster

Welches sind die besten Ansätze für das Clustering/die Verteilung einer Java-Server-Anwendung? Ich bin auf der Suche nach einem Ansatz, der eine horizontale Skalierung durch Hinzufügen weiterer Anwendungsserver und Datenbankserver ermöglicht.

  • Welche Technologien (Software-Engineering-Techniken oder spezifische Technologien) würden Sie vorschlagen, um diese Art von Problem anzugehen?
  • Welche Techniken verwenden Sie, um eine Persistenzschicht so zu gestalten, dass sie für viele Leser/Schreiber skalierbar ist? Skalierung von Anwendungstransaktionen und Skalierung des Zugriffs auf gemeinsam genutzte Daten (der beste Ansatz ist die Beseitigung gemeinsam genutzter Daten; welche Techniken können Sie anwenden, um gemeinsam genutzte Daten zu beseitigen).
  • Unterschiedliche Ansätze scheinen erforderlich zu sein, je nachdem, ob Ihre Transaktionen lese- oder schreiblastig sind, aber ich habe das Gefühl, dass, wenn Sie eine "schreiblastige" Anwendung optimieren können, diese auch für "leselastig" effizient wäre.

Die "beste" Lösung würde es Ihnen ermöglichen, eine Java-Anwendung für einen einzelnen Knoten zu schreiben und hoffentlich die meisten Details des Zugriffs auf/der Sperrung von gemeinsam genutzten Daten zu "verstecken".

In einer verteilten Umgebung besteht das schwierigste Problem immer darin, dass mehrere Transaktionen auf gemeinsame Daten zugreifen. Es scheint, als gäbe es 2 gängige Ansätze für gleichzeitige Transaktionen.

  1. Explizite Sperren (was extrem fehleranfällig und langsam bei der Koordinierung über mehrere Knoten in einem verteilten System ist)
  2. Software-Transaktionsspeicher (STM), auch bekannt als optimistische Parallelität, bei der eine Transaktion während eines Commits zurückgerollt wird, wenn sie feststellt, dass sich der gemeinsame Zustand geändert hat (und die Transaktion später erneut durchgeführt werden kann). Welcher Ansatz ist besser skalierbar und was sind die Kompromisse in einem verteilten System?

Ich habe mich mit Skalierungslösungen (und allgemein mit Anwendungen, die ein Beispiel für die Skalierung bieten) beschäftigt, wie z. B.:

  1. Terrakotta - bietet eine "transparente" Skalierung durch die Erweiterung des Java-Speichermodells um einen verteilten gemeinsamen Speicher unter Verwendung von Javas Gleichzeitigkeits-Sperrmechanismus (synchronisierte, ReentrantReadWriteLocks).
  2. Google App Engine Java - Ermöglicht das Schreiben von Java- (oder Python)-Anwendungen, die auf "Cloud"-Servern verteilt werden, wobei Sie verteilen, welcher Server eine Transaktion abwickelt, und Sie BigTable zum Speichern Ihrer persistenten Daten verwenden (ich bin nicht sicher, wie Sie Transaktionen, die auf gemeinsam genutzte Daten zugreifen, oder Sperrkonflikte handhaben, um effektiv skalieren zu können)
  3. Darkstar MMO Server - Darkstar ist ein quelloffener MMO (Massively Multiplayer Online)-Spieleserver von Sun. Sie skalieren Transaktionen auf eine thread-transaktionale Art und Weise, die es einer bestimmten Transaktion erlaubt, nur für eine bestimmte Zeitspanne zu laufen und sich zu binden, und wenn sie zu lange dauert, wird sie zurückgerollt (eine Art softwaretransaktionaler Speicher). Sie forschen in folgenden Bereichen Unterstützung einer Multi-Node-Server-Konfiguration für die Skalierung.
  4. Hibernate's optimistisches Sperren - Wenn Sie Hibernate verwenden, können Sie deren optimistische Gleichzeitigkeitsunterstützung nutzen, um die Software-Transaktionsspeicher Typverhalten
  5. Apache CouchDB soll natürlich auf viele Leser/Schreiber-DBs in einer Mesh-Konfiguration "skalieren". (Gibt es ein gutes Beispiel dafür, wie Sie das Sperren von Daten oder die Gewährleistung der Transaktionsisolation handhaben?):
  6. JCache - Skalierung "leseintensiver" Anwendungen durch Zwischenspeichern der Ergebnisse häufiger Abfragen, die Sie in der Google Appengine für den Zugriff auf Memcached und zum Zwischenspeichern anderer häufig gelesener Daten verwenden können.

Terracotta scheint die vollständigste Lösung zu sein, da man eine bestehende Serveranwendung "einfach" modifizieren kann, um die Skalierung zu unterstützen (nachdem man @Root-Objekte und @AutoLockRead/Write-Methoden definiert hat). Das Problem ist, dass die Optimierung für verteilte Systeme kein nachträglicher Gedanke ist, wenn man wirklich das Maximum an Leistung aus einer verteilten Anwendung herausholen will - man muss sie sozusagen mit dem Wissen entwerfen, dass der Objektzugriff möglicherweise durch Netzwerk-E/A blockiert werden könnte.

Um richtig zu skalieren, scheint es immer auf die Partitionierung von Daten und den Lastausgleich von Transaktionen anzukommen, so dass eine bestimmte "Ausführungseinheit" (CPU-Kern -> Thread -> verteilter Anwendungsknoten -> DB-Masterknoten)

Um eine Anwendung durch Clustering richtig skalieren zu können, müssen Sie in der Lage sein, Ihre Transaktionen in Bezug auf ihre Lese- und Schreibvorgänge zu partitionieren. Welche Lösungen haben die Menschen mit, um ihre Anwendungen Daten (Oracle, Google BigTable, MySQL, Data Warehousing) zu verteilen, und im Allgemeinen, wie Sie Partitionierung von Daten (viele Schreib-Master, mit vielen mehr lesen DBs usw.) zu verwalten.

In Bezug auf die Skalierung Ihrer Datenpersistenzschicht, welche Art von Konfiguration skaliert die beste in Bezug auf die Partitionierung Ihrer Daten auf viele Leser/viele Schreiber (in der Regel würde ich meine Daten auf der Grundlage eines bestimmten Benutzers (oder was auch immer Kern Entität, die in der Regel Ihre "Root"-Objekt-Entität) im Besitz von einem einzigen Master-DB) partitionieren

3voto

srini.venigalla Punkte 5031

Ich dachte, ich hätte eine großartige Java Clustering/Distributed Plattform gefunden und wollte diese wieder eröffnen.

Kasse http://www.hazelcast.com

Ich habe die Testprogramme ausgeführt, es ist sehr cool, sehr leichtgewichtig und einfach zu bedienen. Es erkennt automatisch die Cluster-Mitglieder in einer Peer-to-Peer-Konfiguration. Die Möglichkeiten sind grenzenlos.

2voto

srini.venigalla Punkte 5031

Vielen Dank für die gute Zusammenfassung aller Möglichkeiten an einem Ort.

Allerdings fehlt hier eine Technik. Das ist MapReduce-Hadoop. Wenn es möglich ist, das Problem in das MapReduce-Paradigma einzupassen, ist dies vielleicht die am weitesten verbreitete Lösung. Ich frage mich auch, ob das Actor-Framework-Muster (JetLang, Kilim usw.) auf einen Cluster erweitert werden kann.

2voto

alphazero Punkte 26906

1voto

Bob Aman Punkte 32266

Vergessen Sie nicht Erlangs Mnesia .

Mnesia bietet Ihnen Dinge wie Transaktionen, die Sie von einer normalen DB gewohnt sind, aber es bietet Echtzeitoperationen und Fehlertoleranz. Außerdem können Sie Dinge ohne Ausfallzeiten neu konfigurieren. Der Nachteil ist, dass es eine speicherresidente Datenbank ist, so dass man wirklich große Tabellen fragmentieren muss. Die größte Tabellengröße beträgt 4 GB.

1voto

cpurdy Punkte 1139

Während Oracle Coherence und viele der anderen vorgeschlagenen Lösungen gut für die gemeinsame Nutzung von Daten sind, haben Sie nur Locking und STM als Möglichkeiten zur Verwaltung von Zustandsmutationen in einer verteilten Umgebung genannt; beides sind im Allgemeinen ziemlich schlechte Möglichkeiten zur Skalierung der Zustandsverwaltung. Auf einer anderen Website habe ich vor kurzem das Folgende über die Implementierung von (zum Beispiel) Sequenzzählern gepostet:

Wenn es sich um einen Zähler handelt, kann man mit einem Coherence EntryProcessor leicht ein "Nur-einmal"-Verhalten und HA für eine beliebige Anzahl monoton ansteigender Sequenzen erreichen; hier ist die gesamte Implementierung:

public class SequenceCounterProcessor
        extends AbstractProcessor
    {
    public Object process(InvocableMap.Entry entry)
        {
        long l = entry.isPresent() ? (Long) entry.getValue() + 1 : 0;
        entry.setValue(l);
        return l;
        }
    }

Ja. Das war's. Automatische und nahtlose HA, dynamische Scale-Out-Elastizität, Einmal-und-nur-einmal-Verhalten, etc. Erledigt.

Der EntryProcessor ist eine Art von verteiltem Abschluss, den wir 2005 eingeführt haben.

Nebenbei bemerkt: In Java 8 (noch nicht veröffentlicht) führt das Projekt Lambda die offizielle Unterstützung für Closures in der Sprache und den Standardbibliotheken ein.

Im Grunde geht es darum, in einer verteilten Umgebung den Abschluss an den Ort des "Eigentümers" der Daten zu liefern. Coherence verwaltet das Dateneigentum dynamisch, indem es eine dynamische Partitionierung verwendet, die es dem verteilten System ermöglicht, die Daten auf die verschiedenen laufenden Maschinen und Knoten zu verteilen. Standardmäßig ist dies alles zu 100 % automatisiert, so dass Sie dem System nie sagen, wo es die Daten ablegen soll oder wie viele Daten wohin gehören. Zusätzlich gibt es sekundäre (und vielleicht tertiäre usw.) Kopien der Daten, die auf anderen Knoten und anderen physischen Servern verwaltet werden, um eine hohe Verfügbarkeit zu gewährleisten, falls ein Prozess ausfällt oder ein Server stirbt. Auch hier erfolgt die Verwaltung dieser Sicherungskopien standardmäßig völlig automatisch und völlig synchron, was bedeutet, dass das System standardmäßig zu 100 % HA ist (d. h. ohne Konfiguration).

Wenn der Abschluss beim Dateneigentümer eintrifft, wird er in einem transaktionalen Arbeitsbereich ausgeführt, und wenn der Vorgang erfolgreich abgeschlossen ist, wird er zur sicheren Aufbewahrung an die Datensicherung übertragen. Die Datenveränderung (z. B. das Ergebnis der Operation) wird für das übrige System erst sichtbar, wenn die Sicherung erfolgreich durchgeführt wurde.

Zu den Optimierungen gehören das Hinzufügen der ExternalizableLite- und PortableObject-Schnittstellen für eine optimierte Serialisierung und die Vermeidung der Serialisierung der "boxed long"-Daten, indem direkt die "netzwerkfähige" Form der Daten verwendet wird:

public Object process(InvocableMap.Entry entry)
    {
    try
        {
        BinaryEntry binentry = (BinaryEntry) entry;
        long l = entry.isPresent() ? binentry.getBinaryValue()
                .getBufferInput().readLong() + 1 : 0L;
        BinaryWriteBuffer buf = new BinaryWriteBuffer(8);
        buf.getBufferOutput().writeLong(l);
        binentry.updateBinaryValue(buf.toBinary());
        return l;
        }
    catch (IOException e)
        {
        throw new RuntimeException(e);
        }
    }

Und da es zustandslos ist, warum nicht gleich eine Singleton-Instanz bereithalten?

public static final SequenceCounterProcessor INSTANCE =
        new SequenceCounterProcessor();

Die Nutzung von überall im Netz ist so einfach wie eine einzige Codezeile:

long l = (Long) sequences.invoke(x, SequenceCounterProcessor.INSTANCE);

Dabei ist "x" ein beliebiges Objekt oder ein Name, der den gewünschten Sequenzzähler identifiziert. Weitere Informationen finden Sie in der Coherence Knowledge Base unter: http://coherence.oracle.com/

Oracle Coherence ist ein verteiltes System. Wenn Sie einen Coherence-Knoten starten, schließt er sich mit anderen Coherence-Knoten zusammen, die bereits in Betrieb sind, und bildet dynamisch einen elastischen Cluster. Dieser Cluster hostet Daten in einer partitionierten, hochverfügbaren (HA) und transaktionskonsistenten Art und Weise und hostet Operationen (wie die oben gezeigte), die diese Daten "einmal und nur einmal" bearbeiten.

Darüber hinaus können Sie diese Logik nicht nur von jedem Coherence-Knoten aus aufrufen oder transparent auf diese Daten zugreifen, sondern auch von jedem Prozess im Netz aus (natürlich vorbehaltlich der Authentifizierung und Autorisierung). Dieser Code würde also von jedem Coherence-Cluster-Knoten oder von jedem (Java / C / C++ / C# / .NET) Client aus funktionieren:

Der Vollständigkeit halber sei gesagt, dass ich bei Oracle arbeite. Die in diesem Beitrag geäußerten Meinungen und Ansichten sind meine eigenen und spiegeln nicht unbedingt die Meinungen oder Ansichten meines Arbeitgebers wider.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X