0
点赞
收藏
分享

微信扫一扫

#yyds干货盘点# 雪花算法 分布式ID生成器

SnowFlake(雪花) 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。
在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的。

这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号
(这个我也不太懂知道就行)
举个例子,比如下面那个 64 bit 的 long 型数字:
image.png
第一个部分,是 1 个 bit:0,这个是无意义的。
第二个部分是 41 个 bit:表示的是时间戳。
第三个部分是 5 个 bit:表示的是机房 id,10001。
第四个部分是 5 个 bit:表示的是机器 id,1 1001。
第五个部分是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号。
解释:
1 bit:是不用的
因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
41 bit:表示的是时间戳,单位是毫秒。
41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成年就是表示 69 年的时间。
10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器
但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2 ^ 5 个机房(32 个机房),每个机房里可以代表 2 ^ 5 个机器(32 台机器),也可以根据自己公司的实际情况确定。
12 bit:这个是用来记录同一个毫秒内产生的不同 id。
12 bit 可以代表的最大正整数是 2 ^ 12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id。

简单来说,你的某个服务假设要生成一个全局唯一 id,那么就可以发送一个请求给部署了 SnowFlake 算法的系统,由这个 SnowFlake 算法系统来生成唯一 id。代码逻辑非常简单,同一毫秒内,订单ID的序列号自增。同步锁只作用于本机,机器之间互不影响,每毫秒可以4百万的订单ID,非常强悍。

SnowFlake算法的优点:
1) 高性能高可用:生成时不依赖于数据库,完全在内存中生成。
2) 容量大:每秒中能生成数百万的自增ID。
3) ID自增:存入数据库中,索引效率高。
SnowFlake算法的缺点:
依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。
上代码:

package me.hekr.iotos.softgateway.subsystem.util;

import cn.hutool.core.lang.Snowflake;
import lombok.extern.log4j.Log4j2;

/**
 * 雪花算法
 **/
@Log4j2
public class SnowFlake {

    /**
     * 起始时间戳,从2021-12-01开始生成
     * 初始化时间起点 ,后期修改会导致id重复,慎用
     */
    private final static long START_STAMP = 1638288000000L;

    /**
     * 序列号占用的位数 12
     */
    private final static long SEQUENCE_BIT = 12;

    /**
     * 机器标识占用的位数
     */
    private final static long MACHINE_BIT = 10;

    /**
     * 机器数量最大值
     */
    private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);

    /**
     * 序列号最大值
     */
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    /**
     * 机器标识
     */
    private long machineId;
    /**
     * 序列号
     */
    private long sequence = 0L;
    /**
     * 上一次时间戳
     */
    private long lastStamp = -1L;

    /**
     * 构造方法
     * @param machineId 机器ID
     */
    private SnowFlake(long machineId) {
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new RuntimeException("机器超过最大数量");
        }
        this.machineId = machineId;
    }

    /**
     * 单例
     */
    private static SnowFlake SNOW_FLAKE = null;

    /**
     * 获取雪花算法对象
     */
    public static SnowFlake getSnowFlake(long machineId){
        if(SNOW_FLAKE == null){
            SNOW_FLAKE = new SnowFlake(machineId);
        }
        return SNOW_FLAKE;
    }

    /**
     * 产生下一个ID
     */
    public synchronized long nextId() {
        long currStamp = getNewStamp();

        if (currStamp < lastStamp) {
            throw new RuntimeException("时钟后移,拒绝生成ID!");
        }

        if (currStamp == lastStamp) {
            // 相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                log.info("同一毫秒的序列数已经达到最大");
                currStamp = getNextMill();
            }
        } else {
            // 不同毫秒内,序列号置为0
            sequence = 0L;
        }

        lastStamp = currStamp;

        return (currStamp - START_STAMP) << TIMESTAMP_LEFT // 时间戳部分
                | machineId << MACHINE_LEFT             // 机器标识部分
                | sequence;                             // 序列号部分
    }

    /**
     *  获取下一个时间
     */
    private long getNextMill() {
        long mill = getNewStamp();
        while (mill <= lastStamp) {
            mill = getNewStamp();
        }
        return mill;
    }

    /**
     * 获取当前时间
     */
    private long getNewStamp() {
        return System.currentTimeMillis();
    }

    /**
     * 开始测试
     */
    public static void main(String[] args) {
        long l = System.currentTimeMillis();
        // 订单ID生成测试,机器ID指定第1台
        SnowFlake snowFlake = getSnowFlake(1);
        for (int i = 0; i < 100;i++){
            System.out.println(snowFlake.nextId());
        }
        System.out.println("用时:"+ (System.currentTimeMillis()-l) + "毫秒");
    }

}

代码看看, 会用就行了
不想自己写的可以用hutool工具包 里边也实现了雪花算法 开箱即用
也可以参考百度开源的UidGenerator也是用雪花算法实现的 https://github.com/baidu/uid-generator
请添加链接描述

举报

相关推荐

0 条评论