Generazione di numeri sequenziali distribuiti?


103

In passato ho generalmente implementato la generazione di numeri di sequenza utilizzando sequenze di database.

ad esempio, utilizzando il tipo SERIAL di Postgres http://www.neilconway.org/docs/sequences/

Sono curioso però di sapere come generare numeri di sequenza per grandi sistemi distribuiti dove non c'è un database. Qualcuno ha qualche esperienza o suggerimento di una migliore pratica per ottenere la generazione di numeri di sequenza in modo thread-safe per più client?


Questa domanda è vecchia, ma vedi la mia nuova risposta stackoverflow.com/questions/2671858/…
Jesper M

Come usi nextval.org? Il sito web è un po 'strano e non so di cosa si tratta. È un comando Unix? O qualche servizio cloud?
diegosasw

Risposte:


116

OK, questa è una domanda molto vecchia, che vedo per la prima volta ora.

Dovrai distinguere tra numeri di sequenza e ID univoci che sono (facoltativamente) liberamente ordinabili in base a criteri specifici (in genere il tempo di generazione). I veri numeri di sequenza implicano la conoscenza di ciò che tutti gli altri lavoratori hanno fatto e come tali richiedono uno stato condiviso. Non esiste un modo semplice per farlo in modo distribuito e su larga scala. Potresti esaminare cose come trasmissioni di rete, intervalli con finestre per ogni lavoratore e tabelle hash distribuite per ID lavoratore univoci , ma è molto lavoro.

Gli ID univoci sono un'altra questione, ci sono molti buoni modi per generare ID univoci in modo decentralizzato:

a) È possibile utilizzare il servizio di rete Snowflake ID di Twitter . Snowflake è un:

  • Servizio in rete, ovvero si effettua una chiamata in rete per ottenere un ID univoco;
  • che produce ID univoci a 64 bit ordinati per tempo di generazione;
  • e il servizio è altamente scalabile e (potenzialmente) altamente disponibile; ogni istanza può generare molte migliaia di ID al secondo e puoi eseguire più istanze sulla tua LAN / WAN;
  • scritto in Scala, gira sulla JVM.

b) È possibile generare gli ID univoci sui client stessi, utilizzando un approccio derivato da come sono realizzati gli UUID e gli ID di Snowflake. Ci sono più opzioni, ma qualcosa sulla falsariga di:

  • I 40 bit più significativi: un timestamp; il tempo di generazione dell'ID. (Stiamo usando i bit più significativi per il timestamp per rendere gli ID ordinabili in base al tempo di generazione.)

  • I successivi 14 bit circa: un contatore per generatore, che ogni generatore incrementa di uno per ogni nuovo ID generato. Ciò garantisce che gli ID generati nello stesso momento (stessi timestamp) non si sovrappongano.

  • Gli ultimi 10 bit circa: un valore univoco per ogni generatore. Usando questo, non è necessario eseguire alcuna sincronizzazione tra i generatori (il che è estremamente difficile), poiché tutti i generatori producono ID non sovrapposti a causa di questo valore.

c) È possibile generare gli ID sui client, utilizzando solo un timestamp e un valore casuale. Ciò evita la necessità di conoscere tutti i generatori e assegnare a ciascun generatore un valore univoco. D'altro canto, non è garantito che tali ID siano univoci a livello globale, ma è molto probabile che siano unici. (Per entrare in collisione, uno o più generatori dovrebbero creare lo stesso valore casuale allo stesso tempo.) Qualcosa sulla falsariga di:

  • I 32 bit più significativi: Timestamp, il tempo di generazione dell'ID.
  • I 32 bit meno significativi: 32 bit di casualità, generati di nuovo per ogni ID.

d) La via più semplice, usa UUID / GUID .


Cassandra supporta i contatori ( cassandra.apache.org/doc/cql3/CQL.html#counters ), tuttavia ci sono alcune limitazioni.
Piyush Kansal

i numeri di sequenza è facile da impostare la posizione per l'indice bitmap, ma l'ID univoco a volte troppo lungo (64 bit o 128 bit), come può l'ID univoco mappare una posizione dell'indice bitmap? Grazie.
Brucenan

2
l'opzione #b è davvero piaciuta ..... potrebbe consentire una scala elevata e non causare molti problemi di concorrenza
puneet

2
twitter/snowflakenon è più mantenuto
Navin

Se desideri un'implementazione con licenza Apache2 dell'opzione B, controlla bitbucket.org/pythagorasio/common-libraries/src/master/… Puoi anche ottenerla da maven io.pythagoras.common: distributed-sequence-id-generator: 1.0 .0
Wpigott

16

Ora ci sono più opzioni.

Sebbene questa domanda sia "vecchia", sono arrivato qui, quindi penso che potrebbe essere utile lasciare le opzioni che conosco (finora):

  • Potresti provare Hazelcast . Nella sua versione 1.9 include un'implementazione distribuita di java.util.concurrent.AtomicLong
  • Puoi anche usare Zookeeper . Fornisce metodi per la creazione di nodi di sequenza (aggiunti ai nomi znode, anche se preferisco usare i numeri di versione dei nodi). Fai attenzione a questo però: se non vuoi numeri persi nella tua sequenza, potrebbe non essere quello che vuoi.

Saluti


3
Zookeeper era l'opzione con cui sono andato, c'è una buona descrizione e un articolo di questo sulla mailing list che ho iniziato - mail-archive.com/zookeeper-user@hadoop.apache.org/msg01967.html
Jon

Jon, grazie per aver indicato quel thread, questo è esattamente il tipo di soluzione a cui stavo pensando. BTW, hai creato il codice per superare la limitazione MAX_INT?
Paolo

15

Potresti avere ogni nodo con un ID univoco (che potresti avere comunque) e poi anteporlo al numero di sequenza.

Ad esempio, il nodo 1 genera la sequenza 001-00001 001-00002 001-00003 ecc. E il nodo 5 genera 005-00001 005-00002

Unico :-)

In alternativa, se si desidera una sorta di sistema centralizzato, si potrebbe prendere in considerazione la possibilità che il server di sequenza si arresti in blocchi. Ciò riduce notevolmente l'overhead. Ad esempio, invece di richiedere un nuovo ID dal server centrale per ogni ID che deve essere assegnato, richiedi ID in blocchi di 10.000 dal server centrale e poi devi solo fare un'altra richiesta di rete quando finisci.


1
mi piace il tuo punto di vista sulla generazione dell'ID batch, ma limita qualsiasi possibilità di calcolo in tempo reale.
ishan

Ho implementato un meccanismo simile. In questo, oltre ai client che memorizzano nella cache un blocco di sequenze, ho aggiunto diversi server host che memorizzano nella cache i blocchi di sequenze. Un generatore principale (singolo) viene mantenuto in uno spazio di archiviazione a disponibilità elevata o in un host a master singolo, accessibile solo alla flotta di host server. Il caching del server ci aiuterebbe anche in più uptime nonostante il singolo master si interrompa per un momento.
Janakiram

11

Può essere fatto con Redisson . Implementa la versione distribuita e scalabile di AtomicLong. Ecco un esempio:

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

Redisson redisson = Redisson.create(config);
RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");
atomicLong.incrementAndGet();

8

Se davvero deve essere sequenziale a livello globale e non semplicemente unico, prenderei in considerazione la creazione di un unico e semplice servizio per la distribuzione di questi numeri.

I sistemi distribuiti si basano su molti piccoli servizi che interagiscono e, per questo semplice tipo di attività, hai davvero bisogno o trarresti davvero vantaggio da qualche altra soluzione complessa e distribuita?


3
... e cosa succede quando il server che esegue quel servizio non funziona?
Navin

Hai un avviso che dice a qualcuno di avviarne un altro? A volte andrà bene. Penso che la risposta stia cercando di dire "mantenere le cose in prospettiva". La soluzione distribuita perfetta ha i suoi svantaggi e talvolta più semplice è migliore.
nic ferrier

6

Ci sono alcune strategie; ma nessuno di quello che conosco può essere realmente distribuito e fornire una sequenza reale.

  1. avere un generatore di numeri centrale. non deve essere un grande database. memcachedha un contatore atomico veloce, nella stragrande maggioranza dei casi è abbastanza veloce per l'intero cluster.
  2. separare un intervallo intero per ogni nodo (come la risposta di Steven Schlanskter )
  3. utilizzare numeri casuali o UUID
  4. usa alcuni dati, insieme all'ID del nodo, e hash tutto (o hmac )

personalmente, mi appoggerei agli UUID, o memcached se voglio avere uno spazio per lo più contiguo.


5

Perché non utilizzare un generatore di UUID (thread-safe)?

Probabilmente dovrei approfondire questo argomento.

Gli UUID sono garantiti come univoci a livello globale (se eviti quelli basati su numeri casuali, dove l'unicità è semplicemente altamente probabile).

Il tuo requisito "distribuito" è soddisfatto, indipendentemente dal numero di generatori di UUID che utilizzi, dall'unicità globale di ciascun UUID.

Il tuo requisito "thread safe" può essere soddisfatto scegliendo i generatori di UUID "thread safe".

Si presume che il requisito del "numero di sequenza" sia soddisfatto dall'unicità globale garantita di ciascun UUID.

Si noti che molte implementazioni di numeri di sequenza di database (ad es. Oracle) non garantiscono numeri di sequenza in aumento monotono o (pari) in aumento (su base per "connessione"). Questo perché un batch consecutivo di numeri di sequenza viene allocato in blocchi "memorizzati nella cache" in base alla connessione. Ciò garantisce l'unicità globale e mantiene una velocità adeguata. Ma i numeri di sequenza effettivamente assegnati (nel tempo) possono essere confusi quando vengono assegnati da più connessioni!


1
Mentre gli UUID funzionano, il problema con loro è che devi stare attento a come li memorizzi se alla fine hai bisogno di indicizzare le chiavi generate. Inoltre, in genere occupano molto più spazio rispetto a una sequenza monotonicamente aumentata. Vedere percona.com/blog/2014/12/19/store-uuid-optimized-way per una discussione su come archiviarli con MySQL.
Pavel

2

La generazione di ID distribuiti può essere archiviata con Redis e Lua. L'implementazione disponibile in Github . Produce ID univoci distribuiti e k-ordinabili.


2

So che questa è una vecchia domanda, ma anche noi stavamo affrontando la stessa esigenza e non siamo riusciti a trovare la soluzione che soddisfi la nostra esigenza. La nostra esigenza era di ottenere una sequenza unica (0,1,2,3 ... n) di id e quindi il fiocco di neve non aiutava. Abbiamo creato il nostro sistema per generare gli ID utilizzando Redis. Redis è a thread singolo, quindi il suo meccanismo elenco / coda ci darebbe sempre 1 pop alla volta.

Quello che facciamo è creare un buffer di ID, Inizialmente, la coda avrà da 0 a 20 ID pronti per essere inviati quando richiesto. Più client possono richiedere un ID e redis visualizzerà 1 ID alla volta, dopo ogni pop da sinistra, inseriamo BUFFER + currentId a destra, il che mantiene attivo l'elenco del buffer. Implementazione qui


0

Ho scritto un semplice servizio in grado di generare numeri semi-univoci non sequenziali a 64 bit. Può essere distribuito su più macchine per ridondanza e scalabilità. Usa ZeroMQ per la messaggistica. Per maggiori informazioni su come funziona guarda la pagina github: zUID


0

Utilizzando un database è possibile raggiungere 1.000+ incrementi al secondo con un singolo core. È abbastanza facile. È possibile utilizzare il proprio database come backend per generare quel numero (come dovrebbe essere il proprio aggregato, in termini DDD).

Ho avuto quello che sembra un problema simile. Avevo diverse partizioni e volevo ottenere un contatore di offset per ciascuna. Ho implementato qualcosa del genere:

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

Quindi ha eseguito la seguente istruzione:

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

Se la tua applicazione te lo consente, puoi allocare un blocco in una volta (era il mio caso).

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

Se hai bisogno di un ulteriore throughput e non puoi allocare offset in anticipo, puoi implementare il tuo servizio utilizzando Flink per l'elaborazione in tempo reale. Sono stato in grado di ottenere circa 100.000 incrementi per partizione.

Spero che sia d'aiuto!


0

Il problema è simile a: Nel mondo iscsi, dove ogni lun / volume deve essere identificabile in modo univoco dagli iniziatori in esecuzione sul lato client. Lo standard iscsi afferma che i primi pochi bit devono rappresentare le informazioni del fornitore / produttore di archiviazione e il resto in aumento monotono.

Allo stesso modo, si possono usare i bit iniziali nel sistema distribuito di nodi per rappresentare il nodeID e il resto può essere monotonicamente crescente.


1
per favore aggiungi qualche dettaglio in più
Ved Prakash

0

Una soluzione decente è utilizzare una generazione basata su un lungo periodo di tempo. Può essere fatto con il supporto di un database distribuito.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.