• Snowflake


      雪花算法简单来说是这样一个长整形数值。它64位,8个字节,刚好一个long,在单个节点上是有序的。如图它主要由4部分组成。

    1bit:固定为0
      二进制里第一个bit如果是 1,表示负数,但是我们生成的 id都是正数,所以第一个 bit 统一都是 0。
    41 bit:时间戳,单位毫秒
      表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值。
    10 bit:哪台机器产生的
      前5 个 bit 代表机房 id,后5 个 bit 代表机器 id。
    12 bit:自增序列
      同1毫秒内,同一机器,可以产生2 ^ 12 - 1 = 4096个不同的 id。

    package com.test;
    
    import org.apache.logging.log4j.util.PropertiesUtil;
    
    import java.io.IOException;
    import java.util.Properties;
    
    public class Snowflake {
    
        // ==============================Fields===========================================
        /** 序列的掩码,12个1,也就是(0B111111111111=0xFFF=4095) */
        private static final long SEQUENCE_MASK = 0xFFF;
    
        /**系统起始时间,这里取2020-01-01 **/
        private long startTimeStamp = 1577836800000L;
    
        /** 上次生成 ID 的时间截 */
        private long lastTimestamp = -1L;
    
        /** 毫秒内序列(0~4095) */
        private long sequence = 0L;
    
        // --------------  初始化  -------------
        /** 工作机器 ID(0~31) */
        private static long workerId;
    
        /** 工作组 ID(0~31) */
        private static long datacenterId;
    
        static{
            String filename = "application.yml";
            Properties pro = new Properties();
            try {
                pro.load(PropertiesUtil.class.getClassLoader().getResourceAsStream(filename));
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 这种读取配置方式,没法在yml 里面写 snowflake:datacenterId: 2 的格式
            workerId = Long.valueOf(pro.getProperty("workerId"));
            datacenterId = Long.valueOf(pro.getProperty("datacenterId"));
    
            if (workerId > 31 || workerId < 0) {
                throw new IllegalArgumentException("workId必须在0-31之间,当前="+workerId);
            }
            if (datacenterId > 31 || datacenterId < 0) {
                throw new IllegalArgumentException("datacenterId必须在0-31之间,当前="+datacenterId);
            }
        }
    
    
    //    /**
    //     * 标注为 @Component 类,通过注解读取配置
    //     * @param datacenterId 工作组 ID (0~31)
    //     * @param workerId     工作机器 ID (0~31)
    //     */
    //    public Snowflake(@Value("${datacenterId}") long datacenterId, @Value("${workerId}") long workerId) {
    //        if (workerId > 31 || workerId < 0) {
    //            throw new IllegalArgumentException("workId必须在0-31之间,当前="+workerId);
    //        }
    //        if (datacenterId > 31 || datacenterId < 0) {
    //            throw new IllegalArgumentException("datacenterId必须在0-31之间,当前="+datacenterId);
    //        }
    //
    //        Snowflake.workerId = workerId;
    //        Snowflake.datacenterId = datacenterId;
    //    }
    
        /**
         * 加锁,线程安全
         * @return long 类型的 ID
         */
        public synchronized long nextId() {
            long timestamp = currentTime();
    
            // 如果当前时间小于上一次 ID 生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
            if (timestamp < lastTimestamp) {
                throw new RuntimeException("时钟回退!时间差="+(lastTimestamp - timestamp));
            }
    
            // 同一毫秒内,序列增加
            if (lastTimestamp == timestamp) {
                //超出阈值。思考下为什么这么运算?
                sequence = (sequence + 1) & SEQUENCE_MASK;
                // 毫秒内序列溢出
                if (sequence == 0) {
                    //自旋等待下一毫秒
                    while ((timestamp= currentTime()) <= lastTimestamp) {
    
                    }
                }
            } else {
                //已经进入下一毫秒,从0开始计数
                sequence = 0L;
            }
    
            //赋值为新的时间戳
            lastTimestamp = timestamp;
    
            //移位拼接
            long id = ((timestamp - startTimeStamp) << 22)
                    | (datacenterId << 17)
                    | (workerId << 12)
                    | sequence;
            return id;
        }
    
    
        /**
         * 返回当前时间,以毫秒为单位
         */
        protected long currentTime() {
            return System.currentTimeMillis();
        }
    
    //    /**
    //     * 转成二进制展示
    //     */
    //    public static String toBit(long id){
    //        String bit = org.apache.commons.lang3.StringUtils.leftPad(Long.toBinaryString(id), 64, "0");
    //        return bit.substring(0,1) +
    //                " - " +
    //                bit.substring(1,42) +
    //                " - " +
    //                bit.substring(42,52)+
    //                " - " +
    //                bit.substring(52,64);
    //    }
    
        public static void main(String[] args) {
            Snowflake idWorker = new Snowflake();
    
            for (int i = 0; i < 10; i++) {
                long id = idWorker.nextId();
                System.out.println(id);
            }
        }
    }

      雪花算法 依赖于本地时钟。所以存在时钟回拨问题。Snowflake是一种约定,它把时间戳、工作组 ID、工作机器 ID、自增序列号组合在一起,生成一个 64bits 的整数ID,能够使用 (2^41)/(1000606024365) = 69.7 年,每台机器每毫秒理论最多生成 2^12 个 ID。那么,如何避免时钟回拨?可以参考下美团的实现:

      1. 机器号

      使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID,来保证机器id不重复。

      2. 相对时间

      每3秒上报timestamp。并且上报时,如果发现当前时间戳少于最后一次上报的时间戳,那么会放弃上报。防止在实例重启过程中,由于时钟回拨导致可能产生重复ID的问题。

  • 相关阅读:
    树链剖分( 洛谷P3384 )
    ZJOI 2015 诸神眷顾的幻想乡
    BZOJ 1002 [FJOI2007]轮状病毒
    洛谷 P1485 火枪打怪
    Luogu2860 [USACO06JAN]冗余路径Redundant Paths
    CF962F Simple Cycles Edges
    Luogu3605 [USACO17JAN]Promotion Counting晋升者计数
    Luogu2295 MICE
    CF341D Iahub and Xors
    CF617E XOR and Favorite Number
  • 原文地址:https://www.cnblogs.com/wlwl/p/16136188.html
Copyright © 2020-2023  润新知