美文网首页
雪花算法(Snowflake)顺序ID

雪花算法(Snowflake)顺序ID

作者: 史啸天 | 来源:发表于2021-03-30 18:07 被阅读0次
    分库分表场景下如何选择主键

    数据库本身有自己的自增id,但在分库分表场景下,则无法保证主键的唯一,这时就需要可以替代的东西;
    常见的分布式id生成方案有:UUID、Redis的incr命令、Zookeeper的顺序节点、雪花算法;

    实现原理

    SnowFlake算法,是Twitter开源的分布式id生成算法。其核心思想:使用一个64bit的long型的数字作为全局唯一id。

    实现
    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 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 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;
        }
    }
    
    缺点

    依赖与系统时间的一致性,如果系统时间被回调,可能会造成冲突。

    其它的分布式id生成方式

    1、Leaf美团点评分布式ID生成系统
    https://tech.meituan.com/2017/04/21/mt-
    https://github.com/Meituan-Dianping/Leaf/blob/master/README_CN.md
    2、Tinyid滴滴分布式ID生成算法
    https://github.com/didi/tinyid
    3、UidGenerator百度分布式ID生成算法
    https://github.com/baidu/uid-generator

    相关文章

      网友评论

          本文标题:雪花算法(Snowflake)顺序ID

          本文链接:https://www.haomeiwen.com/subject/ausghltx.html