zoukankan      html  css  js  c++  java
  • ID生成算法(一)——雪花算法

    JavaScript生成有序GUID或者UUID,这时就想到了雪花算法。

     

    原理介绍:

    snowFlake算法最终生成ID的结果为一个64bit大小的整数,结构如下图:

    解释:

    • 1bit。二进制中最高位为1表示负数,但是我们最终生成的ID一般都是整数,所以这个最高位固定为0。
    • 41bit。用于记录时间戳(毫秒)
      • 41bit可以表示241-1个数字
      • 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是0到241-1,减1是因为可表示的数值范围从0开始计算,而不是1.
      • 即41bit可以表示241-1个毫秒值转换为年为(241 - 1) / (1000 * 60 * 60 * 24 * 365) = 69.73年
    • 10bit。用于记录机器ID
      • 可以用于部署210=1024个节点,包含5bit 的 datacenterId 和5bit 的workerId
      • 5bit可以表示的最大正整数为25-1=31 即可以用0、1、2、3....31这32个数字来表示不同的datacenterId 和 workerId
    • 12bit。序列号用于记录相同毫秒内产生的不同ID
      • 12bit可以表示的最大正整数为212-1 = 4095,可以用0、1、2、3...4094这4095个数字来表示同一机器同一时间戳(毫秒)内产生的4095个ID序号

    snowFlake算法可以保证:所有生成的ID按时间趋势递增;整个分布式系统内不会产生重复ID,由于5bit 的 datacenterId 和5bit 的workerId来区分。 

     

    算法代码实现原理解释:

    计算机中负数的二进制是用补码来表示的。

    假设使用int类型来进行存储数字,int类型的大小是32bit二进制位,4个byte。(1byte = 8bit)

    那么十进制中的3在二进制中的表示应该是:

    00000000  00000000  00000000  00000011    // 3的二进制原码  

    那么数字 -3 在二进制中的表示应该是怎样的?试想: -3 + 3 = 0  在二进制运算中把 -3 的二进制看成未知数X来求解。

        00000000   00000000   00000000   00000011   // 3 原码
    +   xxxxxxxx   xxxxxxxx   xxxxxxxx   xxxxxxxx   //  -3 补码
    ------------------------------------------------------
        00000000   00000000   00000000   00000000

    反推X 即 二进制数从最低位开始逐位加1,使溢出的1不断向高位溢出,直到溢出到第33位,然后由于int类型最多只能保存32位二进制位,所以最高位的1溢出,剩余32位就成了0.

    则:

        00000000   00000000   00000000   00000011   // 3 原码
    +   11111111   11111111   11111111   11111101   //  -3 补码
    ---------------------------------------------------------
      1 00000000   00000000   00000000   00000000

    总结公式:

    • 补码 = 反码 + 1
    • 补码 = (原码 - 1) 再取反码

    workerIdBits = 5L;
    maxWorkerId = -1L ^ (-1L << workerIdBits);

    -1左移5位高位溢出的舍去后得到a,a与-1异或运算得到最终结果。

                   11111111   11111111   11111111   11111111   // -1补码
           11111   11111111   11111111   11111111   11100000   
    ---------------------------------------------------------------------
                   11111111   11111111   11111111   11100000   //  高位溢出舍弃  
                   11111111   11111111   11111111   11111111   // -1补码
        ^          11111111   11111111   11111111   11100000   
    ---------------------------------------------------------------------
                   00000000   00000000   00000000   00011111

    24+23+22+21+2= 16+8+4+2+1 = 31

    -1L ^ (-1L << 5L) = 31 也就是 25-1 = 31, 该写法是利用位运算计算出5位能表示的最大正整数是多少。

     

    用掩码mask防止溢出

    seq = (seq  + 1) & seqMask 

    这段代码通过按位与运算保证计算的结果范围始终是0 - 4095. 

     

    按位运算结果:

    return   ((timestamp - twepoch) << timestampLeftShift) |
                (datacnterId << datacenterIdShift) |
                (workerId << workerIdShift) |
                sequence;

    解析:

    var twepoch = 1571192786565; // 起始时间戳 用于当前时间戳减去这个时间戳得到偏移量
    var workerIdBits = 5; // workId占用的位数5
    var datacenterIdBit = 5;// datacenterId占用的位数5
    var maxWorkerId = -1 ^ (-1 << workerIdBits); // workId可以使用的最大数值31
    var maxDatacenterId = -1 ^ (-1 << datacenterIdBits); // datacenterId可以使用的最大数值31
    var sequenceBit = 12;// 序列号占用的位数12
    workerIdShift = sequenceBits; // 12 
    datacenterIdShift = sequenceBits + workerIdBits; // 12+5 = 17
    timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 12+5+5 = 22
    sequenceMask = -1 ^ (-1 << sequenceBits); // 4095
    lastTimestamp = -1;

      

    JavaScript中Number的最大值为Number.MAX_SAFE_INTEGER:9007199254740991。在雪花算法中,有的操作在JS中会溢出,所以选用BigInt实现雪花算法。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Snowflake</title> 
    </head> 
    <body> 
    <script>
      var Snowflake = (function() {
                function Snowflake(_workerId, _dataCenterId, _sequence) {
                    this.twepoch = 1288834974657n;
                    //this.twepoch = 0n;
                    this.workerIdBits = 5n;
                    this.dataCenterIdBits = 5n;
                    this.maxWrokerId = -1n ^ (-1n << this.workerIdBits); // 值为:31
                    this.maxDataCenterId = -1n ^ (-1n << this.dataCenterIdBits); // 值为:31
                    this.sequenceBits = 12n;
                    this.workerIdShift = this.sequenceBits; // 值为:12
                    this.dataCenterIdShift = this.sequenceBits + this.workerIdBits; // 值为:17
                    this.timestampLeftShift = this.sequenceBits + this.workerIdBits + this.dataCenterIdBits; // 值为:22
                    this.sequenceMask = -1n ^ (-1n << this.sequenceBits); // 值为:4095
                    this.lastTimestamp = -1n;
                    //设置默认值,从环境变量取
                    this.workerId = 1n;
                    this.dataCenterId = 1n;
                    this.sequence = 0n;
                    if (this.workerId > this.maxWrokerId || this.workerId < 0) {
                        throw new Error('_workerId must max than 0 and small than maxWrokerId-[' + this.maxWrokerId + ']');
                    }
                    if (this.dataCenterId > this.maxDataCenterId || this.dataCenterId < 0) {
                        throw new Error('_dataCenterId must max than 0 and small than maxDataCenterId-[' + this.maxDataCenterId + ']');
                    }
    
                    this.workerId = BigInt(_workerId);
                    this.dataCenterId = BigInt(_dataCenterId);
                    this.sequence = BigInt(_sequence);
                }
                Snowflake.prototype.tilNextMillis = function(lastTimestamp) {
                    var timestamp = this.timeGen();
                    while (timestamp <= lastTimestamp) {
                        timestamp = this.timeGen();
                    }
                    return BigInt(timestamp);
                };
                Snowflake.prototype.timeGen = function() {
                    return BigInt(Date.now());
                };
                Snowflake.prototype.nextId = function() {
                    var timestamp = this.timeGen();
                    if (timestamp < this.lastTimestamp) {
                        throw new Error('Clock moved backwards. Refusing to generate id for ' +
                            (this.lastTimestamp - timestamp));
                    }
                    if (this.lastTimestamp === timestamp) {
                        this.sequence = (this.sequence + 1n) & this.sequenceMask;
                        if (this.sequence === 0n) {
                            timestamp = this.tilNextMillis(this.lastTimestamp);
                        }
                    } else {
                        this.sequence = 0n;
                    }
                    this.lastTimestamp = timestamp;
                    return ((timestamp - this.twepoch) << this.timestampLeftShift) |
                        (this.dataCenterId << this.dataCenterIdShift) |
                        (this.workerId << this.workerIdShift) |
                        this.sequence;
                };
                return Snowflake;
            }());
            console.time();
            var tempSnowflake = new Snowflake(1n, 1n, 0n);
            var tempIds = [];
            for (var i = 0; i < 10000; i++) {
                var tempId = tempSnowflake.nextId();
                console.log(tempId);
                if (tempIds.indexOf(tempId) < 0) {
                    tempIds.push(tempId);
                }
            }
            console.log(tempIds.length);
            console.timeEnd();
        </script>
    </body>
    </html>

      

      

  • 相关阅读:
    Nginx安装
    win卸载输入法之后,在系统设置的键盘中还有这个输入法
    为Delphi 10.4.2实现android拍照填坑
    图文解说 ChinaCock 华为扫描
    Delphi Event Bus进阶(二)GlobalEventBus是怎么来的?
    小心SecondsBetween有坑
    Delphi Event Bus进阶(一)控制订阅方法的线程模式
    Delphi 10.4.2试用报告
    Delphi Event Bus入门
    UML建模——活动图(Activity Diagram)
  • 原文地址:https://www.cnblogs.com/sunyuweb/p/11673001.html
Copyright © 2011-2022 走看看