算法--基础--12--SnowFlake

算法–基础–12–SnowFlake


1、介绍

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

2、结构

在这里插入图片描述

0 - 0001000000 0000010000 0001000100 0000100000 0 - 10001 - 11001 - 000000000000 

2.1、第1部分:1bit

  1. 1 个 bit
  2. 这个是无意义的
无意义原因

因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。

2.2、第2部分:41 bit

  1. 41 个 bit
  2. 表示时间戳。
  3. 单位是毫秒。
  4. 可以使用69年
可以使用69年的原因

41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成年就是表示 69 年的时间。

2.3、第3部分:10 bit

  1. 记录工作的机器 id,代表的是这个雪花算法服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。

  2. 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2 ^ 5 个机房(32 个机房),每个机房里可以代表 2 ^ 5 个机器(32 台机器),也可以根据自己公司的实际情况确定。

2.3.1、第1部分:5bit

  1. 5 个 bit
  2. 表示机房 id,如上面10001

2.3.2、第2部分:5bit

  1. 5 个 bit
  2. 表示机器id,如上面11001

2.4、第4部分:12 个 bit

  1. 12 个 bit:
  2. 表示序列号
    1. 表示10001机房11001机器上这一毫秒内同时生成的id 的序号
12 个 bit 可以生产多少序列号

12 bit 可以代表的最大正整数是 2 ^ 12= 4096,也就是同一个毫秒内可以生成 4096 个不同的 id。也就是0到4095。

3、总结

3.1、生成唯一 id的过程

假设你的某个服务假设要生成一个全局唯一 id,那么就可以发送一个请求给部署了 SnowFlake 算法的系统,由这个 SnowFlake 算法系统来生成唯一 id。

这个 SnowFlake 算法系统首先肯定是知道自己所在的机房和机器的,比如机房 id = 17,机器 id = 25。

接着 SnowFlake 算法系统接收到这个请求之后,首先就会用二进制位运算的方式生成一个 64 bit 的 long 型id

步骤1: 1bit

给1bit 设置为0

步骤2: 41bit

用当前时间戳(单位到毫秒)生成41bit

步骤3: 5bit

机房 id = 17,生成5bit

步骤4: 5bit

机器 id = 25,生成5bit

步骤5: 12bit

最后再判断一下,当前这台机房的这台机器上这一毫秒内,这是第几个请求,给这次生成 id 的请求累加一个序号,作为最后的 12 个 bit。

最终一个 64 个 bit 的 id 就出来了

这个算法可以保证说,一个机房的一台机器上,在同一毫秒内,生成了一个唯一的 id。可能一个毫秒内会生成多个 id,但是有最后 12 个 bit 的序号来区分开来。

3.2、小结

用一个 64 bit 的数字中各个 bit 位来设置不同的标志位,区分每一个 id。

4、代码

package com.example.snowflake.demo1;

public class SnowFlake {


    /**
     * 起始的时间戳 2016-11-26 21:21:05
     */
    private final static long START_STAMP = 1480166465631L;
    
    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 12; // 序列号占用的位数
    private final static long MACHINE_BIT = 5; // 机器标识占用的位数
    private final static long DATA_CENTER_BIT = 5;// 数据中心占用的位数
    
    /**
     * 每一部分的最大值
     * MAX_DATA_CENTER_NUM的计算过程
     *  DATA_CENTER_BIT=5,对应的补码: 0000 0101
     *  -1L对应的补码: 1111 1111
     *  -1L <<5 后:1110 0000
     *  -1L ^ 1110 0000后 0001 1111
     *  0001 1111  等于31
     */

    // 机房的最大id,也就是机房的最大数量
    // 这里是31
    private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);
    // 机器的最大id,也就是机器的最大数量
    // 这里是31
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    // 序列号的最大值
    // 这里是 4095
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
    
    /**
     * 每一部分向左的位移
     */
    // 机器的二进制数据左位移  12位
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    // 机房的二进制数据左位移  17位
    private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    // 时间戳的二进制数据左位移 22位
    private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;

    // 机房ID,5个bit,最大2^5=32,0到31,即(0000 0000-0001 1111)
    private long dataCenterId;
    // 机器ID,5个bit,最大2^5=32,0到31,即(0000 0000-0001 1111)
    private long machineId;
    // 序列号:12个bit,最大2^12=4096,0到4096,即(0000 0000 0000-1111 1111 1111)
    // 代表一毫秒内生成多少个序号,可以生成4096个,因为第1个0不需要生成。所以真正可以生成4095个
    private long sequence = 0L;
    // 上一次时间戳
    private long lastStamp = -1L;
    
    public SnowFlake(long dataCenterId, long machineId) {
        // 检查机房id是否超过31 不能小于0
        if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
            throw new IllegalArgumentException("机房id的id不能超过最大值,也不能为0");
        }
        // 检查机器id是否超过31 不能小于0
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("机器id的id不能超过最大值,也不能为0");
        }
        // 设置当前机房id
        this.dataCenterId = dataCenterId;
        // 设置当前机器id
        this.machineId = machineId;
    }
    
    /**
     * 让当前这台机器上的snowflake算法程序生成一个全局唯一的id
     * @return
     */
    public synchronized long nextId() {
        // 获得当前时间的毫秒数
        long currStamp = getNewStamp();
        if (currStamp < lastStamp) {
            throw new RuntimeException("服务器的时钟向后移动,拒绝生成id");
        }

        // 假设在同一个毫秒内,又发送了一个请求生成一个id
        // 这个时候就得把seqence序号给递增1,最多就是4095
        if (currStamp == lastStamp) {
            // 相同毫秒内,序列号自增,且保证取值范围最大为MAX_SEQUENCE
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒的序列数已经达到最大,也就是产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
            if (sequence == 0L) { // 即:已经超出MAX_SEQUENCE 即:1 0000 0000 0000
                currStamp = getNextMill();
            }
        } else {
            // 不同毫秒内,序列号置为0
            sequence = 0L;
        }

        // 这儿记录一下最近一次生成id的时间戳,单位是毫秒
        lastStamp = currStamp;
        // 这儿就生成一个64bit id的二进制位运算操作

        System.out.println("时间戳:" + (currStamp - START_STAMP));
        System.out.println("时间戳左移位数:" + (TIMESTAMP_LEFT));
        System.out.println("机房id:" + (dataCenterId));
        System.out.println("机房id左移位数:" + (DATA_CENTER_LEFT));
        System.out.println("机器id:" + (machineId));
        System.out.println("机器id左移位数:" + (MACHINE_LEFT));
        System.out.println("序列号:" + (sequence));

        long id = (currStamp - START_STAMP) << TIMESTAMP_LEFT // 时间戳部分
                | dataCenterId << DATA_CENTER_LEFT // 数据中心部分
                | machineId << MACHINE_LEFT // 机器标识部分
                | sequence; // 序列号部分

        System.out.println("id的二进制是:" + Long.toBinaryString(id));
        return id;
    }
    
    /**
     * 获得下一个毫秒数,比lastStamp大的下一个毫秒数
     *
     * @return
     */
    private long getNextMill() {
        long mill = getNewStamp();
        while (mill <= lastStamp) {
            mill = getNewStamp();
        }
        return mill;
    }
    
    /**
     * 获得当前毫秒数
     *
     * @return
     */
    private long getNewStamp() {
        return System.currentTimeMillis();
    }
    
    public static void main(String[] args) {
        SnowFlake snowFlake = new SnowFlake(3L, 10L);
        System.out.println("snowFlake.nextId() = " + snowFlake.nextId());
    }
}

5、sequence = (sequence + 1) & MAX_SEQUENCE的逻辑

5.1、 相同毫秒内,序列号自增 原理

&公式
1&1=1
1&0=0
0&1=0
0&0=0
原理

MAX_SEQUENCE是2^12-1,即 1111 1111 1111。

根据 &公式,我们可以得到,x & 1111 1111 1111,那么结果就是x二进制从右到左的12位,内容都不变,因为1&1=1,0&1=0。

在这里插入图片描述

5.2、 sequence 保证取值范围最大为MAX_SEQUENCE的原理

x二进制从右到左的13位开始,和1111 1111 1111 的& 运算都是都是0,所以最大就是MAX_SEQUENCE

在这里插入图片描述

测试地址
https://www.23bei.com/tool-531.html

6、(currStamp - START_STAMP) << TIMESTAMP_LEFT | dataCenterId << DATA_CENTER_LEFT | machineId << MACHINE_LEFT | sequence 原理

6.1、 为什么要currStamp - START_STAMP) << TIMESTAMP_LEFT

TIMESTAMP_LEFT=22

在这里插入图片描述

6.2、 原理

t=currStamp - START_STAMP
t最大是:2^41-1

假设t=1000

1000的二进制如下(41bit)
0 0000 0000 0000 0000 0000 0000 0000 0011 1110 1000

假设dataCenterId=3

3的二进制如下(5bit)
0 0011

假设machineId=10

10的二进制如下(5bit)
0 1010

假设sequence=10

10的二进制如下(12bit)
0000 0000 0000

因为第1位是0,所以拼接起来id的二进制就是

0000000000000000000000000000000011111010000001101010000000000000
我们通过代码调试结果
时间戳:1000
时间戳左移位数:22
机房id:3
机房id左移位数:17
机器id:10
机器id左移位数:12
序列号:0
id的二进制是:11111010000001101010000000000000
snowFlake.nextId() = 4194738176
     

在这里插入图片描述

7、优缺点

7.1、优点

  1. 按照时间自增排序,在多个分布式系统内不会产生id碰撞(数据中心+机器id区分)
    1. 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
  2. 容量大:理论上QPS约为409.6w/s(1000*2^12)个自增ID。
  3. 高可用:生成时不依赖于数据库,完全在内存中生成。
  4. 灵活性高:可以根据自身业务情况调整分配bit位

7.2、缺点:

强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
针对此,美团做出了改进:

https://github.com/Meituan-Dianping/Leaf

8、分布式部署

8.1、场景1

应用A,容器化部署,有10个POD,现在有个定时任务,每秒往T1表插入1000条数据。那么10个POD一起跑的时候,就有可能产生主键冲突

解决方案:

通过第3部分:10 bit来解决,不同的pod,设置不同的机器 id
![](https://i-blog.csdnimg.cn/direct/bd91e54be5cc4e7695f19df15b0ce5c1.png
机器 id可以通过 (podIP转数字)%32取模来获取,其中32是2^5,表示机器id的范围。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值