• 《Java知识应用》Java雪花算法的原理和实现


    SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的。

    这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。

    给大家举个例子吧,比如下面那个 64 bit 的 long 型数字:

    第一个部分是 1 个 bit:0,这个是无意义的,因为二进制中第一位是符号位,1表示负数,0表示正数。

    第二个部分是 41 个 bit:表示的是时间戳。

    第三个部分是 5 个 bit:表示的是机房 id,10001。

    第四个部分是 5 个 bit:表示的是机器 id,1 1001。

    第五个部分是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 00000000。

    简单来说,你的某个服务假设要生成一个全局唯一 id,那么就可以发送一个请求给部署了 SnowFlake 算法的系统,由这个 SnowFlake 算法系统来生成唯一 id。

    这个 SnowFlake 算法系统首先肯定是知道自己所在的机房和机器的,比如机房 id = 17,机器 id = 12。

    接着 SnowFlake 算法系统接收到这个请求之后,首先就会用二进制位运算的方式生成一个 64 bit 的 long 型 id,64 个 bit 中的第一个 bit 是无意义的。

    接着 41 个 bit,就可以用当前时间戳(单位到毫秒),然后接着 5 个 bit 设置上这个机房 id,还有 5 个 bit 设置上机器 id。 

    最后再判断一下,当前这台机房的这台机器上这一毫秒内,这是第几个请求,给这次生成 id 的请求累加一个序号,作为最后的 12 个 bit。

    雪花算法代码:

    import java.lang.management.ManagementFactory;
    import java.lang.management.RuntimeMXBean;
    import java.net.NetworkInterface;
    import java.net.SocketException;
    import java.util.Enumeration;
    
    /**
     * 雪花算法
     */
    public class SnowFlake {
    
        private final static long twepoch = 12888349746579L;
        // 机器标识位数
        private final static long workerIdBits = 5L;
        // 数据中心标识位数
        private final static long datacenterIdBits = 5L;
        // 毫秒内自增位数
        private final static long sequenceBits = 12L;
        // 机器ID偏左移12位
        private final static long workerIdShift = sequenceBits;
        // 数据中心ID左移17位
        private final static long datacenterIdShift = sequenceBits + workerIdBits;
        // 时间毫秒左移22位
        private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
        //sequence掩码,确保sequnce不会超出上限
        private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
        //上次时间戳
        private static long lastTimestamp = -1L;
        //序列
        private long sequence = 0L;
        //服务器ID
        private long workerId = 1L;
        private static long workerMask = -1L ^ (-1L << workerIdBits);
        //进程编码
        private long processId = 1L;
        private static long processMask = -1L ^ (-1L << datacenterIdBits);
    
        private static SnowFlake snowFlake = null;
    
        static{
            snowFlake = new SnowFlake();
        }
        public static synchronized long nextId(){
            return snowFlake.getNextId();
        }
    
        private SnowFlake() {
            //获取机器编码
            this.workerId=this.getMachineNum();
            //获取进程编码
            RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
            this.processId=Long.valueOf(runtimeMXBean.getName().split("@")[0]).longValue();
    
            //避免编码超出最大值
            this.workerId=workerId & workerMask;
            this.processId=processId & processMask;
        }
    
        public synchronized long getNextId() {
            //获取时间戳
            long timestamp = timeGen();
            //如果时间戳小于上次时间戳则报错
            if (timestamp < lastTimestamp) {
                try {
                    throw new Exception("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //如果时间戳与上次时间戳相同
            if (lastTimestamp == timestamp) {
                // 当前毫秒内,则+1,与sequenceMask确保sequence不会超出上限
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    // 当前毫秒内计数满了,则等待下一秒
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0;
            }
            lastTimestamp = timestamp;
            // ID偏移组合生成最终的ID,并返回ID
            long nextId = ((timestamp - twepoch) << timestampLeftShift) | (processId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
            return nextId;
        }
    
        /**
         * 再次获取时间戳直到获取的时间戳与现有的不同
         * @param lastTimestamp
         * @return 下一个时间戳
         */
        private long tilNextMillis(final long lastTimestamp) {
            long timestamp = this.timeGen();
            while (timestamp <= lastTimestamp) {
                timestamp = this.timeGen();
            }
            return timestamp;
        }
    
        private long timeGen() {
            return System.currentTimeMillis();
        }
    
        /**
         * 获取机器编码
         * @return
         */
        private long getMachineNum(){
            long machinePiece;
            StringBuilder sb = new StringBuilder();
            Enumeration<NetworkInterface> e = null;
            try {
                e = NetworkInterface.getNetworkInterfaces();
            } catch (SocketException e1) {
                e1.printStackTrace();
            }
            while (e.hasMoreElements()) {
                NetworkInterface ni = e.nextElement();
                sb.append(ni.toString());
            }
            machinePiece = sb.toString().hashCode();
            return machinePiece;
        }
    
        public static void main(String[] args) {
            new SnowFlake().getNextId();
        }
    }

    参考:https://blog.csdn.net/lq18050010830/article/details/89845790

    This moment will nap, you will have a dream; But this moment study,you will interpret a dream.
  • 相关阅读:
    Centos7 下安装python3及卸载
    centos python3 的 卸载 删除
    Centos7安装python3和pip3(一)
    python升级带来的yum异常(解决错误File "/usr/bin/yum", line 30 except KeyboardInterrupt, e:)
    python 错误信息是:sudo :apt-get:command not found
    优化页面访问速度(四) ——前端优化
    优化页面访问速度(三) ——服务端优化
    优化页面访问速度(二) ——数据库优化
    优化页面访问速度(一)——综述
    Redis专题——Redis管理工具
  • 原文地址:https://www.cnblogs.com/jssj/p/12235757.html
Copyright © 2020-2023  润新知