美文网首页
SnowFlake 深入理解

SnowFlake 深入理解

作者: oO白眉大虾Oo | 来源:发表于2021-03-10 08:56 被阅读0次

    SnowFlake算法

    image.png
    • 1位 不用, 二进制的最高位 1 代表的是负数, 生成的 ID 一般都是整数, 所以最高位用不到.

    • 41 位用来记录时间戳(毫秒)

      • 41 位可以表示 2^{{41}}-1
      • 41 位可以表示 2^{41}-1 个毫秒的值,转化成单位年则是(2^{41}-1) / (1000 * 60 * 60 * 24 * 365) = 69年
    • 10 位, 用来记录工作机器ID

      • 5 位 dataCenterId 和 5 位 machineId
      • 5 位 = 2^{5}-1 = 31
    • 12 位, 序列号, 用来表示同毫秒内生成的不同id

      • 12 位 = 2^{12}-1 = 4095
      • 表示同一个机器, 同一时间戳(毫秒) 内可以产生 4095 个ID

    算法 Java 实现

    代码摘自网络

    package org.example;
    
    import org.apache.commons.lang3.StringUtils;
    
    /**
     * twitter的snowflake算法 -- java实现
     *
     * @author beyond
     * @date 2016/11/26
     */
    public class SnowFlake {
    
        /**
         * 起始的时间戳, 2019-07-01 00:00:00
         */
        private final static long START_STAMP = 1561910400000L;
    
        /**
         * 序列号占用的位数
         */
        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); // 31
        private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); // 31
        private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); // 4095
    
        /**
         * 每一部分向左的位移
         */
        private final static long MACHINE_LEFT = SEQUENCE_BIT; // 左移12位
        private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; // 左移17位
        private final static long TIMESTAMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; // 左移22位
    
        /**
         * 数据中心
         */
        private final long datacenterId;
    
        /**
         * 机器标识
         */
        private final long machineId;
    
        /**
         * 序列号
         */
        private long sequence = 0L;
    
        /**
         * 上一次时间戳
         */
        private long lastStamp = -1L;
    
        public SnowFlake(long datacenterId, long machineId) {
            if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
                throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
            }
            if (machineId > MAX_MACHINE_NUM || machineId < 0) {
                throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
            }
            this.datacenterId = datacenterId;
            this.machineId = machineId;
        }
    
        /**
         * 产生下一个ID
         */
        public synchronized long nextId() {
            long currStamp = getNewStamp();
            if (currStamp < lastStamp) {
                throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
            }
    
            if (currStamp == lastStamp) {
                // 相同毫秒内,序列号自增
                sequence = (sequence + 1) & MAX_SEQUENCE;
                // 同一毫秒的序列数已经达到最大
                if (sequence == 0L) {
                    currStamp = getNextMill();
                }
            } else {
                // 不同毫秒内,序列号置为0
                sequence = 0L;
            }
    
            lastStamp = currStamp;
            return (currStamp - START_STAMP) << TIMESTAMP_LEFT // 时间戳部分
                    | datacenterId << DATACENTER_LEFT // 数据中心部分
                    | machineId << MACHINE_LEFT // 机器标识部分
                    | sequence; // 序列号部分
        }
    
        private long getNextMill() {
            long mill = getNewStamp();
            while (mill <= lastStamp) {
                mill = getNewStamp();
            }
            return mill;
        }
    
        private long getNewStamp() {
            return System.currentTimeMillis();
        }
    
    }
    

    代码比较简单, 比较难理解的应该是下面这段, 我们将其拆解并结合运行数据可以更好的理解

    return (currStamp - START_STAMP) << TIMESTAMP_LEFT // 时间戳部分
                    | datacenterId << DATACENTER_LEFT // 数据中心部分
                    | machineId << MACHINE_LEFT // 机器标识部分
                    | sequence; // 序列号部分
    
    数据准备
    START_STAMP = 1561910400000L ; // 2019-07-01 00:00:00, 自定义
    TIMESTAMP_LEFT = 22 ; // 左移22位, 表示时间戳部分
    DATACENTER_LEFT = 17 // 左移17位, 表示数据中心部分
    MACHINE_LEFT = 12  // 左移12位, 表示机器中心部分
    datacenterId = 1 // 数据中心ID
    machineId = 1 // 机器中心ID
    sequence = 0 // 序列号, 每毫秒内根据运行次数递增, 这里因为是第一次, 所以是0
    
    步骤分解
    S1: 计算时间戳之差
    (currStamp - START_STAMP) 
    00000000 00000000 00000000 00001100 01001000 01010010 01010110 00000011
    
    S2: S1左移22位(左边41位表示的是时间戳)
    (currStamp - START_STAMP) << TIMESTAMP_LEFT
    00000000 00000000 00000000 00001100 01001000 01010010 01010110 00000011 << 22
    00000011 00010010 00010100 10010101 10000000 11000000 00000000 00000000
      
    S3: 数据中心左移17位, 其中是5位机器ID和12位序列号
    datacenterId << DATACENTER_LEFT
    00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000
    
    S4: S2与S3取或运算
    (currStamp - START_STAMP) << TIMESTAMP_LEFT | datacenterId << DATACENTER_LEFT
    00000011 00010010 00010100 10010101 10000000 11000000 00000000 00000000 (S2结果)
    00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000 (S3结果)
    00000011 00010010 00010100 10010101 10000000 11000010 00000000 00000000 (S4结果)
    
    S5: 机器中心左移12位, 其中12位是序列号
    machineId << MACHINE_LEFT
    1 << 12
    00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000
    
    S6: S4与S5取或运算
    (currStamp - START_STAMP) << TIMESTAMP_LEFT | datacenterId << DATACENTER_LEFT | machineId << MACHINE_LEFT
    00000011 00010010 00010100 10010101 10000000 11000010 00000000 00000000 (S4结果)
    00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000 (S5结果)
    00000011 00010010 00010100 10010101 10000000 11000010 00010000 00000000 (S6结果)
      
    S7: S6与序列号取或运算
    (currStamp - START_STAMP) << TIMESTAMP_LEFT | datacenterId << DATACENTER_LEFT | machineId << MACHINE_LEFT | sequence
    00000011 00010010 00010100 10010101 10000000 11000010 00010000 00000000 (S6结果)
    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 (序列号结果, 目前是0)
    00000011 00010010 00010100 10010101 10000000 11000010 00010000 00000000 = 221261964037459968 (S7结果)  
      
    扫盲
    或运算符: 当对应二进位相异时,结果为1, 否则为0.
    
    

    扩展, 能否反推出时间

    思路是, 将 id 右移 22 位, 然后加上开始时间戳, 最后转成时间格式

        long id = 221261964037459968L;
      System.out.println(formatBinaryString(221261964037459968L) + "=" + id);
    
      // 1、右移动22位
      id = id >> 22;
      System.out.println(formatBinaryString(id) + "=" + id);
    
      // 2、加上开始时间戳
      id = id + 1561910400000L;
      System.out.println(Instant.ofEpochMilli(id).atZone(ZoneOffset.ofHours(8)).toLocalDateTime());
    

    相关文章

      网友评论

          本文标题:SnowFlake 深入理解

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