Maison  >  Article  >  base de données  >  Quelles sont les solutions pour l’ID distribué Redis ?

Quelles sont les solutions pour l’ID distribué Redis ?

王林
王林avant
2023-06-03 10:14:271537parcourir

    Solutions d'identification distribuées couramment utilisées

    Dans un système distribué, il est très important de générer un identifiant globalement unique, car dans un système distribué, plusieurs nœuds générant des identifiants en même temps peuvent provoquer des conflits d'identification.

    Ce qui suit présente plusieurs solutions d'identification distribuées couramment utilisées.

    UUID

    UUID (Universally Unique Identifier) ​​​​​​est un identifiant composé de 128 chiffres, qui peut garantir une unicité globale car son algorithme de génération est basé sur des facteurs tels que l'horodatage et l'ID du nœud. L'UUID peut être généré à l'aide de la classe UUID fournie avec Java, comme indiqué ci-dessous :

    javaCopy code
    import java.util.UUID;
    public class UuidGenerator {
        public static void main(String[] args) {
            UUID uuid = UUID.randomUUID();
            System.out.println(uuid.toString());
        }
    }

    La classe UUID fournie avec Java est très simple et facile à utiliser, et ne nécessite aucune configuration ni gestion supplémentaire. C'est son avantage. En raison de sa longueur (128 bits), l'UUID ne convient pas comme clé primaire pour les tables de bases de données et est difficile à trier et à indexer.

    Snowflake

    Snowflake est un algorithme de génération d'identifiant distribué open source par Twitter. Il peut générer un identifiant unique de 64 bits, qui contient des informations telles que l'horodatage, l'identifiant du centre de données et l'identifiant de la machine. Le code Java de l'algorithme Snowflake est le suivant :

    Le code Java de l'algorithme Snowflake :
    javaCopy code
    public class SnowflakeGenerator {
        private final static long START_STMP = 1480166465631L;
        private final static long SEQUENCE_BIT = 12;
        private final static long MACHINE_BIT = 5;
        private final static long DATACENTER_BIT = 5;
        private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
        private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
        private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
        private final static long MACHINE_LEFT = SEQUENCE_BIT;
        private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
        private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
        private long datacenterId;
        private long machineId;
        private long sequence = 0L;
        private long lastStmp = -1L;
        public SnowflakeGenerator(long datacenterId, long machineId) {
            if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
                throw new IllegalArgumentException("datacenterId can&#39;t be greater than MAX_DATACENTER_NUM or less than 0");
            }
            if (machineId > MAX_MACHINE_NUM || machineId < 0) {
                throw new IllegalArgumentException("machineId can&#39;t be greater than MAX_MACHINE_NUM or less than 0");
            }
            this.datacenterId = datacenterId;
            this.machineId = machineId;
        }
        public synchronized long nextId() {
            long currStmp = getNewstmp();
            if (currStmp < lastStmp) {
                throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
            }
            if (currStmp == lastStmp) {
                sequence = (sequence + 1) & MAX_SEQUENCE;
                if (sequence == 0L) {
                    currStmp = getNextMill();
                }
            } else {
                sequence = 0L;
            }
            lastStmp = currStmp;
            return (currStmp - START_STMP) << TIMESTMP_LEFT
                    | datacenterId << DATACENTER_LEFT
                    | machineId << MACHINE_LEFT
                    | sequence;
        }
        private long getNextMill() {
            long mill = getNewstmp();
            while (mill <= lastStmp) {
                mill = getNewstmp();
            }
            return mill;
        }
        private long getNewstmp() {
            return System.currentTimeMillis();
        }
    }

    L'avantage de l'algorithme Snowflake est qu'il a de hautes performances dans la génération d'identifiants et que la longueur de l'identifiant est courte (64 bits) .Il peut être utilisé comme clé primaire de la table de base de données et est pratique pour le tri et l'indexation. Cependant, il convient de noter que si le nombre de nœuds dans le cluster dépasse le nombre de chiffres occupés par l'ID de la machine, ou si le cluster est très grand et que le nombre de chiffres d'horodatage n'est pas suffisant, alors d'autres algorithmes de génération d'ID distribués doivent être considéré.

    Leaf

    Leaf est un algorithme de génération d'identifiant distribué open source par Meituan Dianping. Il peut générer un identifiant 64 bits unique au monde. Le code Java de l'algorithme Leaf est le suivant :

    Le code Java de l'algorithme Leaf :
    javaCopy code
    public class LeafGenerator {
        private static final Logger logger = LoggerFactory.getLogger(LeafGenerator.class);
        private static final String WORKER_ID_KEY = "leaf.worker.id";
        private static final String PORT_KEY = "leaf.port";
        private static final int DEFAULT_PORT = 8080;
        private static final int DEFAULT_WORKER_ID = 0;
        private static final int WORKER_ID_BITS = 10;
        private static final int SEQUENCE_BITS = 12;
        private static final int MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1;
        private static final int MAX_SEQUENCE = (1 << SEQUENCE_BITS) - 1;
        private static final long EPOCH = 1514736000000L;
        private final SnowflakeIdWorker idWorker;
        public LeafGenerator() {
            int workerId = SystemPropertyUtil.getInt(WORKER_ID_KEY, DEFAULT_WORKER_ID);
            int port = SystemPropertyUtil.getInt(PORT_KEY, DEFAULT_PORT);
            this.idWorker = new SnowflakeIdWorker(workerId, port);
            logger.info("Initialized LeafGenerator with workerId={}, port={}", workerId, port);
        }
        public long nextId() {
            return idWorker.nextId();
        }
        private static class SnowflakeIdWorker {
            private final long workerId;
            private final long port;
            private long sequence = 0L;
            private long lastTimestamp = -1L;
            SnowflakeIdWorker(long workerId, long port) {
                if (workerId < 0 || workerId > MAX_WORKER_ID) {
                    throw new IllegalArgumentException(String.format("workerId must be between %d and %d", 0, MAX_WORKER_ID));
                }
                this.workerId = workerId;
                this.port = port;
            }
            synchronized long nextId() {
                long timestamp = System.currentTimeMillis();
                if (timestamp < lastTimestamp) {
                    throw new RuntimeException("Clock moved backwards. Refusing to generate id");
                }
                if (timestamp == lastTimestamp) {
                    sequence = (sequence + 1) & MAX_SEQUENCE;
                    if (sequence == 0L) {
                        timestamp = tilNextMillis(lastTimestamp);
                    }
                } else {
                    sequence = 0L;
                }
                lastTimestamp = timestamp;
                return ((timestamp - EPOCH) << (WORKER_ID_BITS + SEQUENCE_BITS))
                        | (workerId << SEQUENCE_BITS)
                        | sequence;
            }
            private long tilNextMillis(long lastTimestamp) {
                long timestamp = System.currentTimeMillis();
                while (timestamp <= lastTimestamp) {
                    timestamp = System.currentTimeMillis();
                }
                return timestamp;
            }
        }
    }

    Bien que l'algorithme Leaf génère des identifiants légèrement plus lents que l'algorithme Snowflake, il peut prendre en charge plus de nœuds de travail. L'ID généré par l'algorithme Leaf se compose de trois parties, à savoir l'horodatage, l'ID du travailleur et le numéro de série. L'horodatage occupe 42 bits, l'ID du travailleur occupe 10 bits et le numéro de série occupe 12 bits, pour un total de 64 bits.

    Les algorithmes ci-dessus sont des algorithmes de génération d'identifiants distribués courants. Bien sûr, il existe d'autres solutions, telles que : MongoDB ID, UUID, Twitter Snowflake, etc. Différentes solutions conviennent à différents scénarios commerciaux, et les détails de mise en œuvre et les performances spécifiques sont également différents. Vous devez choisir la solution appropriée en fonction de la situation réelle.

    En plus de l'algorithme de génération d'ID distribué présenté ci-dessus, de nouvelles solutions de génération d'ID distribuées émergent également, telles que l'algorithme de génération d'ID distribué de Flicker, qui utilise une idée similaire à Snowflake, mais utilise un nombre de bits différent pour la méthode d'allocation. est plus flexible que Snowflake et le nombre de bits occupés par chaque partie peut être ajusté dynamiquement selon les besoins. En outre, Facebook a également lancé la solution ID Generation Service (IGS), qui sépare la génération d'identifiants et le stockage, offrant une solution plus flexible et évolutive, mais nécessite une conception et une mise en œuvre d'architecture plus complexes.

    Plusieurs solutions de génération d'ID distribuées peuvent être conçues pour différents besoins commerciaux. Voici quelques-unes de mes suggestions personnelles :

    • Génération basée sur l'ID d'auto-incrémentation de la base de données : l'utilisation de l'ID d'auto-incrémentation de la base de données comme identifiant globalement unique peut garantir l'unicité de l'ID et est simple à mettre en œuvre, mais cela peut être possible lorsque le degré de concurrence est élevé. Peut entraîner des goulots d'étranglement en matière de performances. Par conséquent, il n’est pas recommandé de l’utiliser dans des scénarios à forte concurrence.

    • Génération basée sur l'UUID : l'utilisation de l'UUID comme identifiant unique au monde peut bien garantir l'unicité de l'identifiant, mais la longueur de l'identifiant est longue (128 bits), ce qui n'est pas pratique pour le stockage et la transmission, et la probabilité de duplication Les identifiants sont très petits. Pas 0. Il est recommandé que lors de l'utilisation d'un système distribué, la longueur de l'ID ainsi que le coût du stockage et de la transmission soient pris en compte.

    • Généré sur la base de Redis : grâce au fonctionnement atomique de Redis, l'unicité de l'ID peut être garantie et la vitesse de génération de l'ID est très rapide, ce qui peut être appliqué à des scénarios de concurrence élevée. Il convient de noter que si Redis plante ou fonctionne mal, cela peut affecter l'efficacité et la disponibilité de la génération d'ID.

    • Généré sur la base de ZooKeeper : l'utilisation du générateur de numéro de série de ZooKeeper peut garantir l'unicité de l'ID, et la mise en œuvre est relativement simple, mais elle nécessite l'introduction de dépendances et de ressources supplémentaires, et il peut y avoir des goulots d'étranglement en termes de performances.

    Pour choisir une solution de génération d'ID distribuée adaptée à votre scénario commercial, vous devez prendre en compte de manière globale plusieurs facteurs tels que l'unicité de l'ID, la vitesse de génération, la longueur, le coût de stockage, l'évolutivité et la disponibilité. La mise en œuvre de différentes solutions nécessite de considérer des compromis et des choix dans des situations réelles, car les détails de leur mise en œuvre et leurs performances sont également différents.

    La démo détaillée du code de chaque solution est donnée ci-dessous :

    Générer en fonction de l'ID d'incrémentation automatique de la base de données

    javaCopy code
    public class IdGenerator {
        private static final String JDBC_URL = "jdbc:mysql://localhost:3306/test";
        private static final String JDBC_USER = "root";
        private static final String JDBC_PASSWORD = "password";
        public long generateId() {
            Connection conn = null;
            PreparedStatement pstmt = null;
            ResultSet rs = null;
            try {
                Class.forName("com.mysql.jdbc.Driver");
                conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
                pstmt = conn.prepareStatement("INSERT INTO id_generator (stub) VALUES (null)", Statement.RETURN_GENERATED_KEYS);
                pstmt.executeUpdate();
                rs = pstmt.getGeneratedKeys();
                if (rs.next()) {
                    return rs.getLong(1);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (rs != null) {
                        rs.close();
                    }
                    if (pstmt != null) {
                        pstmt.close();
                    }
                    if (conn != null) {
                        conn.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return 0L;
        }
    }

    Générer en fonction de l'UUID

    javaCopy code
    import java.util.UUID;
    public class IdGenerator {
        public String generateId() {
            return UUID.randomUUID().toString().replace("-", "");
        }
    }

    Générer en fonction de Redis

    javaCopy code
    import redis.clients.jedis.Jedis;
    public class IdGenerator {
        private static final String REDIS_HOST = "localhost";
        private static final int REDIS_PORT = 6379;
        private static final String REDIS_PASSWORD = "password";
        private static final int ID_GENERATOR_EXPIRE_SECONDS = 3600;
        private static final String ID_GENERATOR_KEY = "id_generator";
        public long generateId() {
            Jedis jedis = null;
            try {
                jedis = new Jedis(REDIS_HOST, REDIS_PORT);
                jedis.auth(REDIS_PASSWORD);
                long id = jedis.incr(ID_GENERATOR_KEY);
                jedis.expire(ID_GENERATOR_KEY, ID_GENERATOR_EXPIRE_SECONDS);
                return id;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
            return 0L;
        }
    }

    Générer en fonction de ZooKeeper

    javaCopy code
    import java.util.concurrent.CountDownLatch;
    import org.apache.zookeeper.CreateMode;
    import org.apache.zookeeper.WatchedEvent;
    import org.apache.zookeeper.Watcher;
    import org.apache.zookeeper.ZooDefs.Ids;
    import org.apache.zookeeper.ZooKeeper;
    public class IdGenerator implements Watcher {
        private static final String ZK_HOST = "localhost";
        private static final int ZK_PORT = 2181;
        private static final int SESSION_TIMEOUT = 5000;
        private static final String ID_GENERATOR_NODE = "/id_generator";
        private static final int ID_GENERATOR_EXPIRE_SECONDS = 3600;
        private long workerId = 0;
        public IdGenerator() {
            try {
                ZooKeeper zk = new ZooKeeper(ZK_HOST + ":" + ZK_PORT, SESSION_TIMEOUT, this);
                CountDownLatch latch = new CountDownLatch(1);
                latch.await();
                if (zk.exists(ID_GENERATOR_NODE, false) == null) {
                    zk.create(ID_GENERATOR_NODE, null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                }
                workerId = zk.getChildren(ID_GENERATOR_NODE, false).size();
                zk.create(ID_GENERATOR_NODE + "/worker_" + workerId, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        public long generateId() {
            ZooKeeper zk = null;
            try {
                zk = new ZooKeeper(ZK_HOST + ":" + ZK_PORT, SESSION_TIMEOUT, null);
                CountDownLatch latch = new CountDownLatch(1);
                latch.await();
                zk.create(ID_GENERATOR_NODE + "/id_", null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, (rc, path, ctx, name) -> {}, null);
                byte[] data = zk.getData(ID_GENERATOR_NODE + "/worker_" + workerId, false, null);
                long id = Long.parseLong(new String(data)) * 10000 + zk.getChildren(ID_GENERATOR_NODE, false).size();
                return id;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (zk != null) {
                    try {
                        zk.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            return 0L;
        }
        @Override
        public void process(WatchedEvent event) {
            if (event.getState() == Event.KeeperState.SyncConnected) {
                System.out.println("Connected to ZooKeeper");
                CountDownLatch latch = new CountDownLatch(1);
                latch.countDown();
            }
        }
    }

    Notez que ZooKeeper's temporaire est utilisé ici pour coordonner chaque nœud de travail. Si un nœud de travail meurt, son nœud temporaire sera également supprimé. Cela garantit que l'ID obtenu par chaque nœud de travail est unique.

    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:
    Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer