Maison  >  Article  >  php教程  >  Résumé des solutions de génération d'ID uniques pour les systèmes distribués

Résumé des solutions de génération d'ID uniques pour les systèmes distribués

坏嘻嘻
坏嘻嘻original
2018-09-14 13:39:205449parcourir

L'ID système unique est un problème que nous rencontrons souvent lors de la conception d'un système, et nous sommes souvent aux prises avec ce problème. Il existe de nombreuses façons de générer des identifiants, en s'adaptant à différents scénarios, besoins et exigences de performances. Par conséquent, certains systèmes plus complexes auront plusieurs stratégies de génération d’ID. Voici quelques stratégies courantes de génération d’identifiants.

1. Séquence ou champ auto-croissant de base de données

La manière la plus courante. En utilisant la base de données, l’ensemble de la base de données est unique.

Avantages :

  1. Code simple et pratique, performances acceptables.

  2. Les identifiants numériques sont naturellement triés, ce qui est utile pour la pagination ou les résultats qui doivent être triés.

Inconvénients :

  1. La syntaxe et l'implémentation de bases de données différentes sont différentes, lors de la migration de la base de données ou lorsque plusieurs versions de la base de données sont prises en charge Doit être traité.

  2. Dans le cas d'une seule base de données ou d'une séparation lecture-écriture ou d'un maître et de plusieurs esclaves, il y a est qu'une seule base de données principale peut être générée. Il existe un risque de défaillance unique.

  3. Il est difficile de se développer lorsque les performances ne peuvent pas répondre aux exigences.

  4. Si vous rencontrez plusieurs systèmes qui doivent être fusionnés ou si une migration de données est impliquée, ce sera assez douloureux.

  5. Il y aura des problèmes lors de la division des tables et des bases de données.

Plan d'optimisation :

  1. Pour la bibliothèque principale, point unique, s'il existe plusieurs bibliothèques Master, chaque Master The Le numéro de départ défini par la bibliothèque est différent, mais la taille du pas est la même, qui peut être le nombre de maîtres. Par exemple : Master1 génère 1, 4, 7, 10, Master2 génère 2,5,8,11, Master3 génère 3,6,9,12. Cela peut générer efficacement des identifiants uniques dans le cluster et peut également réduire considérablement la charge des opérations de génération d'identifiants dans la base de données.

2. Méthodes courantes UUID.

Il peut être généré à l'aide d'une base de données ou d'un programme, et est généralement unique au monde.

Avantages :

  1. Code simple et pratique.

  2. Les performances de génération d'ID sont très bonnes et il n'y a pratiquement aucun problème de performances.

  3. Unique au monde, en cas de migration de données, de fusion de données système ou de modification de base de données, vous pouvez le prendre dans la foulée.

Inconvénients :

  1. Il n'y a pas de tri et on ne peut pas garantir que la tendance augmente.

  2. L'UUID est souvent stocké à l'aide de chaînes et l'efficacité des requêtes est relativement faible.

  3. L'espace de stockage est relativement important S'il s'agit d'une base de données massive, vous devez tenir compte du stockage. montant.

  4. Grande quantité de données à transférer

  5. n'est pas lisible.

3. Redis génère un identifiant

Lorsque les performances d'utilisation de la base de données pour générer un identifiant ne sont pas suffisantes, nous pouvons essayer d'utiliser Redis pour générer un identifiant. Cela repose principalement sur le fait que Redis soit monothread, il peut donc également être utilisé pour générer des identifiants uniques au monde. Ceci peut être réalisé en utilisant les opérations atomiques INCR et INCRBY de Redis.

Vous pouvez utiliser le cluster Redis pour obtenir un débit plus élevé. Supposons qu'il y ait 5 Redis dans un cluster. Les valeurs de chaque Redis peuvent être initialisées à 1, 2, 3, 4, 5 respectivement, puis la taille du pas est de 5. Les identifiants générés par chaque Redis sont :

A : 1,6,11,16,21 B : 2,7,12,17,22 C : 3,8,13,18,23 D : 4 , 9,14,19,24 E : 5,10,15,20,25

Cela peut être déterminé par la machine dans laquelle il est chargé. Il sera difficile de le modifier à l'avenir. Cependant, 3 à 5 serveurs peuvent essentiellement satisfaire les besoins du serveur, et ils peuvent tous obtenir des identifiants différents. Mais la taille du pas et la valeur initiale doivent être requises à l'avance. L'utilisation du cluster Redis peut également résoudre le problème du point de défaillance unique.

De plus, il est plus adapté d'utiliser Redis pour générer un numéro de série quotidien à partir de 0. Par exemple, numéro de commande = date + numéro auto-croissant ce jour-là. Vous pouvez générer une clé dans Redis chaque jour et utiliser INCR pour l'accumulation.

Avantages :

  1. Ne dépend pas de la base de données, est flexible et pratique, et offre de meilleures performances que la base de données.

  2. Les identifiants numériques sont naturellement triés, ce qui est utile pour la pagination ou les résultats qui doivent être triés.

Inconvénients :

  1. S'il n'y a pas de Redis dans le système, de nouveaux composants doivent être introduits, augmentant ainsi la complexité du système.

  2. La charge de travail requise pour le codage et la configuration est relativement importante.

4. L'algorithme de flocon de neige de Twitter

Snowflake est l'algorithme de génération d'identifiant distribué open source de Twitter, et le résultat est un identifiant long. L'idée principale est la suivante : utiliser 41 bits comme nombre de millisecondes, 10 bits comme ID de machine (5 bits sont le centre de données, 5 bits comme ID de machine) et 12 bits comme numéro de série en millisecondes (ce qui signifie que chaque nœud peut générer 4096 ID), et il y a un bit de signe à la fin, qui est toujours 0. Le code d'implémentation spécifique peut être trouvé sur : https://github.com/twitter/snowflake

public class IdWorker {
// ==============================Fields===========================================
/** 开始时间截 (2015-01-01) */
private final long twepoch = 1420041600000L;

/** 机器id所占的位数 */
private final long workerIdBits = 5L;

/** 数据标识id所占的位数 */
private final long datacenterIdBits = 5L;

/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

/** 支持的最大数据标识id,结果是31 */
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

/** 序列在id中占的位数 */
private final long sequenceBits = 12L;

/** 机器ID向左移12位 */
private final long workerIdShift = sequenceBits;

/** 数据标识id向左移17位(12+5) */
private final long datacenterIdShift = sequenceBits + workerIdBits;

/** 时间截向左移22位(5+5+12) */
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
private final long sequenceMask = -1L ^ (-1L << sequenceBits);

/** 工作机器ID(0~31) */
private long workerId;

/** 数据中心ID(0~31) */
private long datacenterId;

/** 毫秒内序列(0~4095) */
private long sequence = 0L;

/** 上次生成ID的时间截 */
private long lastTimestamp = -1L;

//==============================Constructors=====================================
/**
 * 构造函数
 * @param workerId 工作ID (0~31)
 * @param datacenterId 数据中心ID (0~31)
 */
public IdWorker(long workerId, long datacenterId) {
    if (workerId > maxWorkerId || workerId < 0) {
        throw new IllegalArgumentException(String.format("worker Id can&#39;t be greater than %d or less than 0", maxWorkerId));
    }
    if (datacenterId > maxDatacenterId || datacenterId < 0) {
        throw new IllegalArgumentException(String.format("datacenter Id can&#39;t be greater than %d or less than 0", maxDatacenterId));
    }
    this.workerId = workerId;
    this.datacenterId = datacenterId;
}

// ==============================Methods==========================================
/**
 * 获得下一个ID (该方法是线程安全的)
 * @return SnowflakeId
 */
public synchronized long nextId() {
    long timestamp = timeGen();

    //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
    if (timestamp < lastTimestamp) {
        throw new RuntimeException(
                String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
    }

    //如果是同一时间生成的,则进行毫秒内序列
    if (lastTimestamp == timestamp) {
        sequence = (sequence + 1) & sequenceMask;
        //毫秒内序列溢出
        if (sequence == 0) {
            //阻塞到下一个毫秒,获得新的时间戳
            timestamp = tilNextMillis(lastTimestamp);
        }
    }
    //时间戳改变,毫秒内序列重置
    else {
        sequence = 0L;
    }

    //上次生成ID的时间截
    lastTimestamp = timestamp;

    //移位并通过或运算拼到一起组成64位的ID
    return ((timestamp - twepoch) << timestampLeftShift) //
            | (datacenterId << datacenterIdShift) //
            | (workerId << workerIdShift) //
            | sequence;
}

/**
 * 阻塞到下一个毫秒,直到获得新的时间戳
 * @param lastTimestamp 上次生成ID的时间截
 * @return 当前时间戳
 */
protected long tilNextMillis(long lastTimestamp) {
    long timestamp = timeGen();
    while (timestamp <= lastTimestamp) {
        timestamp = timeGen();
    }
    return timestamp;
}

/**
 * 返回以毫秒为单位的当前时间
 * @return 当前时间(毫秒)
 */
protected long timeGen() {
    return System.currentTimeMillis();
}

//==============================Test=============================================
/** 测试 */
public static void main(String[] args) {
    IdWorker idWorker = new IdWorker(0, 0);
    for (int i = 0; i < 1000; i++) {
        long id = idWorker.nextId();
        System.out.println(Long.toBinaryString(id));
        System.out.println(id);
    }
}}

L'algorithme du flocon de neige peut être modifié en fonction des besoins de votre propre projet. Par exemple, estimez le nombre de futurs centres de données, le nombre de machines dans chaque centre de données et le nombre de simultanéités possibles en une milliseconde unifiée pour ajuster le nombre de bits requis dans l'algorithme.

Avantages :

  1. Ne dépend pas de la base de données, est flexible et pratique, et offre de meilleures performances que la base de données.

  2. L'ID augmente sur une seule machine au fil du temps.

Inconvénients :

  1. est incrémental sur une seule machine, mais comme il s'agit d'un environnement distribué, chaque machine Les horloges sur l'horloge ne peuvent pas être complètement synchronisées et il peut parfois y avoir des situations où l'incrément global n'est pas atteint.

5. Utilisez zookeeper pour générer un identifiant unique

zookeeper génère principalement des numéros de série via sa version de données znode, qui peut générer des numéros de version de données 32 bits et 64 bits. . Clients Le client peut utiliser ce numéro de version comme numéro de série unique.

Zookeeper est rarement utilisé pour générer des identifiants uniques. Principalement parce qu'il s'appuie sur zookeeper et appelle l'API en plusieurs étapes. Si la concurrence est importante, vous devez envisager d'utiliser des verrous distribués. Par conséquent, les performances ne sont pas idéales dans un environnement distribué hautement concurrent.

6. L'ObjectId de MongoDB

L'ObjectId de MongoDB est similaire à l'algorithme du flocon de neige. Il est conçu pour être léger et différentes machines peuvent facilement le générer en utilisant la même méthode unique au monde. MongoDB a été conçu dès le départ comme une base de données distribuée, et la gestion de plusieurs nœuds est une exigence fondamentale. Ce qui rend la génération beaucoup plus facile dans un environnement fragmenté. Le format est le suivant : [src/main/resources/objectId.png] Écrivez la description de l'image ici :

Résumé des solutions de génération dID uniques pour les systèmes distribués

Les 4 premiers octets sont l'horodatage à partir de l'époque standard , l'unité est la seconde. L'horodatage, combiné aux 5 octets suivants, fournit une unicité de deuxième niveau. Puisque l'horodatage vient en premier, cela signifie que les ObjectIds seront triés approximativement dans l'ordre dans lequel ils ont été insérés. Ceci est utile pour des choses comme l'utiliser comme index pour améliorer l'efficacité. Ces 4 octets impliquent également l'heure à laquelle le document a été créé. La plupart des bibliothèques clientes exposeront une méthode pour obtenir ces informations à partir de l'ObjectId. Les 3 octets suivants sont l'identifiant unique de l'hôte. Généralement un hachage du nom d'hôte de la machine. Cela garantit que différents hôtes génèrent différents ObjectIds sans conflit. Pour garantir que l'ObjectId généré par plusieurs processus simultanés sur la même machine est unique, les deux octets suivants proviennent de l'identifiant de processus (PID) qui a généré l'ObjectId. Les 9 premiers octets garantissent que l'ObjectId généré par différents processus sur différentes machines au cours de la même seconde est unique. Les 3 derniers octets constituent un compteur qui augmente automatiquement pour garantir que l'ObjectId généré par le même processus dans la même seconde est également différent. Chaque processus est autorisé à avoir jusqu'à 2 563 (16 777 216) ObjectIds différents dans la même seconde.

Recommandations associées :

Exemple de développement d'un système de gestion des communiqués de presse PHP

Tutoriel de développement d'un système de communiqués de presse simple en PHP

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn