Generierung verteilter Sequenznummern?

103

Ich habe allgemein die Generierung von Sequenznummern implementiert in der Vergangenheit von Sequenznummern mithilfe von Datenbanksequenzen .

Beispiel: Verwenden Sie Postgres SERIAL und geben Sie http://www.neilconway.org/docs/sequences/ ein.

Ich bin jedoch gespannt, wie Sequenznummern für große verteilte Systeme generiert werden können, für die es keine Datenbank gibt. Hat jemand Erfahrung oder Vorschläge für eine bewährte Methode, um eine Sequenznummerngenerierung auf threadsichere Weise für mehrere Clients zu erreichen?

Jon
quelle
Diese Frage ist alt, aber bitte sehen Sie meine neue Antwort stackoverflow.com/questions/2671858/…
Jesper M
Wie benutzt man nextval.org? Die Website ist etwas seltsam und ich weiß nicht, worum es geht. Ist es ein Unix-Befehl? Oder ein Cloud-Service?
Diegosasw

Antworten:

116

OK, das ist eine sehr alte Frage, die ich jetzt zum ersten Mal sehe.

Sie müssen zwischen Sequenznummern und eindeutigen IDs unterscheiden , die (optional) nach bestimmten Kriterien (normalerweise Generierungszeit) lose sortierbar sind. Wahre Sequenznummern implizieren das Wissen darüber, was alle anderen Arbeiter getan haben, und erfordern als solche einen gemeinsamen Zustand. Es gibt keine einfache Möglichkeit, dies auf verteilte, hochskalierte Weise zu tun. Sie können sich beispielsweise mit Netzwerksendungen, Fensterbereichen für jeden Mitarbeiter und verteilten Hash-Tabellen für eindeutige Mitarbeiter-IDs befassen , aber es ist viel Arbeit.

Eindeutige IDs sind eine andere Sache. Es gibt verschiedene Möglichkeiten, eindeutige IDs dezentral zu generieren:

a) Sie können den Snowflake ID-Netzwerkdienst von Twitter verwenden . Schneeflocke ist ein:

  • Netzwerkdienst, dh Sie tätigen einen Netzwerkanruf, um eine eindeutige ID zu erhalten.
  • die eindeutige 64-Bit-IDs erzeugt, die nach Generierungszeit geordnet sind;
  • und der Dienst ist hoch skalierbar und (potenziell) hoch verfügbar; Jede Instanz kann viele tausend IDs pro Sekunde generieren, und Sie können mehrere Instanzen in Ihrem LAN / WAN ausführen.
  • geschrieben in Scala, läuft auf der JVM.

b) Sie können die eindeutigen IDs auf den Clients selbst mithilfe eines Ansatzes generieren, der sich aus der Erstellung der UUIDs und der IDs von Snowflake ergibt . Es gibt mehrere Optionen, aber etwas in der Art von:

  • Die höchstwertigen 40 Bits: Ein Zeitstempel; die Generierungszeit der ID. (Wir verwenden die höchstwertigen Bits für den Zeitstempel, um IDs nach Generierungszeit sortierbar zu machen.)

  • Die nächsten 14 Bits: Ein Zähler pro Generator, den jeder Generator für jede neu generierte ID um eins erhöht. Dadurch wird sichergestellt, dass sich zum gleichen Zeitpunkt generierte IDs (gleiche Zeitstempel) nicht überlappen.

  • Die letzten 10 Bits: Ein eindeutiger Wert für jeden Generator. Auf diese Weise müssen wir keine Synchronisation zwischen Generatoren durchführen (was extrem schwierig ist), da alle Generatoren aufgrund dieses Werts nicht überlappende IDs erzeugen.

c) Sie können die IDs auf den Clients mit nur einem Zeitstempel und einem zufälligen Wert generieren . Dies vermeidet die Notwendigkeit, alle Generatoren zu kennen und jedem Generator einen eindeutigen Wert zuzuweisen. Auf der anderen Seite ist nicht garantiert , dass solche IDs global eindeutig sind, sondern nur sehr wahrscheinlich eindeutig. (Um zu kollidieren, müssten ein oder mehrere Generatoren genau zur gleichen Zeit denselben Zufallswert erstellen.) Etwas in der Art von:

  • Die höchstwertigen 32 Bit: Zeitstempel, die Generierungszeit der ID.
  • Die niedrigstwertigen 32 Bit: 32 Bit Zufälligkeit, neu generiert für jede ID.

d) Verwenden Sie für den einfachen Ausweg UUIDs / GUIDs .

Jesper M.
quelle
Cassandra unterstützt Zähler ( cassandra.apache.org/doc/cql3/CQL.html#counters ), es gibt jedoch einige Einschränkungen.
Piyush Kansal
Sequenznummern lassen sich leicht für den Bitmap-Index festlegen, aber die eindeutige ID ist manchmal zu lang (64 Bit oder 128 Bit). Wie kann eine eindeutige ID einer Bitmap-Indexposition zugeordnet werden? Vielen Dank.
Brucenan
2
Die Option #b hat mir sehr gut gefallen ..... sie könnte eine hohe Skalierbarkeit ermöglichen und nicht viele
Probleme mit
2
twitter/snowflakewird nicht mehr gewartet
Navin
Wenn Sie eine Apache2-lizenzierte Implementierung von Option B wünschen , besuchen Sie bitbucket.org/pythagorasio/common-libraries/src/master/…. Sie können sie auch von maven io.pythagoras.common beziehen: verteilter Sequenz-ID-Generator: 1.0 .0
Wpigott
16

Jetzt gibt es mehr Möglichkeiten.

Obwohl diese Frage "alt" ist, bin ich hierher gekommen, daher denke ich, dass es nützlich sein könnte, die mir bekannten Optionen (bis jetzt) ​​zu belassen:

  • Sie könnten Hazelcast versuchen . In Version 1.9 enthält es eine verteilte Implementierung von java.util.concurrent.AtomicLong
  • Sie können auch Zookeeper verwenden . Es bietet Methoden zum Erstellen von Sequenzknoten (an zNode-Namen angehängt, obwohl ich die Versionsnummern der Knoten bevorzuge). Seien Sie jedoch vorsichtig: Wenn Sie in Ihrer Sequenz keine verpassten Zahlen möchten, ist dies möglicherweise nicht das, was Sie möchten.

Prost

Paolo
quelle
3
Zookeeper war die Option, mit der ich mich entschieden habe. Es gibt eine gute Beschreibung und Beschreibung auf der Mailingliste, die ich gestartet habe - mail-archive.com/[email protected]/msg01967.html
Jon
Jon, danke, dass du auf diesen Thread hingewiesen hast, das ist genau die Art von Lösung, an die ich gedacht habe. Übrigens, haben Sie den Code erstellt, um die MAX_INT-Einschränkung zu überwinden?
Paolo
15

Sie können jedem Knoten eine eindeutige ID geben (die Sie möglicherweise ohnehin haben) und diese dann der Sequenznummer voranstellen.

Beispielsweise erzeugt Knoten 1 die Sequenz 001-00001 001-00002 001-00003 usw. und Knoten 5 erzeugt 005-00001 005-00002

Einzigartig :-)

Wenn Sie alternativ ein zentrales System wünschen, können Sie Ihren Sequenzserver auch in Blöcken ausgeben lassen. Dies reduziert den Overhead erheblich. Anstatt beispielsweise für jede zuzuweisende ID eine neue ID vom zentralen Server anzufordern, fordern Sie IDs in Blöcken von 10.000 vom zentralen Server an und müssen dann nur dann eine weitere Netzwerkanforderung ausführen, wenn Sie keine mehr haben.

Steven Schlansker
quelle
1
Ich mag Ihren Standpunkt zur Batch-ID-Generierung, aber er schränkt nur jede Möglichkeit der Echtzeitberechnung ein.
Ishan
Ich habe einen ähnlichen Mechanismus implementiert. Zusätzlich zu den Clients, die einen Sequenzblock zwischenspeichern, habe ich mehrere Server-Hosts hinzugefügt, die die Sequenzblöcke zwischenspeichern. Ein (einzelner) Master-Generator wird in einem hochverfügbaren Speicher oder einem einzelnen Master-Host verwaltet, auf den nur die Flotte von Server-Hosts zugreifen kann. Das Server-Caching würde uns auch helfen, die Betriebszeit zu verlängern, obwohl der einzelne Master für einen Moment ausfällt.
Janakiram
11

Es kann mit Redisson gemacht werden . Es implementiert eine verteilte und skalierbare Version von AtomicLong. Hier ist ein Beispiel:

Config config = new Config();
config.addAddress("some.server.com:8291");

Redisson redisson = Redisson.create(config);
RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");
atomicLong.incrementAndGet();
Nikita Koksharov
quelle
8

Wenn es wirklich global sequentiell und nicht einfach eindeutig sein muss, würde ich in Betracht ziehen, einen einzigen, einfachen Dienst für die Ausgabe dieser Nummern zu erstellen.

Verteilte Systeme sind auf die Interaktion vieler kleiner Dienste angewiesen. Benötigen Sie für diese einfache Aufgabe wirklich eine andere komplexe verteilte Lösung oder würden Sie wirklich davon profitieren?

wsorenson
quelle
3
... und was passiert, wenn der Server, auf dem dieser Dienst ausgeführt wird, ausfällt?
Navin
Haben Sie eine Warnung, die jemanden auffordert, eine andere zu starten? Manchmal ist das in Ordnung. Ich denke, die Antwort lautet: "Behalte die Perspektive". Die perfekte verteilte Lösung hat ihre eigenen Nachteile und manchmal ist einfacher besser.
Nic Ferrier
6

Es gibt einige Strategien; aber keiner, den ich kenne, kann wirklich verteilt werden und eine echte Sequenz geben.

  1. haben einen zentralen Zahlengenerator. Es muss keine große Datenbank sein. memcachedhat einen schnellen Atomzähler, in den allermeisten Fällen ist er schnell genug für Ihren gesamten Cluster.
  2. Trennen Sie für jeden Knoten einen ganzzahligen Bereich (wie die Antwort von Steven Schlanskter ).
  3. Verwenden Sie Zufallszahlen oder UUIDs
  4. Verwenden Sie einige Daten zusammen mit der ID des Knotens und hashen Sie alles (oder hmac it).

persönlich würde ich mich an UUIDs lehnen oder mich merken, wenn ich einen größtenteils zusammenhängenden Raum haben möchte.

Javier
quelle
5

Warum nicht einen (thread-sicheren) UUID-Generator verwenden?

Ich sollte das wahrscheinlich erweitern.

UUIDs sind garantiert global eindeutig (wenn Sie solche vermeiden, die auf Zufallszahlen basieren, bei denen die Eindeutigkeit nur sehr wahrscheinlich ist).

Ihre "verteilte" Anforderung wird unabhängig von der Anzahl der von Ihnen verwendeten UUID-Generatoren durch die globale Eindeutigkeit jeder UUID erfüllt.

Ihre "Thread-sichere" Anforderung kann durch Auswahl von "Thread-sicheren" UUID-Generatoren erfüllt werden.

Es wird davon ausgegangen, dass Ihre Anforderung "Sequenznummer" durch die garantierte globale Eindeutigkeit jeder UUID erfüllt wird.

Beachten Sie, dass viele Implementierungen von Datenbanksequenznummern (z. B. Oracle) weder eine monoton ansteigende noch eine (sogar) ansteigende Sequenznummer (pro "Verbindung") garantieren. Dies liegt daran, dass ein fortlaufender Stapel von Sequenznummern pro Verbindung in "zwischengespeicherten" Blöcken zugewiesen wird. Dies garantiert globale Einzigartigkeit und sorgt für eine angemessene Geschwindigkeit. Die tatsächlich zugewiesenen Sequenznummern (im Laufe der Zeit) können jedoch durcheinander gebracht werden, wenn sie von mehreren Verbindungen zugewiesen werden!

Phil
quelle
1
Während UUIDs funktionieren, besteht das Problem bei ihnen darin, dass Sie vorsichtig sein müssen, wie Sie sie speichern, wenn Sie letztendlich die generierten Schlüssel indizieren müssen. Sie nehmen normalerweise auch viel mehr Platz ein als eine monoton erhöhte Sequenz. Unter percona.com/blog/2014/12/19/store-uuid-optimized-way finden Sie eine Diskussion zum Speichern mit MySQL.
Pavel
2

Die verteilte ID-Generierung kann mit Redis und Lua archiviert werden. Die Implementierung in Github verfügbar . Es erzeugt verteilte und k-sortierbare eindeutige IDs.

SANN3
quelle
2

Ich weiß, dass dies eine alte Frage ist, aber wir hatten auch das gleiche Bedürfnis und konnten keine Lösung finden, die unser Bedürfnis erfüllt. Unsere Anforderung war es, eine eindeutige Sequenz (0,1,2,3 ... n) von IDs zu erhalten, und daher half Schneeflocke nicht. Wir haben unser eigenes System erstellt, um die IDs mit Redis zu generieren. Redis ist Single-Threaded, daher würde sein Listen- / Warteschlangenmechanismus immer 1 Pop auf einmal geben.

Wir erstellen einen Puffer mit IDs. Zu Beginn hat die Warteschlange 0 bis 20 IDs, die auf Anfrage zum Versand bereit sind. Mehrere Clients können eine ID anfordern und redis wird jeweils 1 ID anzeigen. Nach jedem Pop von links fügen wir rechts BUFFER + currentId ein, wodurch die Pufferliste am Laufen bleibt. Implementierung hier

Zohair
quelle
0

Ich habe einen einfachen Dienst geschrieben, der halb eindeutige, nicht sequentielle 64-Bit-lange Zahlen erzeugen kann. Es kann aus Redundanz- und Skalierbarkeitsgründen auf mehreren Computern bereitgestellt werden. Es verwendet ZeroMQ für Messaging. Weitere Informationen zur Funktionsweise finden Sie auf der Github-Seite: zUID

Majid Azimi
quelle
0

Mit einer Datenbank können Sie mit einem einzigen Kern mehr als 1.000 Inkremente pro Sekunde erreichen. Es ist ziemlich einfach. Sie können eine eigene Datenbank als Backend verwenden, um diese Nummer zu generieren (da es sich in DDD-Begriffen um ein eigenes Aggregat handeln sollte).

Ich hatte ein ähnliches Problem. Ich hatte mehrere Partitionen und wollte für jede einen Versatzzähler bekommen. Ich habe so etwas implementiert:

CREATE DATABASE example;
USE example;
CREATE TABLE offsets (partition INTEGER, offset LONG, PRIMARY KEY (partition));
INSERT offsets VALUES (1,0);

Führen Sie dann die folgende Anweisung aus:

SELECT @offset := offset from offsets WHERE partition=1 FOR UPDATE;
UPDATE offsets set offset=@offset+1 WHERE partition=1;

Wenn Ihre Anwendung es Ihnen erlaubt, können Sie einen Block sofort zuweisen (das war mein Fall).

SELECT @offset := offset from offsets WHERE partition=1 FOR UPDATE;
UPDATE offsets set offset=@offset+100 WHERE partition=1;

Wenn Sie weiteren Durchsatz benötigen und keine Offsets im Voraus zuweisen können, können Sie Ihren eigenen Service mithilfe von Flink für die Echtzeitverarbeitung implementieren. Ich konnte ungefähr 100.000 Inkremente pro Partition erzielen.

Ich hoffe es hilft!

user2108278
quelle
0

Das Problem ist ähnlich wie in der iscsi-Welt, in der jedes Luns / Volume von den auf der Clientseite ausgeführten Initiatoren eindeutig identifiziert werden muss. Der iscsi-Standard besagt, dass die ersten Bits die Informationen des Speicheranbieters / Herstellers darstellen müssen und der Rest monoton ansteigt.

In ähnlicher Weise kann man die Anfangsbits in dem verteilten Knotensystem verwenden, um die Knoten-ID darzustellen, und der Rest kann monoton ansteigen.

user1860223
quelle
1
Bitte fügen Sie einige weitere Details hinzu
Ved Prakash
0

Eine vernünftige Lösung besteht darin, eine zeitbasierte Generation zu verwenden. Dies kann mit der Unterstützung einer verteilten Datenbank erfolgen.

Refuess
quelle