web-dev-qa-db-fra.com

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

J'ai généralement implémenté la génération numéro de séquence en utilisant des séquences de base de données dans le passé.

par exemple. Utilisation de Postgres SERIAL type http://www.neilconway.org/docs/sequences/

Je suis toutefois curieux de savoir comment générer des numéros de séquence pour les grands systèmes distribués où il n’existe pas de base de données. Quelqu'un a-t-il déjà une expérience ou des suggestions quant aux meilleures pratiques permettant de générer de manière sécurisée des numéros de séquence de manière thread pour plusieurs clients?

88
Jon

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

Vous devrez faire la distinction entre numéros de séquence et identifiants uniques qui peuvent être triés (éventuellement) librement selon un critère spécifique (généralement le temps de génération). Les vrais numéros de séquence impliquent une connaissance de ce que tous les autres travailleurs ont fait et, en tant que tels, nécessitent un état partagé. Il n’est pas 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 de fenêtres pour chaque travailleur et les tables de hachage distribuées pour les ID de travail uniques , mais cela demande beaucoup de travail.

Les identifiants uniques sont un autre problème. Il existe plusieurs façons de générer des identifiants uniques de manière décentralisée:

a) Vous pouvez utiliser le service réseau Twitter ID Snowflake . Flocon de neige 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 64 bits, ordonnés en fonction du temps de génération;
  • et le service est hautement évolutif et (potentiellement) hautement disponible; chaque instance peut générer plusieurs milliers d'identifiants par seconde et vous pouvez exécuter plusieurs instances sur votre réseau local/étendu (WAN);
  • écrit en scala, fonctionne sur la machine virtuelle Java.

b) Vous pouvez générer les identifiants uniques sur les clients eux-mêmes, en utilisant une approche dérivée de comment les identifiants UUID et Snowflake sont créés. Il y a plusieurs options, mais quelque chose comme:

  • Les 40 bits les plus significatifs: A estampille temporelle; le temps de génération de l'ID. (Nous utilisons les bits les plus significatifs pour l'horodatage afin que les ID puissent être triés en fonction du temps de génération.)

  • Les quelque 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 environ: Une valeur unique pour chaque générateur. En utilisant cela, nous n'avons pas besoin de synchroniser les générateurs (ce qui est extrêmement difficile), car tous les générateurs produisent des ID ne se chevauchant pas à cause de cette valeur.

c) Vous pouvez générer les identifiants sur les clients, en utilisant uniquement timestamp et une valeur aléatoire. Cela évite de connaître tous les générateurs et d'attribuer à chaque générateur une valeur unique. D'un autre côté, de tels identifiants ne sont pas garantis globalement uniques, ils sont seulement très fortement susceptibles d'être 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 dans le genre de:

  • 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éatoire, généré de nouveau pour chaque ID.

d) La solution de facilité, utilisez les UUID/GUID .

108
Jesper Mortensen

Vous pouvez attribuer à chaque nœud un identifiant unique (que vous pouvez avoir de toute façon), puis l'ajouter au numéro de séquence.

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

Unique :-)

Si vous souhaitez une sorte de système centralisé, vous pouvez également demander à votre serveur de séquence de fonctionner par blocs. Cela réduit considérablement les frais généraux. Par exemple, au lieu de demander un nouvel ID au serveur central pour chaque ID à attribuer, vous demandez des ID par blocs de 10 000 à partir du serveur central, puis vous ne devez effectuer une autre demande réseau que lorsque vous êtes épuisé.

15
Steven Schlansker

Maintenant, il y a plus d'options.

Tu cette question est "vieux", je suis arrivé 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 noeuds de séquence (ajoutés aux noms de znode, mais je préfère utiliser les numéros de version des noeuds). Soyez prudent avec celui-ci: si vous ne voulez pas que des nombres soient manqués dans votre séquence, ce n'est peut-être pas ce que vous voulez.

À votre santé

13
Paolo

Cela peut être fait avec Redisson . Il implémente la 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();
11
Nikita Koksharov

S'il doit vraiment être séquentiel au niveau mondial, et pas simplement unique, alors je envisagerais de créer un service unique et simple pour la distribution de 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 réellement d'une autre solution distribuée complexe?

8
wsorenson

Il y a quelques stratégies; mais aucun que je sache ne peut être vraiment distribué et donner une vraie séquence.

  1. avoir un générateur de numéro central. il n'est pas nécessaire que ce soit une grande base de données. memcached a 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 entière pour chaque noeud (comme réponse de Steven Schlanskter )
  3. utiliser des nombres aléatoires ou des UUID
  4. utiliser des données, avec l'ID du nœud, et tout hacher (ou hmac it)

personnellement, je m'appuierais sur des UUID ou sur memcached si je veux avoir un espace essentiellement contigu.

6
Javier

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

Je devrais probablement développer ceci.

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

Votre exigence "distribué" est remplie, quel que soit le nombre de générateurs d'UUID que vous utilisez, grâce à l'unicité globale de chaque UUID.

Vous pouvez répondre à votre exigence "thread safe" en choisissant des générateurs UUID "thread safe". 

Votre exigence de "numéro de séquence" est supposée être 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 un nombre croissant, ni même un nombre croissant de numéros de séquence (par "connexion"). En effet, un lot consécutif de numéros de séquence est alloué par blocs "mis en cache", connexion par connexion. Cela garantit une unicité globale et maintient une vitesse adéquate. Mais les numéros de séquence effectivement attribués (dans le temps) peuvent être mélangés quand ils sont attribués par plusieurs connexions!

4
Phil

Je sais que c’est une vieille question, mais nous étions également confrontés au même besoin et n’avions pas pu trouver la solution qui répond à notre besoin. Notre exigence était de disposer d’une séquence unique (0,1,2,3 ... n ) des identifiants et donc le flocon de neige n’a pas aidé .. Nous avons créé notre propre système pour générer les identifiants avec Redis. Redis est mono-threadé donc son mécanisme liste/file nous donnerait toujours 1 pop à la fois.

Nous créons un tampon d'identifiants. Initialement, la file d'attente comportera de 0 à 20 identifiants prêts à être envoyés sur demande. Plusieurs clients peuvent demander un identifiant et redis affichera un identifiant à la fois. Après chaque saut, nous insérons BUFFER + currentId à droite, ce qui maintient la liste des tampons active. Mise en oeuvre ici

2
Zohair

La génération d'identifiant distribué peut être archivée avec Redis et Lua. La mise en oeuvre disponible dans Github . Il produit des identifiants uniques distribués et k-triables.

1
SANN3

J'ai écrit un service simple qui peut générer des nombres semi-uniques non séquentiels longs de 64 bits. 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
Majid Azimi

En utilisant une base de données, vous pouvez atteindre plus de 1 000 incréments par seconde avec un seul cœur. C'est assez facile. Vous pouvez utiliser sa propre base de données en tant que serveur 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 un problème similaire. J'avais plusieurs partitions et je voulais obtenir un compteur de décalage pour chacune d'elles. J'ai mis en place quelque chose comme ça:

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

Puis exécuté la déclaration 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 compensation à l'avance, vous pouvez implémenter votre propre service en utilisant Flink pour le traitement en temps réel. J'ai pu obtenir environ 100 000 incréments par partition.

J'espère que ça aide!

0
user2108278

Une solution décente consiste à utiliser une génération de longue date . Cela peut être fait avec le support d’une base de données distribuée. 

0
refuess

Le problème est semblable à celui-ci: Dans le monde iscsi, où chaque luns/volumes doivent être identifiables de manière unique par les initiateurs exécutés côté client./informations du fabricant, et le reste en augmentation monotone.

De même, on peut utiliser les bits initiaux dans le système distribué de noeuds pour représenter l'identifiant de noeud et le reste peut augmenter de façon monotone.

0
user1860223