Génération de numéros de séquence distribuée?


103

J'ai généralement mis en œuvre la génération de numéros de séquence à l'aide de séquences de base de données dans le passé.

Par exemple, en utilisant le type SERIAL Postgres http://www.neilconway.org/docs/sequences/

Je suis cependant curieux de savoir comment générer des numéros de séquence pour les grands systèmes distribués où il n'y a pas de base de données. Quelqu'un a-t-il une expérience ou des suggestions d'une meilleure pratique pour obtenir la génération de numéros de séquence d'une manière thread-safe pour plusieurs clients?


Cette question est ancienne, mais veuillez voir ma nouvelle réponse stackoverflow.com/questions/2671858/…
Jesper M

Comment utilisez-vous nextval.org? Le site Web est un peu étrange et je ne sais pas de quoi il s'agit. Est-ce une commande Unix? Ou un service cloud?
diegosasw

Réponses:


116

OK, c'est une question très ancienne, que je vois pour la première fois maintenant.

Vous devrez faire la différence entre les numéros de séquence et les ID uniques qui sont (éventuellement) librement triables selon un critère spécifique (généralement l'heure de génération). Les vrais numéros de séquence impliquent la connaissance de ce que tous les autres travailleurs ont fait et, en tant que tels, nécessitent un état partagé. Il n'y a pas de moyen facile de le faire de manière distribuée et à grande échelle. Vous pouvez examiner des éléments tels que les diffusions réseau, les plages fenêtrées pour chaque travailleur et les tables de hachage distribuées pour les ID de travailleur uniques , mais c'est beaucoup de travail.

Les identifiants uniques sont une autre question, il existe plusieurs bonnes façons de générer des identifiants uniques de manière décentralisée:

a) Vous pouvez utiliser le service réseau Snowflake ID de Twitter . Snowflake est un:

  • Service en réseau, c'est-à-dire que vous passez un appel réseau pour obtenir un identifiant unique;
  • qui produit des identifiants uniques de 64 bits classés par heure de génération;
  • et le service est hautement évolutif et (potentiellement) hautement disponible; chaque instance peut générer plusieurs milliers d'ID par seconde et vous pouvez exécuter plusieurs instances sur votre LAN / WAN;
  • écrit en Scala, fonctionne sur la JVM.

b) Vous pouvez générer les ID uniques sur les clients eux-mêmes, en utilisant une approche dérivée de la manière dont les UUID et les ID de Snowflake sont créés. Il existe plusieurs options, mais quelque chose du genre:

  • Les 40 bits les plus significatifs: un horodatage; l'heure de génération de l'ID. (Nous utilisons les bits les plus significatifs pour l'horodatage afin de rendre les identifiants triables par heure de génération.)

  • Les 14 bits suivants: Un compteur par générateur, que chaque générateur incrémente de un pour chaque nouvel ID généré. Cela garantit que les ID générés au même moment (mêmes horodatages) ne se chevauchent pas.

  • Les 10 derniers bits: une valeur unique pour chaque générateur. En utilisant cela, nous n'avons pas besoin de faire de synchronisation entre les générateurs (ce qui est extrêmement difficile), car tous les générateurs produisent des ID qui ne se chevauchent pas à cause de cette valeur.

c) Vous pouvez générer les ID sur les clients, en utilisant simplement un horodatage et une valeur aléatoire. Cela évite d'avoir à connaître tous les générateurs et d'attribuer à chaque générateur une valeur unique. D'un autre côté, ces identifiants ne sont pas garantis d'être uniques au monde, ils sont très probablement uniques. (Pour entrer en collision, un ou plusieurs générateurs devraient créer la même valeur aléatoire exactement au même moment.) Quelque chose du genre:

  • Les 32 bits les plus significatifs: Timestamp, l'heure de génération de l'ID.
  • Les 32 bits les moins significatifs: 32 bits d'aléa, générés à nouveau pour chaque ID.

d) La solution la plus simple consiste à utiliser des UUID / GUID .


Cassandra prend en charge les compteurs ( cassandra.apache.org/doc/cql3/CQL.html#counters ), il existe cependant quelques limitations.
Piyush Kansal

les numéros de séquence sont faciles à définir la position pour l'index bitmap, mais l'identifiant unique parfois trop long (64 bits ou 128 bits), comment un mappage d'identifiant unique à une position d'index bitmap? Merci.
brucenan

2
vraiment aimé l'option #b ..... cela pourrait permettre une grande échelle et ne pas causer beaucoup de problème de concurrence
puneet

2
twitter/snowflaken'est plus maintenu
Navin

Si vous voulez une implémentation sous licence Apache2 de l'option B, consultez bitbucket.org/pythagorasio/common-libraries/src/master/... Vous pouvez également l'obtenir à partir de maven io.pythagoras.common: Distribué-sequence-id-generator: 1.0 .0
Wpigott

16

Maintenant, il y a plus d'options.

Bien que cette question soit "ancienne", je suis arrivée ici, donc je pense qu'il pourrait être utile de laisser les options que je connais (jusqu'à présent):

  • Vous pouvez essayer Hazelcast . Dans sa version 1.9, il inclut une implémentation distribuée de java.util.concurrent.AtomicLong
  • Vous pouvez également utiliser Zookeeper . Il fournit des méthodes pour créer des nœuds de séquence (ajoutés aux noms de znode, bien que je préfère utiliser les numéros de version des nœuds). Soyez prudent avec celui-ci cependant: si vous ne voulez pas de numéros manqués dans votre séquence, ce n'est peut-être pas ce que vous voulez.

À votre santé


3
Zookeeper était les options avec lesquelles je suis allé, il y a une bonne description et une bonne rédaction de cela sur la liste de diffusion que j'ai commencée - mail-archive.com/zookeeper-user@hadoop.apache.org/msg01967.html
Jon

Jon, merci d'avoir indiqué ce fil, c'est exactement le type de solution auquel je pensais. BTW, avez-vous créé le code pour surmonter la limitation MAX_INT?
Paolo

15

Vous pouvez faire en sorte que chaque nœud ait un ID unique (que vous pouvez avoir de toute façon), puis l'ajouter au numéro de séquence.

Par exemple, le nœud 1 génère la séquence 001-00001 001-00002 001-00003 etc. et le nœud 5 génère 005-00001 005-00002

Unique :-)

Alternativement, si vous voulez une sorte de système centralisé, vous pouvez envisager de faire distribuer votre serveur de séquence par blocs. Cela réduit considérablement les frais généraux. Par exemple, au lieu de demander un nouvel identifiant au serveur central pour chaque identifiant qui doit être attribué, vous demandez des identifiants par blocs de 10 000 au serveur central, puis vous n'avez à faire une autre demande réseau que lorsque vous êtes à court.


1
J'aime votre point sur la génération d'identifiant de lot, mais cela limite simplement toute possibilité de calcul en temps réel.
ishan

J'ai mis en place un mécanisme similaire. En cela, en plus des clients mettant en cache un bloc de séquences, j'ai ajouté plusieurs serveurs-hôtes qui mettent en cache les blocs de séquences. Un générateur maître (unique) est maintenu dans un stockage hautement disponible ou un hôte à maître unique, accessible uniquement à la flotte d'hôtes-serveurs. La mise en cache du serveur nous aiderait également à augmenter le temps de disponibilité même si le maître unique tombe en panne pendant un moment.
Janakiram du

11

Cela peut être fait avec Redisson . Il implémente une version distribuée et évolutive de AtomicLong. Voici un exemple:

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

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

8

Si cela doit vraiment être globalement séquentiel, et pas simplement unique, alors j'envisagerais de créer un service unique et simple pour distribuer ces numéros.

Les systèmes distribués reposent sur de nombreux petits services en interaction, et pour ce type de tâche simple, avez-vous vraiment besoin ou bénéficieriez-vous vraiment d'une autre solution complexe et distribuée?


3
... et que se passe-t-il lorsque le serveur exécutant ce service tombe en panne?
Navin

Vous avez une alerte qui dit à quelqu'un d'en démarrer une autre? Parfois, ce sera très bien. Je pense que la réponse essaie de dire "garder les choses en perspective". La solution distribuée parfaite a ses propres inconvénients et parfois plus simple est mieux.
nic ferrier le

6

Il existe quelques stratégies; mais aucun de ceux que je connais ne peut être vraiment distribué et donner une vraie séquence.

  1. avoir un générateur de numéros central. il n'est pas nécessaire que ce soit une grosse base de données. memcacheddispose d'un compteur atomique rapide, dans la grande majorité des cas, il est assez rapide pour l'ensemble de votre cluster.
  2. séparer une plage d'entiers pour chaque nœud (comme la réponse de Steven Schlanskter )
  3. utiliser des nombres aléatoires ou des UUID
  4. utilisez des données, ainsi que l'ID du nœud, et hachez tout (ou hmac )

personnellement, je me pencherais sur les UUID, ou sur memcached si je veux avoir un espace principalement contigu.


5

Pourquoi ne pas utiliser un générateur UUID (thread-safe)?

Je devrais probablement m'étendre là-dessus.

Les UUID sont garantis uniques au monde (si vous évitez ceux basés sur des nombres aléatoires, où l'unicité est tout simplement hautement probable).

Votre exigence "distribuée" est satisfaite, quel que soit le nombre de générateurs d'UUID que vous utilisez, par l'unicité globale de chaque UUID.

Votre exigence "thread-safe" peut être satisfaite en choisissant des générateurs UUID "thread-safe".

Votre exigence de "numéro de séquence" est supposée satisfaite par l'unicité globale garantie de chaque UUID.

Notez que de nombreuses implémentations de numéros de séquence de base de données (par exemple, Oracle) ne garantissent ni l'augmentation monotone, ni (même) l'augmentation des numéros de séquence (sur une base par «connexion»). Cela est dû au fait qu'un lot consécutif de numéros de séquence est alloué dans des blocs «mis en cache» par connexion. Cela garantit l'unicité globale et maintient une vitesse adéquate. Mais les numéros de séquence réellement alloués (au fil du temps) peuvent être mélangés lorsqu'ils sont alloués par plusieurs connexions!


1
Alors que les UUID fonctionnent, le problème avec eux est que vous devez faire attention à la façon dont vous les stockez si vous devez finalement indexer les clés générées. Ils prendront également généralement beaucoup plus d'espace qu'une séquence augmentée de manière monotone. Voir percona.com/blog/2014/12/19/store-uuid-optimized-way pour une discussion sur leur stockage avec MySQL.
Pavel

2

La génération d'ID distribuée peut être archivée avec Redis et Lua. L'implémentation disponible dans Github . Il produit des identifiants uniques distribués et triables en k.


2

Je sais que c'est une vieille question, mais nous étions également confrontés au même besoin et n'avons pas été en mesure de trouver la solution qui répond à notre besoin. Notre exigence était d'obtenir une séquence unique (0,1,2,3 ... n) d'id et donc flocon de neige n'a pas aidé. Nous avons créé notre propre système pour générer les identifiants à l'aide de Redis. Redis est monothread, donc son mécanisme de liste / file d'attente nous donnerait toujours 1 pop à la fois.

Ce que nous faisons, c'est que nous créons un tampon d'identifiants. Au départ, la file d'attente aura de 0 à 20 identifiants prêts à être envoyés sur demande. Plusieurs clients peuvent demander un identifiant et redis affichera 1 identifiant à la fois, après chaque pop de gauche, nous insérons BUFFER + currentId à droite, ce qui maintient la liste de tampons en cours. Implémentation ici


0

J'ai écrit un service simple qui peut générer des nombres longs de 64 bits semi-uniques non séquentiels. Il peut être déployé sur plusieurs machines à des fins de redondance et d'évolutivité. Il utilise ZeroMQ pour la messagerie. Pour plus d'informations sur son fonctionnement, consultez la page github: zUID


0

En utilisant une base de données, vous pouvez atteindre plus de 1000 incréments par seconde avec un seul cœur. C'est assez simple. Vous pouvez utiliser sa propre base de données comme backend pour générer ce nombre (comme il devrait s'agir de son propre agrégat, en termes DDD).

J'ai eu ce qui semble être un problème similaire. J'avais plusieurs partitions et je voulais obtenir un compteur de décalage pour chacune. J'ai implémenté quelque chose comme ceci:

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

Ensuite, exécutez l'instruction suivante:

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

Si votre application vous le permet, vous pouvez allouer un bloc à la fois (c'était mon cas).

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

Si vous avez besoin d'un débit supplémentaire et que vous ne pouvez pas allouer de compensations à l'avance, vous pouvez implémenter votre propre service en utilisant Flink pour un traitement en temps réel. J'ai pu obtenir des incréments d'environ 100K par partition.

J'espère que ça aide!


0

Le problème est similaire à: Dans le monde iscsi, où chaque lun / volume doit être identifiable de manière unique par les initiateurs exécutés côté client. La norme iscsi dit que les premiers bits doivent représenter les informations du fournisseur / fabricant de stockage et que le reste augmente de manière monotone.

De même, on peut utiliser les bits initiaux dans le système distribué de nœuds pour représenter le nodeID et le reste peut être monotone croissant.


1
s'il vous plaît ajouter quelques détails
Ved Prakash

0

Une solution décente consiste à utiliser une génération basée sur le temps. Cela peut être fait avec le support d'une base de données distribuée.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.