【分布式ID】理解Snowflake算法的实现原理

2023-11-17

在这里插入图片描述

1.概述

转载:冷饭新炒:理解Snowflake算法的实现原理
我上次也看了一个视频讲解:【分布式ID】键高并发 分布式 全局唯一 ID 雪花算法 snowflake

2.前提#

Snowflake(雪花)是Twitter开源的高性能ID生成算法(服务)。

在这里插入图片描述
上图是Snowflake的Github仓库,master分支中的REAEMDE文件中提示:初始版本于2010年发布,基于Apache Thrift,早于Finagle(这里的Finagle是Twitter上用于RPC服务的构建模块)发布,而Twitter内部使用的Snowflake是一个完全重写的程序,在很大程度上依靠Twitter上的现有基础架构来运行。

而2010年发布的初版Snowflake源码是使用Scala语言编写的,归档于scala_28分支。换言之,大家目前使用的Snowflake算法原版或者改良版已经是十年前(当前是2020年)的产物,不得不说这个算法确实比较厉害。scala_28分支中有介绍该算法的动机和要求,这里简单摘录一下:

2.1 动机:

Cassandra中没有生成顺序ID的工具,Twitter由使用MySQL转向使用Cassandra的时候需要一种新的方式来生成ID(印证了架构不是设计出来,而是基于业务场景迭代出来)。

2.2 要求:

  • 高性能:每秒每个进程至少产生10K个ID,加上网络延迟响应速度要在2ms内。
  • 顺序性:具备按照时间的自增趋势,可以直接排序。
  • 紧凑性:保持生成的ID的长度在64 bit或更短。
  • 高可用:ID生成方案需要和存储服务一样高可用。

下面就Snowflake的源码分析一下他的实现原理。

3.Snowflake方案简述

Snowflake在初版设计方案是:

  • 时间:41 bit长度,使用毫秒级别精度,带有一个自定义epoch,那么可以使用大概69年。
  • 可配置的机器ID:10 bit长度,可以满足1024个机器使用。
  • 序列号:12 bit长度,可以在4096个数字中随机取值,从而避免单个机器在1 ms内生成重复的序列号。

在这里插入图片描述
但是在实际源码实现中,Snowflake把10 bit的可配置的机器ID拆分为5 bit的Worker ID(这个可以理解为原来的机器ID)和5 bit的Data Center ID(数据中心ID),详情见IdWorker.scala:

在这里插入图片描述
也就是说,支持配置最多32个机器ID和最多32个数据中心ID:

在这里插入图片描述由于算法是Scala语言编写,是依赖于JVM的语言,返回的ID值为Long类型,也就是64 bit的整数,原来的算法生成序列中只使用了63 bit的长度,要返回的是无符号数,所以在高位补一个0(占用1 bit),那么加起来整个ID的长度就是64 bit:
在这里插入图片描述
其中:

  • 41 bit毫秒级别时间戳的取值范围是:[0, 2^41 - 1] => 0 ~ 2199023255551,一共2199023255552个数字。
  • 5 bit机器ID的取值范围是:[0, 2^5 - 1] => 0 ~ 31,一共32个数字。
  • 5 bit数据中心ID的取值范围是:[0, 2^5 - 1] => 0 ~ 31,一共32个数字。
  • 12 bit序列号的取值范围是:[0, 2^12 - 1] => 0 ~ 4095,一共4096个数字。

那么理论上可以生成2199023255552 * 32 * 32 * 4096个完全不同的ID值。

Snowflake算法还有一个明显的特征:依赖于系统时钟。41 bit长度毫秒级别的时间来源于系统时间戳,所以必须保证系统时间是向前递进,不能发生时钟回拨(通说来说就是不能在同一个时刻产生多个相同的时间戳或者产生了过去的时间戳)。一旦发生时钟回拨,Snowflake会拒绝生成下一个ID。

4.位运算知识补充#

Snowflake算法中使用了大量的位运算。由于整数的补码才是在计算机中的存储形式,Java或者Scala中的整型都使用补码表示,这里稍微提一下原码和补码的知识。

  • 原码用于阅读,补码用于计算。
  • 正数的补码与其原码相同。
  • 负数的补码是除最高位其他所有位取反,然后加1(反码加1),而负数的补码还原为原码也是使用这个方式。
  • +0的原码是0000 0000,而-0的原码是1000 0000,补码只有一个0值,用0000 0000表示,这一点很重要,补码的0没有二义性。

简单来看就是这样:

* [+ 11] 原码 = [0000 1011] 补码 = [0000 1011]
* [- 11] 原码 = [1000 1011] 补码 = [1111 0101]

* [- 11]的补码计算过程: 
        原码                  1000 1011
        除了最高位其他位取反   1111 01001                   1111 0101  (补码) 

使用原码、反码在计算的时候得到的不一定是准确的值,而使用补码的时候计算结果才是正确的,记住这个结论即可,这里不在举例。由于Snowflake的ID生成方案中,除了最高位,其他四个部分都是无符号整数,所以四个部分的整数使用补码进行位运算的效率会比较高,也只有这样才能满足Snowflake高性能设计的初衷。Snowflake算法中使用了几种位运算:异或(^)、按位与(&)、按位或(|)和带符号左移(<<)。

4.1 异或

异或的运算规则是:0^0=0 0^1=1 1^0=1 1^1=0,也就是位不同则结果为1,位相同则结果为0。主要作用是:

  • 特定位翻转,也就是一个数和N个位都为1的数进行异或操作,这对应的N个位都会翻转,例如0100 & 1111,结果就是1011。
  • 与0项异或,则结果和原来的值一致。
  • 两数的值交互:a=a^b b=b^a a=a^b,这三个操作完成之后,a和b的值完成交换。

这里推演一下最后一条:

* [+ 11] 原码 = [0000 1011] 补码 = [0000 1011] a
* [- 11] 原码 = [1000 1011] 补码 = [1111 0101] b

a=a^b          0000 1011
               1111 0101
               ---------^
               1111 1110
b=b^a          1111 0101
               ---------^
               0000 1011  (十进制数:11) b
a=a^b          1111 1110
               ---------^
               1111 0101  (十进制数:-11) a 

4.2 按位与#

按位与的运算规则是:0&0=0 0&1=0 1&0=0 1&1=1,只有对应的位都为1的时候计算结果才是1,其他情况的计算结果都是0。主要作用是:

  • 清零,如果想把一个数清零,那么和所有位为0的数进行按位与即可。
  • 取一个数中的指定位,例如要取X中的低4位,只需要和zzzz…1111进行按位与即可,例如取1111 0110的低4位,则11110110 & 00001111即可得到00000110。

4.3 按位或

按位与的运算规则是:0|0=0 0|1=1 1|0=1 1|1=1,只要有其中一个位存在1则计算结果是1,只有两个位同时为0的情况下计算结果才是0。主要作用是:

  • 对一个数的部分位赋值为1,只需要和对应位全为0的数做按位或操作就行,例如1011 0000如果低4位想全部赋值为1,那么10110000 | 00001111即可得到1011 1111。

4.4 带符号左移

带符号左移的运算符是<<,一般格式是:M << n。作用如下:

  • M的二进制数(补码)向左移动n位。
  • 左边(高位)移出部分直接舍弃,右边(低位)移入部分全部补0。
  • 移位结果:相当于M的值乘以2的n次方,并且0、正、负数通用。
  • 移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取模,例如int移位33位,实际上只移动了33 % 2 = 1位。

推演过程如下(假设n = 2):

* [+ 11] 原码 = [0000 1011] 补码 = [0000 1011]
* [- 11] 原码 = [1000 1011] 补码 = [1111 0101]

* [+ 11 << 2]的计算过程
      补码          0000 1011
      左移20000 1011  
      舍高补低      0010 1100
      十进制数    2^2 + 2^3 + 2^5 = 44

* [- 11 << 2]的计算过程
      补码          1111 0101
      左移21111 0101  
      舍高补低      1101 0100 
      原码          1010 1100 (补码除最高位其他所有位取反再加1)
      十进制数    - (2^2 + 2^3 + 2^5) = -44

可以写个main方法验证一下:

public static void main(String[] args) {
System.out.println(-11 << 2); // -44
System.out.println(11 << 2); // 44
}

4.5 组合技巧#

利用上面提到的三个位运算符,相互组合可以实现一些高效的计算方案。

计算n个bit能表示的最大数值

Snowflake算法中有这样的代码:

// 机器ID的位长度
private val workerIdBits = 5L;
// 最大机器ID -> 31
private val maxWorkerId = -1L ^ (-1L << workerIdBits);

这里的算子是-1L ^ (-1L << 5L),整理运算符的顺序,再使用64 bit的二进制数推演计算过程如下:

* [-1] 的补码         11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
  左移511111111 11111111 11111111 11111111 11111111 11111111 11111111 11100000
  [-1] 的补码         11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
  异或                ----------------------------------------------------------------------- ^ 
  结果的补码          00000000 00000000 00000000 00000000 00000000 00000000 00000000 00011111  (十进制数 2^0 + 2^1 + 2^2 + 2^3 + 2^4 = 31

这样就能计算出5 bit能表示的最大数值n,n为整数并且0 <= n <= 31,即0、1、2、3…31。Worker ID和Data Center ID部分的最大值就是使用这种组合运算得出的。

用固定位的最大值作为Mask避免溢出

Snowflake算法中有这样的代码:

var sequence = 0L
......
private val sequenceBits = 12L
// 这里得到的是sequence的最大值4095
private val sequenceMask = -1L ^ (-1L << sequenceBits)
......
sequence = (sequence + 1) & sequenceMask

最后这个算子其实就是sequence = (sequence + 1) & 4095,假设sequence当前值为4095,推演一下计算过程:

* [4095] 的补码                 00000000 00000000 00000000 00000000 00000000 00000000 00000111 11111111
  [sequence + 1] 的补码         00000000 00000000 00000000 00000000 00000000 00000000 00001000 00000000
  按位与                        ----------------------------------------------------------------------- &
  计算结果                      00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000  (十进制数:0

可以编写一个main方法验证一下:

public static void main(String[] args) {
    int mask = 4095;
    System.out.println(0 & mask); // 0
    System.out.println(1 & mask); // 1
    System.out.println(2 & mask); // 2
    System.out.println(4095 & mask); // 4095
    System.out.println(4096 & mask); // 0
    System.out.println(4097 & mask); // 1
}

也就是x = (x + 1) & (-1L ^ (-1L << N))能保证最终得到的x值不会超过N,这是利用了按位与中的"取指定位"的特性。

5. Snowflake算法实现源码分析#

Snowflake虽然用Scala语言编写,语法其实和Java差不多,当成Java代码这样阅读就行,下面阅读代码的时候会跳过一些日志记录和度量统计的逻辑。先看IdWorker.scala的属性值:

// 定义基准纪元值,这个值是北京时间2010-11-04 09:42:54,估计就是2010年初版提交代码时候定义的一个时间戳
val twepoch = 1288834974657L

// 初始化序列号为0
var sequence = 0L //TODO after 2.8 make this a constructor param with a default of 0

// 机器ID的最大位长度为5
private val workerIdBits = 5L

// 数据中心ID的最大位长度为5
private val datacenterIdBits = 5L

// 最大的机器ID值,十进制数为为31
private val maxWorkerId = -1L ^ (-1L << workerIdBits)

// 最大的数据中心ID值,十进制数为为31
private val maxDatacenterId = -1L ^ (-1L << datacenterIdBits)

// 序列号的最大位长度为12
private val sequenceBits = 12L

// 机器ID需要左移的位数12
private val workerIdShift = sequenceBits

// 数据中心ID需要左移的位数 = 12 + 5
private val datacenterIdShift = sequenceBits + workerIdBits

// 时间戳需要左移的位数 = 12 + 5 + 5
private val timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits

// 序列号的掩码,十进制数为4095
private val sequenceMask = -1L ^ (-1L << sequenceBits)

// 初始化上一个时间戳快照值为-1
private var lastTimestamp = -1L

// 下面的代码块为参数校验和初始化日志打印,这里不做分析
if (workerId > maxWorkerId || workerId < 0) {
exceptionCounter.incr(1)
throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0".format(maxWorkerId))
}

if (datacenterId > maxDatacenterId || datacenterId < 0) {
exceptionCounter.incr(1)
throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0".format(maxDatacenterId))
}

log.info("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId)

在这里插入图片描述

接着看算法的核心代码逻辑:

// 同步方法,其实就是protected synchronized long nextId(){ ...... }
protected[snowflake] def nextId(): Long = synchronized {
    // 获取系统时间戳(毫秒)
    var timestamp = timeGen()
    // 高并发场景,同一毫秒内生成多个ID
    if (lastTimestamp == timestamp) {
        // 确保sequence + 1之后不会溢出,最大值为4095,其实也就是保证1毫秒内最多生成4096个ID值
        sequence = (sequence + 1) & sequenceMask
        // 如果sequence溢出则变为0,说明1毫秒内并发生成的ID数量超过了4096个,这个时候同1毫秒的第4097个生成的ID必须等待下一毫秒
        if (sequence == 0) {
            // 死循环等待下一个毫秒值,直到比lastTimestamp大
            timestamp = tilNextMillis(lastTimestamp)
        }
    } else {
        // 低并发场景,不同毫秒中生成ID
        // 不同毫秒的情况下,由于外层方法保证了timestamp大于或者小于lastTimestamp,而小于的情况是发生了时钟回拨,下面会抛出异常,所以不用考虑
        // 也就是只需要考虑一种情况:timestamp > lastTimestamp,也就是当前生成的ID所在的毫秒数比上一个ID大
        // 所以如果时间戳部分增大,可以确定整数值一定变大,所以序列号其实可以不用计算,这里直接赋值为0
        sequence = 0
    }
    // 获取到的时间戳比上一个保存的时间戳小,说明时钟回拨,这种情况下直接抛出异常,拒绝生成ID
    // 个人认为,这个方法应该可以提前到var timestamp = timeGen()这段代码之后
    if (timestamp < lastTimestamp) {
      exceptionCounter.incr(1)
      log.error("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
      throw new InvalidSystemClock("Clock moved backwards.  Refusing to generate id for %d milliseconds".format(lastTimestamp - timestamp));
    }
    // lastTimestamp保存当前时间戳,作为方法下次被调用的上一个时间戳的快照
    lastTimestamp = timestamp
    // 度量统计,生成的ID计数器加1
    genCounter.incr()
    // X = (系统时间戳 - 自定义的纪元值) 然后左移22位
    // Y = (数据中心ID左移17位)
    // Z = (机器ID左移12位)
    // 最后ID = X | Y | Z | 计算出来的序列号sequence
    ((timestamp - twepoch) << timestampLeftShift) |
      (datacenterId << datacenterIdShift) |
      (workerId << workerIdShift) | 
      sequence
}

// 辅助方法:获取系统当前的时间戳(毫秒)
protected def timeGen(): Long = System.currentTimeMillis()

// 辅助方法:获取系统当前的时间戳(毫秒),用死循环保证比传入的lastTimestamp大,也就是获取下一个比lastTimestamp大的毫秒数
protected def tilNextMillis(lastTimestamp: Long): Long = {
    var timestamp = timeGen()
    while (timestamp <= lastTimestamp) {
      timestamp = timeGen()
    }
    timestamp
}

最后一段逻辑的位操作比较多,但是如果熟练使用位运算操作符,其实逻辑并不复杂,这里可以画个图推演一下:

在这里插入图片描述

四个部分的整数完成左移之后,由于空缺的低位都会补充了0,基于按位或的特性,所有低位只要存在1,那么对应的位就会填充为1,由于四个部分的位不会越界分配,所以这里的本质就是:四个部分左移完毕后最终的数字进行加法计算。

6.Snowflake算法改良#

Snowflake算法有几个比较大的问题:

  • 低并发场景会产生连续偶数,原因是低并发场景系统时钟总是走到下一个毫秒值,导致序列号重置为0。
  • 依赖系统时钟,时钟回拨会拒绝生成新的ID(直接抛出异常)。
  • Woker ID和Data Center ID的管理比较麻烦,特别是同一个服务的不同集群节点需要保证每个节点的Woker ID和Data Center ID组合唯一。

这三个问题美团开源的Leaf提供了解决思路,下图截取自com.sankuai.inf.leaf.snowflake.SnowflakeIDGenImpl:

在这里插入图片描述

对应的解决思路是(不进行深入的源码分析,有兴趣可以阅读以下Leaf的源码):

  • 序列号生成添加随机源,会稍微减少同一个毫秒内能产生的最大ID数量。
  • 时钟回拨则进行一定期限的等待。
  • 使用Zookeeper缓存和管理Woker ID和Data Center ID。

Woker ID和Data Center ID的配置是极其重要的,对于同一个服务(例如支付服务)集群的多个节点,必须配置不同的机器ID和数据中心ID或者同样的数据中心ID和不同的机器ID(简单说就是确保Woker ID和Data Center ID的组合全局唯一),否则在高并发的场景下,在系统时钟一致的情况下,很容易在多个节点产生相同的ID值,所以一般的部署架构如下:

在这里插入图片描述

管理这两个ID的方式有很多种,或者像Leaf这样的开源框架引入分布式缓存进行管理,再如笔者所在的创业小团队生产服务比较少,直接把Woker ID和Data Center ID硬编码在服务启动脚本中,然后把所有服务使用的Woker ID和Data Center ID统一登记在团队内部知识库中。

7.自实现简化版Snowflake#

如果完全不考虑性能的话,也不考虑时钟回拨、序列号生成等等问题,其实可以把Snowflake的位运算和异常处理部分全部去掉,使用Long.toBinaryString()方法结合字符串按照Snowflake算法思路拼接出64 bit的二进制数,再通过Long.parseLong()方法转化为Long类型。编写一个main方法如下:

public class Main {

    private static final String HIGH = "0";

    /**
     * 2020-08-01 00:00:00
     */
    private static final long EPOCH = 1596211200000L;

    public static void main(String[] args) {
        long workerId = 1L;
        long dataCenterId = 1L;
        long seq = 4095;
        String timestampString = leftPadding(Long.toBinaryString(System.currentTimeMillis() - EPOCH), 41);
        String workerIdString = leftPadding(Long.toBinaryString(workerId), 5);
        String dataCenterIdString = leftPadding(Long.toBinaryString(dataCenterId), 5);
        String seqString = leftPadding(Long.toBinaryString(seq), 12);
        String value = HIGH + timestampString + workerIdString + dataCenterIdString + seqString;
        long num = Long.parseLong(value, 2);
        System.out.println(num);   // 某个时刻输出为3125927076831231
    }

    private static String leftPadding(String value, int maxLength) {
        int diff = maxLength - value.length();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < diff; i++) {
            builder.append("0");
        }
        builder.append(value);
        return builder.toString();
    }
}
然后把代码规范一下,编写出一个简版Snowflake算法实现的工程化代码:

// 主键生成器接口
public interface PrimaryKeyGenerator {

    long generate();
}

// 简易Snowflake实现
public class SimpleSnowflake implements PrimaryKeyGenerator {

    private static final String HIGH = "0";
    private static final long MAX_WORKER_ID = 31;
    private static final long MIN_WORKER_ID = 0;

    private static final long MAX_DC_ID = 31;
    private static final long MIN_DC_ID = 0;

    private static final long MAX_SEQUENCE = 4095;

    /**
     * 机器ID
     */
    private final long workerId;

    /**
     * 数据中心ID
     */
    private final long dataCenterId;

    /**
     * 基准纪元值
     */
    private final long epoch;

    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SimpleSnowflake(long workerId, long dataCenterId, long epoch) {
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
        this.epoch = epoch;
        checkArgs();
    }

    private void checkArgs() {
        if (!(MIN_WORKER_ID <= workerId && workerId <= MAX_WORKER_ID)) {
            throw new IllegalArgumentException("Worker id must be in [0,31]");
        }
        if (!(MIN_DC_ID <= dataCenterId && dataCenterId <= MAX_DC_ID)) {
            throw new IllegalArgumentException("Data center id must be in [0,31]");
        }
    }

    @Override
    public synchronized long generate() {
        long timestamp = System.currentTimeMillis();
        // 时钟回拨
        if (timestamp < lastTimestamp) {
            throw new IllegalStateException("Clock moved backwards");
        }
        // 同一毫秒内并发
        if (lastTimestamp == timestamp) {
            sequence = sequence + 1;
            if (sequence == MAX_SEQUENCE) {
                timestamp = untilNextMillis(lastTimestamp);
                sequence = 0L;
            }
        } else {
            // 下一毫秒重置sequence为0
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        // 41位时间戳字符串,不够位数左边补"0"
        String timestampString = leftPadding(Long.toBinaryString(timestamp - epoch), 41);
        // 5位机器ID字符串,不够位数左边补"0"
        String workerIdString = leftPadding(Long.toBinaryString(workerId), 5);
        // 5位数据中心ID字符串,不够位数左边补"0"
        String dataCenterIdString = leftPadding(Long.toBinaryString(dataCenterId), 5);
        // 12位序列号字符串,不够位数左边补"0"
        String seqString = leftPadding(Long.toBinaryString(sequence), 12);
        String value = HIGH + timestampString + workerIdString + dataCenterIdString + seqString;
        return Long.parseLong(value, 2);
    }

    private long untilNextMillis(long lastTimestamp) {
        long timestamp;
        do {
            timestamp = System.currentTimeMillis();
        } while (timestamp <= lastTimestamp);
        return timestamp;
    }

    private static String leftPadding(String value, int maxLength) {
        int diff = maxLength - value.length();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < diff; i++) {
            builder.append("0");
        }
        builder.append(value);
        return builder.toString();
    }

    public static void main(String[] args) {
        long epoch = LocalDateTime.of(1970, 1, 1, 0, 0, 0, 0)
                .toInstant(ZoneOffset.of("+8")).toEpochMilli();
        PrimaryKeyGenerator generator = new SimpleSnowflake(1L, 1L, epoch);
        for (int i = 0; i < 5; i++) {
            System.out.println(String.format("第%s个生成的ID: %d", i + 1, generator.generate()));
        }
    }
}
// 某个时刻输出如下1个生成的ID: 66982479663665029122个生成的ID: 66982480274481520643个生成的ID: 66982480321625497604个生成的ID: 66982480330769080325个生成的ID: 6698248033827688448

通过字符串拼接的写法虽然运行效率低,但是可读性会比较高,工程化处理后的代码可以在实例化时候直接指定Worker ID和Data Center ID等值,并且这个简易的Snowflake实现没有第三方库依赖,拷贝下来可以直接运行。上面的方法使用字符串拼接看起来比较低端,其实最后那部分的按位或,可以完全转化为加法:

public class Main {
    
    /**
     * 2020-08-01 00:00:00
     */
    private static final long EPOCH = 1596211200000L;

    public static void main(String[] args) {
        long workerId = 1L;
        long dataCenterId = 1L;
        long seq = 4095;
        long timestampDiff = System.currentTimeMillis() - EPOCH;
        long num = (long) (timestampDiff * Math.pow(2, 22)) + (long) (dataCenterId * Math.pow(2, 17)) + (long) (workerId * Math.pow(2, 12)) + seq;
        System.out.println(num);   // 某个时刻输出为3248473482862591
    }
}

这样看起来整个算法都变得简单,不过这里涉及到指数运算和加法运算,效率会比较低。

8.小结#

Snowflake算法是以高性能为核心目标的算法,基于这一点目的巧妙地大量使用位运算,这篇文章已经把Snowflake中应用到的位运算和具体源码实现彻底分析清楚。最后,基于Twitter官方的Snowflake算法源码,修订出了一版Java实现版本,并且应用前面提到的改良方式,修复了低并发场景下只产生偶数的问题,并且已经应用于生产环境一段时间,代码仓库如下(代码没有任何第三方库依赖,拷贝出来就直接可用):

Github:https://github.com/zjcscut/framework-mesh/tree/master/java-snowflake
参考资料:

美团Leaf源码
Twitter Snowflake
(本文完 c-3-d e-a-20200809 封面图来源于国漫《灵笼》)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【分布式ID】理解Snowflake算法的实现原理 的相关文章

  • QT5.9.4 + opencv3.0.0编译配置

    QT5 9 4 opencv3 0 0编译配置 1 安装QT5 9 4 QT下载地址 http download qt io archive qt 安装完毕之后将以下目录加入到系统环境变量 E Qt Qt5 9 4 5 9 4 mingw5
  • windows系统pycharm安装,opencv安装,anaconda安装

    1 python IDE安装 3 9 https www python org getit 2 pycharm安装 社区版最新 https www jetbrains com pycharm 3 anaconda3安装 https www
  • Electron 自定义 Dock 图标

    转载自https cloud tencent com developer article 1650700 学透 Electron 自定义 Dock 图标 Mac OS 做为前端开发者的首选操作系统相信大家再熟悉不过了 在电脑主界面的底部可以
  • epoll在多线程中的应用-EPOLLEXCLUSIVE和REUSEPORT(一)

    以下均为对epoll在多线程中的使用的一些笔记 如果有不对的地方 烦请指出 主要对于我所遇到的问题进行讨论 不会讨论代码如何改写 探讨如何解决这个问题 一 引言 这些问题均是我在编写我的Web服务器遇到的 我在编写多线程Web服务器的时候

随机推荐

  • Docker 镜像库国内加速的几种方法

    概述 在国内 拉取 Docker 镜像速度慢 时不时断线 无账号导致限流等 比较痛苦 这里提供加速 优化的几种方法 梳理一下 会碰到以下情况 国内下载速度慢 时不时断线 是因为网络被限制了 没有公共镜像库账号导致限流 是因为 Docker
  • 「网页开发|前端开发|Vue」01 快速入门:快速写一个Vue的HelloWorld项目

    本文主要介绍如何用vue开发的标准化工具vue cli快速搭建一个符合实际业务项目结构的hello world网页项目并理解vue的代码文件结构以及页面渲染流程 文章目录 一 准备工作 安装node js 二 项目搭建 创建项目目录 全局安
  • 谁来教我渗透测试——黑客应该掌握的Windows基础

    今天我们看看作为一个黑客对于Windows应该掌握哪些基础知识 主要内容包含以下四个方面 系统目录 服务 端口和注册表 黑客常用的DOS命令及批处理文件的编写 黑客常用的快捷键 以及如何优化系统 登录密码破解 手动清除木马病毒 系统目录 服
  • 2014年总结

    总结的意义在于认清未来的方向 2014年工作 1 ETL Data Warehouse Data Mining 数据挖掘内容很多 如何与企业需求相结合是重点 2 简单的工作流系统开发 3 体会ArgGIS在物流运输企业中的应用 无论云计算以
  • 色彩空间与像素格式

    转载来自 https www cnblogs com leisure chn p 10290575 html 1 色彩空间基础 颜色是不同波长的光对人眼刺激产生的色彩感觉 色彩空间 Color Space 是颜色的数学表示 根据不同的表示方
  • PSO优化LSTM

    有两个py文件 PSO 1和LSTM 1 在资源那里下载 有数据 环境 python TF2 优化的参数有 神隐藏神经元个数 dropout比率 batch size 这个可以根据自己的意愿改 规定上限和下限 UP 64 0 14 32 D
  • java跨时区问题【相差8小时】

    情况一 后端传递给前端 前端展示到页面中的时间与系统时间相差8小时 解决方法 在该类的日期属性字段上加上注解 JsonFormat pattern yyyy MM dd HH mm ss timezone GMT 8 情况二 展示数据时间与
  • 解决Chrome, NET::ERR_CERT_AUTHORITY_INVALID

    文章目录 前言 解决方法一 解决方法二 总结 前言 解决方法一 首先清理一下缓存 三个点 gt 设置 gt 清除浏览数据 即可 如果还解决不了 因为Chrome是默认使用HSTS传输 严格的http传输方式 解决方法二 在Chrome浏览框
  • C++如何切割String对象

    C 如何切割String对象 C 相较于Java Python 并没有提供的字符串分割的函数split 因此需要自己进行编写 在实际的工作中这一功能会被经常使用 所以进行简单的记录一下 核心函数 代码实现的函数是调用String库中的fin
  • 数学:矩阵求导

    矩阵Y对标量x求导 Y y ij dY dx dy ji dx 求导后 Y变转置了 标量y对矩阵X求导 dy dX Dy Dx ij 求导后 不需要转置 重要结论 y U XV u i x ij v j 于是 dy dX u i v j U
  • ACM-子串(字符串处理)

    问题描述 有一些由英文字符组成的大小写敏感的字符串 请写一个程序 找到一个最长的字符串 x 使得 对于已经给出的字符串中的任意一个 y x 或者是 y 的子串 或者 x 中的字符反序之后得到的新字符串是 y 的子串 输入数据 输入 输入的第
  • vue使用高德地图导航路线失败,获取驾车数据失败:INVALID_USER_SCODE(解决方法)

    高德地图使用路线导航 最近做大屏项目 里面有地图定位 路线导航等方面的功能 在做路线定位是一直都是获取不到导航的路线 经过好长时间的百度看高德api文档终于找到解决方法 解决方法 添加高德地图安全密钥 添加高德安全密钥 安全密钥是和key一
  • 计算机毕业设计-基于SSM的网上书店管理系统

    项目背景 本网上系统是针对目前网上的实际需求 从实际工作出发 对过去的网上系统存在的问题进行分析 结合计算机系统的结构 概念 模型 原理 方法 在计算机各种优势的情况下 采用目前最流行的B S结构和java中流行的ssm框架和eclipse
  • jq ajax fail,javascript - jQuery.ajax fail handler not called - Stack Overflow

    Deprecation Notice The jqXHR success jqXHR error and jqXHR complete callbacks are removed as of jQuery 3 0 You can use j
  • 【Linux】-关于调试器gdb的介绍和使用

    作者 小树苗渴望变成参天大树 作者宣言 认真写好每一篇博客 作者gitee gitee 如 果 你 喜 欢 作 者 的 文 章 就 给 作 者 点 点 关 注 吧 文章目录 前言 一 Linux中的debug和release 二 gdb的使
  • Java作用域

    try catch里面的是局部变量 java变量的作用域分为四个级别 类级 对象实例级 方法级 块级 块级变量就是定义在一个块 内部的变量都是局部变量 try 中try后面的 就是一个块级作用域 所以内部定义的变量是局部变量 在Java中
  • Android自定义导览地图组件(一)

    丨版权说明 Android自定义导览地图组件 一 于当前CSDN博客和乘月网属同一原创 转载请说明出处 谢谢 鉴于Android关于自定义导览地图的相关资料以及开源项目贫乏 应Android同行几位小伙伴们的建议 决定写下这篇文章分享给大家
  • 团队管理的一些总结

    这将近三年 我在公司经历了很多事情 在学习中成长 很多时候我都在总结每个步骤和过程我取得的成绩和失败 如果说我们需要一个有效性高的团队 需要一个能够创造奇迹的团队 那么我觉得如下这些就是我经历了这么多得到的启示 1 必须明确目标 当然这个目
  • CANopen基本概念学习笔记

    参考文献 Kinco FD CD3系列伺服驱动器使用手册20210125 CANopen协议讲解课件 信捷 CANopen 通讯用户手册 基于CANopen协议的网络主控制器的设计 前置文章 CAN总线技术基本概念简述 0 CANopen简
  • 【分布式ID】理解Snowflake算法的实现原理

    1 概述 转载 冷饭新炒 理解Snowflake算法的实现原理 我上次也看了一个视频讲解 分布式ID 键高并发 分布式 全局唯一 ID 雪花算法 snowflake 2 前提 Snowflake 雪花 是Twitter开源的高性能ID生成算