分布式—雪花算法生成ID

这篇具有很好参考价值的文章主要介绍了分布式—雪花算法生成ID。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、简介

1、雪花算法的组成:

由64个Bit(比特)位组成的long类型的数字

0 | 0000000000 0000000000 0000000000 000000000 | 00000 | 00000 | 000000000000

分布式—雪花算法生成ID,分布式

1个bit:符号位,始终为0。
41个bit:时间戳,精确到毫秒级别,可以使用69年。
10个bit:工作机器ID,可以部署在1024个节点上。
12个bit:序列号,每个节点每毫秒内最多可以生成4096个ID。

2、雪花算法的优缺点

优点:

全局唯一:雪花算法生成的ID是全局唯一的,可以用于分布式系统中的数据分片和数据合并。
时间有序:雪花算法生成的ID中包含了时间戳信息,可以根据ID的大小推算出生成的时间。
高性能:雪花算法生成ID的速度很快,可以满足高并发的场景需求。
可扩展性:雪花算法的数据结构相对简单,易于扩展和修改。

缺点:

依赖于系统时钟:雪花算法生成ID的过程中依赖于系统时钟,如果系统时钟发生回拨,可能会导致生成的ID出现重复。
长度固定:雪花算法生成的ID长度固定为64位,可能会导致存储和传输成本较高。
不支持分布式计算:雪花算法生成ID的过程是单线程的,不能支持分布式计算。

二、项目中使用 

1、引入依赖 

https://www.hutool.cn/​​​​​​​文章来源地址https://www.toymoban.com/news/detail-677001.html

        <!-- 雪花算法生成UUID的相关依赖 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.8.20</version>
        </dependency>

2、使用 

    	//生成带-的UUID字符串
        System.out.println(IdUtil.randomUUID());

        //生成不带-的UUID字符串
        System.out.println(IdUtil.simpleUUID());

        //使用雪花算法生成id
        System.out.println(IdUtil.getSnowflake(0,0).nextId());

        //使用雪花算法生成id,返回字符串类型
        System.out.println(IdUtil.getSnowflake(1,1).nextIdStr());
        System.out.println(IdUtil.getSnowflake(1,1).nextIdStr());
        System.out.println(IdUtil.getSnowflake(2,1).nextIdStr());
        System.out.println(IdUtil.getSnowflake(2,1).nextIdStr());

三、手写雪花算法生成ID


import java.util.Date;

public class SnowFlakeUtil {

    private static SnowFlakeUtil snowFlakeUtil;

    static {
        snowFlakeUtil = new SnowFlakeUtil();
    }

    // 初始时间戳(纪年),可用雪花算法服务上线时间戳的值
    // 1650789964886:2022-04-24 16:45:59
    private static final long INIT_EPOCH = 1650789964886L;

    // 时间位取&
    private static final long TIME_BIT = 0b1111111111111111111111111111111111111111110000000000000000000000L;

    // 记录最后使用的毫秒时间戳,主要用于判断是否同一毫秒,以及用于服务器时钟回拨判断
    private long lastTimeMillis = -1L;

    // dataCenterId占用的位数
    private static final long DATA_CENTER_ID_BITS = 5L;

    // dataCenterId占用5个比特位,最大值31
    // 0000000000000000000000000000000000000000000000000000000000011111
    private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);

    // dataCenterId
    private long dataCenterId;

    // workId占用的位数
    private static final long WORKER_ID_BITS = 5L;

    // workId占用5个比特位,最大值31
    // 0000000000000000000000000000000000000000000000000000000000011111
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);

    // workId
    private long workerId;

    // 最后12位,代表每毫秒内可产生最大序列号,即 2^12 - 1 = 4095
    private static final long SEQUENCE_BITS = 12L;

    // 掩码(最低12位为1,高位都为0),主要用于与自增后的序列号进行位与,如果值为0,则代表自增后的序列号超过了4095
    // 0000000000000000000000000000000000000000000000000000111111111111
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);

    // 同一毫秒内的最新序号,最大值可为 2^12 - 1 = 4095
    private long sequence;

    // workId位需要左移的位数 12
    private static final long WORK_ID_SHIFT = SEQUENCE_BITS;

    // dataCenterId位需要左移的位数 12+5
    private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;

    // 时间戳需要左移的位数 12+5+5
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;

    /**
     * 无参构造
     */
    public SnowFlakeUtil() {
        this(1, 1);
    }

    /**
     * 有参构造
     */
    public SnowFlakeUtil(long dataCenterId, long workerId) {
        // 检查dataCenterId的合法值
        if (dataCenterId < 0 || dataCenterId > MAX_DATA_CENTER_ID) {
            throw new IllegalArgumentException(
                String.format("dataCenterId 值必须大于 0 并且小于 %d", MAX_DATA_CENTER_ID));
        }
        // 检查workId的合法值
        if (workerId < 0 || workerId > MAX_WORKER_ID) {
            throw new IllegalArgumentException(String.format("workId 值必须大于 0 并且小于 %d", MAX_WORKER_ID));
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }

    /**
     * 获取唯一ID
     */
    public static Long getSnowFlakeId() {
        return snowFlakeUtil.nextId();
    }

    /**
     * 通过雪花算法生成下一个id,用synchronized同步
     */
    public synchronized long nextId() {
        long currentTimeMillis = System.currentTimeMillis();
        System.out.println(currentTimeMillis);
        // 当前时间小于上一次生成id使用的时间,可能出现服务器时钟回拨问题
        if (currentTimeMillis < lastTimeMillis) {
            throw new RuntimeException(
                String.format("可能出现服务器时钟回拨问题,请检查服务器时间。当前服务器时间戳:%d,上一次使用时间戳:%d", currentTimeMillis,
                    lastTimeMillis));
        }
        if (currentTimeMillis == lastTimeMillis) {
            // 还是在同一毫秒内,则将序列号递增1,序列号最大值为4095
            // 序列号的最大值是4095,使用掩码(最低12位为1,高位都为0)进行位与运行后如果值为0,则自增后的序列号超过了4095
            // 那么就使用新的时间戳
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                currentTimeMillis = getNextMillis(lastTimeMillis);
            }
        } else { // 不在同一毫秒内,则序列号重新从0开始,序列号最大值为4095
            sequence = 0;
        }
        // 记录最后一次使用的毫秒时间戳
        lastTimeMillis = currentTimeMillis;
        // 核心算法,将不同部分的数值移动到指定的位置,然后进行或运行
        // <<:左移运算符, 1 << 2 即将二进制的 1 扩大 2^2 倍
        // |:位或运算符, 是把某两个数中, 只要其中一个的某一位为1, 则结果的该位就为1
        // 优先级:<< > |
        return
            // 时间戳部分
            ((currentTimeMillis - INIT_EPOCH) << TIMESTAMP_SHIFT)
                // 数据中心部分
                | (dataCenterId << DATA_CENTER_ID_SHIFT)
                // 机器表示部分
                | (workerId << WORK_ID_SHIFT)
                // 序列号部分
                | sequence;
    }

    /**
     * 获取指定时间戳的接下来的时间戳,也可以说是下一毫秒
     * @param lastTimeMillis 指定毫秒时间戳
     * @return 时间戳
     */
    private long getNextMillis(long lastTimeMillis) {
        long currentTimeMillis = System.currentTimeMillis();
        while (currentTimeMillis <= lastTimeMillis) {
            currentTimeMillis = System.currentTimeMillis();
        }
        return currentTimeMillis;
    }

    /**
     * 获取随机字符串,length=13
     */
    public static String getRandomStr() {
        return Long.toString(getSnowFlakeId(), Character.MAX_RADIX);
    }

    /**
     * 从ID中获取时间
     * @param id 由此类生成的ID
     */
    public static Date getTimeBySnowFlakeId(long id) {
        return new Date(((TIME_BIT & id) >> 22) + INIT_EPOCH);
    }

    public static void main(String[] args) {
        SnowFlakeUtil snowFlakeUtil = new SnowFlakeUtil();
        long id = snowFlakeUtil.nextId();
        System.out.println("id: "+id);
        Date date = SnowFlakeUtil.getTimeBySnowFlakeId(id);
        System.out.println("date: "+date);
        long time = date.getTime();
        System.out.println("time: "+time);
        System.out.println("getRandomStr: "+getRandomStr());
    }
}

到了这里,关于分布式—雪花算法生成ID的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 分布式唯一ID生成算法——雪花算法(SnowFlake)

    SnowFlake算法 据国家大气研究中心的查尔斯·奈特称,一般的雪花大约由10^19个水分子组成。在雪花形成过程中,会形成不同的结构分支,所以说大自然中不存在两片完全一样的雪花,每一片雪花都拥有自己漂亮独特的形状。 雪花算法表示生成的id如雪花般独一无二。 snowflake是

    2023年04月20日
    浏览(30)
  • 【Java笔记】分布式id生成-雪花算法

    随着业务的增长,有些表可能要占用很大的物理存储空间,为了解决该问题,后期使用数据库分片技术。将一个数据库进行拆分,通过数据库中间件连接。如果数据库中该表选用ID自增策略,则可能产生重复的ID,此时应该使用分布式ID生成策略来生成ID。 snowflake是Twitter开源的

    2024年02月11日
    浏览(30)
  • 分布式Id生成之雪花算法(SnowFlake)

    目录 前言 回顾二进制 二进制概念 运算法则 位(Bit) 字节(Byte) 字符 字符集 二进制原码、反码、补码 有符号数和无符号数 疑问:为什么不是-127 ~ 127 ? 为什么需要分布式全局唯一ID以及分布式ID得业务需求? ID生成规则部分硬性要求 ID生成系统的可用性要求 通用解决方

    2024年02月11日
    浏览(20)
  • 【智能排班系统】雪花算法生成分布式ID

    在复杂而庞大的分布式系统中,确保数据实体的唯一标识性是一项至关重要的任务,生成全局唯一且有序的ID生成机制成为必不可少的环节。雪花算法(Snowflake Algorithm)正是为此目的而生,以其简洁的设计、高效的表现与良好的扩展性赢得了业界的广泛认可。 雪花算法最早由

    2024年04月10日
    浏览(64)
  • 雪花算法,在分布式环境下实现高效的ID生成

    其实雪花算法比较简单,可能称不上什么算法,就是一种构造UID的方法。 点1:UID是一个long类型的41位时间戳,10位存储机器码,12位存储序列号。 点2:时间戳的单位是毫秒,可以同时链接1024台机器,每台机器每毫秒可以使用4096个序列号,我们会给生成id上一个同步锁,阻塞

    2024年02月15日
    浏览(36)
  • 对于现有的分布式id发号器的思考 id生成器 雪花算法 uuid

    目录 雪花id tinyid uuid 分布式id特点 业务编号 数据中心编号 当前时间 ip地址 当前序号 对于时钟回拨问题 发号器机器当期时间小于redis的时间 解决步骤 发号器机器当期时间等于redis时间 发号器机器当期时间大于redis最大的时间(相关的key不存在) 分布式id的单次获取和批次获

    2024年02月13日
    浏览(35)
  • 分布式唯一ID 雪花算法

           📝个人主页:五敷有你        🔥系列专栏:算法分析与设计 ⛺️稳中求进,晒太阳 雪花算法是 64 位 的二进制,一共包含了四部分: 1位是符号位,也就是最高位,始终是0,没有任何意义,因为要是唯一计算机二进制补码中就是负数,0才是正数。 41位是时间戳

    2024年04月10日
    浏览(38)
  • 分布式id解决方法--雪花算法

    uuid,jdk自带,但是数据库性能差,32位呀。 mysql数据库主键越短越好,B+tree产生节点分裂,大大降低数据库性能,所以uuid不建议。 redis的自增,但是要配置维护redis集群,就为了一个id,还要引入一套redis。费事,成本高。 如果有序自增别人就知道你的业务量多少了。 分布式

    2024年02月04日
    浏览(29)
  • C# 分布式自增ID算法snowflake(雪花算法)

    分布式系统中,有一些需要使用全局唯一 ID 的场景,这种时候为了防止 ID 冲突可以使用36位的 UUID ,但是 UUID 有一些缺点,首先他相对比较长,另外 UUID 一般是无序的。有些时候我们希望能使用一种简单一些的 ID ,并且希望 ID 能够按照时间有序生成。而 Twitter 的 snowflake 解

    2024年04月10日
    浏览(32)
  • JAVA实用工具: 改良版雪花算法-分布式唯一ID神器

    Seata内置了一个分布式UUID生成器,用于辅助生成全局事务ID和分支事务ID。具体如下特点: 高性能 全局唯一 趋势递增 这个分布式UUID生成器是基于雪花算法进行改良的,本文针对改良的方法、目的等进行总结 改良版雪花算法的实现原理参考如下: Seata基于改良版雪花算法的分

    2024年02月14日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包