雪花算法的实现原理

在这里插入图片描述

一、简介

1.什么是雪花算法?

雪花算法 英文名为 SnowFlake,是一个64个big位组成的 long 类型的数字,由 Twitter 开源的分布式 ID 算法生成。主要应用于分布式环境下生成全局唯一ID。

雪花算法的意思是生成的ID如雪花一般独一无二。

2.为什么使用雪花算法?

如果我们单单只想解决全局唯一ID这个问题有很多的解决方法:比如 UUID系统时间戳Redis原子递增数据库全局表自增ID等。但在实际应用中,我们需要的ID除了唯一性之外,还需要满足以下特征:

1)单调递增:保证下一个ID号一定大于上一个。

2)保证安全:ID号需要无规则性,不能让别人根据ID号猜出我们的信息和业务数据量,增加恶意用户扒取数据的难度。

3)含时间戳:ID需要记录系统时间戳。

4)高可用:获取分布式ID的请求,服务器至少要保证 99.999% 的情况下给创建一个全局唯一的分布式ID。

5)低延迟:获取分布式ID的请求,要快,急速。

6)高QPS:服务器要顶得住并且成功创建10w个分布式ID。

雪花算法就是一个比较符合这类特征的全局唯一算法。很多大厂的全局ID组件中,都有用到,比如百度的 UidGenerator、美团的 Leaf 算法等。

3.雪花算法的缺点

由于雪花算法严重依赖时间,所以当发生服务器时钟回拨时可能会产生重复ID。

二、实现原理

雪花算法主要由4个部分组成:

在这里插入图片描述

1bit-符号位(基本不用)

1位标识:由于 long 基本类型在 Java 中是带符号的,最高位是符号位,正数是0,负数是1。由于 id 一般是正数,所以第一位都是0。

41bit-时间戳

接下来41位存储毫秒级时间戳,41位可以表示 2^41-1 毫秒。

转化成年则是:(2^41-1)/(1000*60*60*24*356)=69 年。也就是说这个时间戳大概可以使用 69年 不重复。

注意: 这里的41位时间戳不是存储当前时间的时间戳,而是存储时间戳的差值 “当前时间戳 - 开始时间戳” 得到的值。这里的开始时间戳,一般是我们的 id 生成器开始使用的时间,由程序指定,设置好后就不要去变更了,切记!!!由此,雪花算法有了服务器时间回拨可能会生成重复id的缺点。

10bit-机器位

10位的数据机器位,包括 5 位 datacenterId 和 5 位 workerId,最多可以部署 2^10=1024 台机器。

这里的 5 位可以表示的最大整数时 2^5-1=31,即可以用 0、1、2、3、…31 这 32 个数字,来表示不同的 datacenterId 或 workerId

12bit-序列号

用来记录同毫秒内产生的不同ID,12位的计数顺序支持每个节点每毫秒(同一机器,同一时间戳)产生 4096 个ID序号。

理论上雪花算法方案的 QPS 约为 409.6w/s,这种分配方式可以保证在任何一个 IDC 的任何一台机器在任意毫秒内生成的ID都是不同的。

三、代码实现

1.hutool工具

关于具体实现网上有很多,这里就不赘述了,需要的话可以参考一下 hutool 中雪花算法的生成,依赖如下:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.16</version>
</dependency>

使用示例如下:

import cn.hutool.core.util.IdUtil;

public static void main(String[] args) {
    System.out.println(IdUtil.getSnowflakeNextId());
}

网上很多实现方法比较简单,针对机器数部分的处理不太够,在分布式场景下并发容易导致id重复。hutool 中的雪花算法是通过获取 MAC 地址来进行生成的,经过了大量的验证,还是比较严谨的,所以不推荐自己实现雪花算法。

2.逆向解析

了解了雪花算法的实现原理(1位符号位 + 41位时间戳 + 5位数据中心ID + 5位机器ID + 12位序列号)之后,我们就可以根据已有的雪花算法ID来逆向解析出相应的信息。

解析代码实现如下:

AnalyzeSnowflake.java

import cn.hutool.core.util.IdUtil;

import java.text.SimpleDateFormat;
import java.util.Date;

public class AnalyzeSnowflake {

    public static void main(String[] args) {
        // 生成雪花算法ID
        // timestamp - this.twepoch << 22 | this.dataCenterId << 17 | this.workerId << 12 | this.sequence
        long snowflakeNextId = IdUtil.getSnowflakeNextId();
        System.out.println("snowflake:" + snowflakeNextId);

        // 补零
        StringBuilder binaryString = new StringBuilder(Long.toBinaryString(1686769506909614080L)); // 将long类型的数值转换为二进制字符串
        int addZeroCount = 64 - binaryString.length();
        for (int i = 0; i < addZeroCount; i++) {
            binaryString.insert(0, "0");
        }
        System.out.println("length:" + binaryString.length());

        // 1位符号位 + 41位时间戳 + 5位数据中心ID + 5位机器ID + 12位序列号
        System.out.println("part1-符号位:" + binaryString.charAt(0));
        long millis = Long.parseLong(binaryString.substring(1, 42), 2);
        long second = millis / 1000;
        long minute = second / 60;
        long hour = minute / 60;
        long day = hour / 24;
        long year = day / 365;
        day = day % 365;
        hour = hour % 24;
        minute = minute % 60;
        second = second % 60;
        System.out.println("part2-从开始到现在运行了:" + String.format("%d年%d天%d小时%d分钟%d秒", year, day, hour, minute, second));
        long startMillis = System.currentTimeMillis() - millis;
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("part2-开始时间大约在:" + simpleDateFormat.format(new Date(startMillis)));
        System.out.println("part3-1-数据中心id:" + Long.parseLong(binaryString.substring(43, 47), 2));
        System.out.println("part3-2-机器id:" + Long.parseLong(binaryString.substring(48, 52), 2));
        System.out.println("part4-序列号:" + Long.parseLong(binaryString.substring(53, 64), 2));
    }
}

执行结果:

在这里插入图片描述

3.hutool源码解析

在 hutool 的 Snowflake.java 源码中我们可以看到起始时间默认为:2010-11-04 01:42:54,与我们推算的时间基本一致。

在这里插入图片描述

往下拉,在 Snowflake.java 可以看到雪花算法的核心实现:

timestamp - this.twepoch << 22 | this.dataCenterId << 17 | this.workerId << 12 | this.sequence

在这里插入图片描述

整理完毕,完结撒花~ 🌻





参考地址:

1.雪花算法详解(原理优缺点及代码实现),https://www.cnblogs.com/mikechenshare/p/16787023.html

2.雪花算法原理及实现,https://blog.csdn.net/qq_41573860/article/details/124119358

3.通俗聊透雪花算法的实现原理,https://baijiahao.baidu.com/s?id=1750456904521809034&wfr=spider&for=pc

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Java雪花算法(Snowflake Algorithm)是一种分布式系统中用来生成唯一的ID的算法。它由Twitter的工程师李清松在2010年提出。 雪花算法原理是:使用一个64位的long类型的整数来存储ID,其中: - 1位标识位(最高位):用于标识ID是正数还是负数。因为long类型的整数默认是正数,所以这一位可以忽略。 - 41位时间戳位(第2~42位):用于记录ID生成的时间戳。这41位可以表示$2^{41}-1$个单位时间,也就是说可以使用$2^{41}-1$年。 - 10位节点位(第43~52位):用于记录ID生成机器所在的节点。 - 12位序列号位(第53~64位):用于记录在同一时间戳内生成的不同ID。 下面是Java代码的实现示例: ``` public class SnowflakeIdWorker { // 开始时间戳(2020-01-01) private final long startTimestamp = 1577836800000L; // 机器ID所占的位数 private final long workerIdBits = 5L; // 数据中心ID所占的位数 private final long dataCenterIdBits = 5L; // 序列号所占的位数 private final long sequenceBits = 12L; // 机器ID的最大值 private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 数据中心ID的最大值 private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits); // 序列号的最大值 private final long max ### 回答2: 雪花算法是一种分布式唯一ID生成算法,常用于生成全局唯一的ID。下面是Java中实现雪花算法的简要步骤: 1. 定义一个Snowflake类,其中包含以下属性: - startTime:系统的起始时间戳,用于计算生成的ID中的时间戳部分。 - datacenterId:数据中心的标识,可以根据实际需求进行调整。 - workerId:工作节点的标识,可以根据实际需求进行调整。 - sequence:序列号,用于处理同一毫秒内的并发请求。 2. 在Snowflake类中定义一个generateId()方法,用于生成唯一ID。 - 获取当前时间戳,并减去起始时间戳,得到时间戳部分。 - 根据实际情况,将时间戳、数据中心ID和工作节点ID按位进行拼接,生成ID的高位部分。 - 如果当前时间戳相同,则递增序列号部分;否则,将序列号重置为0。 - 将ID的高位部分与序列号进行拼接,生成最终的唯一ID。 3. 在主程序中调用generateId()方法,实例化Snowflake类并生成一定数量的唯一ID。 需要注意的是,雪花算法的实现需要根据实际业务场景进行调整,比如数据中心ID和工作节点ID的位数、起始时间戳的设定等都需要根据实际需求进行适配。另外,为避免时钟回拨等问题,可以使用系统时间的补偿机制来处理。 ### 回答3: 雪花算法是一种生成全局唯一ID的算法,主要用于分布式系统中,保证每个生成的ID在整个系统中都是唯一的。下面是Java实现雪花算法的代码示例: ```java public class SnowflakeIdGenerator { private final long startTime = 1609459200000L; // 设置起始时间戳,例如:2021-01-01 00:00:00 private final long workerIdBits = 5L; // 机器ID所占的位数 private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 最大机器ID private final long dataCenterIdBits = 5L; // 数据中心ID所占的位数 private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits); // 最大数据中心ID private final long sequenceBits = 12L; // 序列号所占的位数 private final long workerIdShift = sequenceBits; // 机器ID向左移的位数 private final long dataCenterIdShift = sequenceBits + workerIdBits; // 数据中心ID向左移的位数 private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits; // 时间戳向左移的位数 private final long sequenceMask = -1L ^ (-1L << sequenceBits); // 序列号的最大值 private long lastTimestamp = -1L; private long sequence = 0L; private final long workerId; private final long dataCenterId; public SnowflakeIdGenerator(long workerId, long dataCenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException("Worker ID超过范围"); } if (dataCenterId > maxDataCenterId || dataCenterId < 0) { throw new IllegalArgumentException("DataCenter ID超过范围"); } this.workerId = workerId; this.dataCenterId = dataCenterId; } public synchronized long nextId() { long timestamp = System.currentTimeMillis(); if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨异常"); } if (timestamp == lastTimestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - startTime) << timestampShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence; } private long tilNextMillis(long lastTimestamp) { long timestamp = System.currentTimeMillis(); while (timestamp <= lastTimestamp) { timestamp = System.currentTimeMillis(); } return timestamp; } } ``` 以上是一个基于Snowflake算法实现的分布式ID生成器,通过构造函数传入机器ID和数据中心ID,然后调用`nextId()`方法就能够生成唯一的ID,ID的格式为一个64位的长整型数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿放下技术的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值