zoukankan      html  css  js  c++  java
  • springboot2 整合雪花算法,并兼容分布式部署

    1,原理

    雪花算法(SnowFlake) 是主键生成策略中介于自增 ID 和 UUID 之间的一种数据库主键生成策略,生成的ID大致上是按照时间递增的
    用在分布式系统中时需要注意数据中心标识和机器标识必须唯一,这样就能保证每个节点生成的 ID 都是唯一的,每秒能够产生 26 万
    ID 左右,这些 ID 是唯一的且有大致的递增顺序,并且是一个 64 位整形,即 8 字节,可以展示为一个 Long 类型的整数
    

    2,上代码

    package com.hwq.web.server.service;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.stereotype.Service;
    
    @Service
    public class SnowFlakeService {
    
        // 机器标识 和 数据标识
        private long machineId = 1;
        private long datacenterId = 1;
    
        // 起始的时间戳,设置的尽量大,最好是项目创建的时间,确定之后禁止修改
        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 << 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 sequence = 0L;
        private long lastStmp = -1L;
    
        /**
         * 生成主键的方法,请确保分布式不同节点中,方法两个参数的唯一性
         */
        public synchronized String nextId() {
            long currStmp = System.currentTimeMillis();
            if (currStmp == lastStmp) {
                sequence = (sequence + 1) & MAX_SEQUENCE;
                if (sequence == 0L){
                    long mill = System.currentTimeMillis();
                    while (mill <= lastStmp) {
                        mill = System.currentTimeMillis();
                    }
                    currStmp = mill;
                }
            } else {
                sequence = 0L;
            }
            lastStmp = currStmp;
            currStmp = (currStmp - START_STMP) << TIMESTMP_LEFT;     // 时间戳部分
            currStmp = currStmp | (datacenterId << DATACENTER_LEFT); // 数据中心部分
            currStmp = currStmp | (machineId << MACHINE_LEFT);       // 机器标识部分
            currStmp = currStmp | sequence;                          // 序列号部分
            return String.valueOf(currStmp);                         // 转化为字符串
        }
    
    }
    

    3,缺陷

    上面的写法虽然能生成 唯一 id,但是在 分布式部署时,就要求我们需要配置不同的 机器标识 和 数据标识,要不然高并发的分布式系统还是会出现重复的情况,
    但是每次部署项目的时候,不管是通过修改代码还是修改配置文件从而保证 机器标识 和 数据标识 的唯一性,都不是很友好,尤其是采用 docker 部署时,每次
    还需要重新制作镜像,基于此,笔者这里打算通过项目部署时不然是唯一的 ip地址 和 端口 来生成唯一的 机器标识 和 数据标识,其中端口可以直接使用,ip
    地址则需要一些算法进行转化,来确保不同的 ip 生成一个 不同的 唯一数字
    

    4,修改后的代码

    package com.hwq.web.server.service;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.stereotype.Service;
    
    @Slf4j
    @Service
    public class SnowFlakeService implements ApplicationRunner {
    
        @Value("${spring.cloud.client.ip-address}")
        private String ip;
        @Value("${server.port}")
        private Long port;
    
        // 机器标识(用 ip 地址计算而来) 和 数据标识(直接使用端口)
        private long machineId;
        private long datacenterId;
    
        // 起始的时间戳,设置的尽量大,最好是项目创建的时间,确定之后禁止修改
        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 << 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 sequence = 0L;
        private long lastStmp = -1L;
    
        /**
         * 生成主键的方法,请确保分布式不同节点中,方法两个参数的唯一性
         */
        public synchronized String nextId() {
            long currStmp = System.currentTimeMillis();
            if (currStmp == lastStmp) {
                sequence = (sequence + 1) & MAX_SEQUENCE;
                if (sequence == 0L){
                    long mill = System.currentTimeMillis();
                    while (mill <= lastStmp) {
                        mill = System.currentTimeMillis();
                    }
                    currStmp = mill;
                }
            } else {
                sequence = 0L;
            }
            lastStmp = currStmp;
            currStmp = (currStmp - START_STMP) << TIMESTMP_LEFT;     // 时间戳部分
            currStmp = currStmp | (datacenterId << DATACENTER_LEFT); // 数据中心部分
            currStmp = currStmp | (machineId << MACHINE_LEFT);       // 机器标识部分
            currStmp = currStmp | sequence;                          // 序列号部分
            return String.valueOf(currStmp);                         // 转化为字符串
        }
    
        /**
         * 当项目启动时,从 redis 获取数据中心标识 和 机器标识
         */
        @Override
        public void run(ApplicationArguments args) {
            machineId = ipToNum(ip);
            datacenterId = port;
            log.info("获得雪花算法的机器码:machineId = " + machineId + "、数据码:datacenterId = " + datacenterId);
        }
    
        /**
         * 将 ipv4 的地址转化为 唯一数字
         * @param ip 地址
         */
        private long ipToNum(String ip) {
            String[] ipArr = ip.split("\.");
            long ipNum = Long.parseLong(ipArr[3]) & 0xFF;
            ipNum |= ((Long.parseLong(ipArr[2]) << 8) & 0xFF00);
            ipNum |= ((Long.parseLong(ipArr[1]) << 16) & 0xFF0000);
            ipNum |= ((Long.parseLong(ipArr[0]) << 24) & 0xFF000000);
            return ipNum;
        }
    }
    
  • 相关阅读:
    求一个数字各个位的数字之和
    二进制和十进制的转换 分别用python和js实现
    pymysql 获取插入数据的主键id
    js03.事件
    02.js运算符
    jsonpath
    01.js控制台
    2.命令补充
    hashmap
    正则表达式的补充
  • 原文地址:https://www.cnblogs.com/lovling/p/13025171.html
Copyright © 2011-2022 走看看