1.雪花算法的介绍

分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成,那么这个时候我们可以考虑一下雪花算法。

特别说明:1个bit是一个值0或1叫做一个二进制位,而1个byte占用8个bit,这是byte与bit的区别。

雪花算法所产生的唯一ID是由64个二进制位转换为十进制位以后所对应的值

第一个部分是1个bit,固定值为0,这个是无意义的

第二个部分是41个bit:表示的是时间戳

第三个部分是5个bit:表示的是机房id,例如“10001”

第四个部分是5个bit:表示的是机器id,例如“11001”

第五个部分是12个bit:表示的是序号,就是某个机房某台机器上这一毫秒内同时生成的id的序号,例如“0000000000”

第一个部分的1bit是不用的,为啥呢?

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

第二个部分的41bit表示的时间戳,单位是毫秒

41 bit 可以表示的数字多达2^41-1,也就是可以标识2^41-1个毫秒值,换算成年就是表示69年的时间。年T=(1L<<41)/(1.00OL* 60* 60* 24* 365)= 69,其中1L<<41表示的是位运算,速度比2^41-1这种速度更快一点。

第三、四个部分的10bit记录工作机器id,代表的是这个服务最多可以部署在2^10台机器上,也就是1024台机器

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

第五个部分的12bit是用来记录同一毫秒内产生的不同id

12bit可以代表的最大正整数是2^12-1=4095,也就是说可以用这个12bit代表的数字来区分同一个毫秒内的4096个不同的 id。

2.雪花算法的源码

雪花算法(Snowflake)是由Twitter开源出来的,在Hutool依赖包中已经对该算法进行了实现

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

我们在项目中使用该算法时,只需要pom.xml中引入上述依赖,然后可以考虑构造一个如下所示的唯一ID生成器封装类,当然也可以直接使用IdUtil.getSnowflake()方法。

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

/**
 * 雪花算法唯一ID生成器
 */
@Component
@Slf4j
public class IdGeneratorSnowflake {

    // workerId表示机房编号,其取值范围为[0-31]
    private final long workerId=1;
    // datacenterId表示机房内某一个服务器的编号,其取值范围为[0-31]
    private final long datacenterId =31;
    private Snowflake snowflake;

    @PostConstruct
    public void init(){
        snowflake=IdUtil.getSnowflake(workerId,datacenterId);
    }

    public synchronized long snowflakeId(){
        return snowflake.nextId();
    }
}
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class WorkApplicationTests {

    @Autowired
    private IdGeneratorSnowflake idGeneratorSnowflake;

    @Test
    void produceTest() {
        for (int i = 0; i < 10; i++) {
            System.out.println(idGeneratorSnowflake.snowflakeId());
        }
    }
}

 执行上述测试类,其输出结果为:

1525670872306552832
1525670872306552833
1525670872306552834
1525670872306552835
1525670872306552836

雪花算法获取唯一ID主要是依靠snowflake.nextId()方法,其核心源码部分如下所示:

public synchronized long nextId() {
    // 1.获取当前时间的时间戳
	long timestamp = genTime();
    // 2.判断当前时间戳是不是小于上一次的时间戳
	if (timestamp < this.lastTimestamp) {
        // 3.判断上一次的时间戳与当前时间戳是不是在可容忍阈值内
		if(this.lastTimestamp - timestamp < timeOffset){
			// 4.容忍指定的回拨,避免NTP校时造成的异常
			timestamp = lastTimestamp;
		} else{
			// 5.如果服务器时间有问题(时钟后退) 报错。
			throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));
		}
	}

	if (timestamp == this.lastTimestamp) {
        // 1.如果当前时间戳等于上一次的时间戳,则说明是在同一毫秒内去生成唯一ID,则序列号进行递增
        // this.sequence的最大值为4095,this.sequence + 1以后值为4096,然后与SEQUENCE_MASK进行与运算所产生的值为0
		final long sequence = (this.sequence + 1) & SEQUENCE_MASK;
        // 2.判断同一毫秒内的序列数已经达到最大值
		if (sequence == 0) {
            // 3.循环等待下一个毫秒时间,获取下一个毫秒时间戳并赋值给当前时间戳
			timestamp = tilNextMillis(lastTimestamp);
		}
        // 4.更新当前对象的序列号
		this.sequence = sequence;
	} else {
        // 5.不同毫秒内,则序列号为0
		this.sequence = 0L;
	}

    // 6.把当前时间戳更新到当前对象的lastTimestamp
    // 即当前时间戳存档记录,用于下次产生id时对比是否为相同时间戳
	this.lastTimestamp = timestamp;

	return ((timestamp - twepoch) << TIMESTAMP_LEFT_SHIFT) // 时间戳部分
			| (dataCenterId << DATA_CENTER_ID_SHIFT)       // 数据中心部分
			| (workerId << WORKER_ID_SHIFT)                // 机器标识部分
			| sequence;                                    // 序列号部分
}

雪花算法生成唯一ID核心源码的总结: 

1.当前时间戳的处理

2.当前所生成的序列号的处理

3.数据值部分进行左移操作,返回唯一ID

timestamp - twepoch:表示的是当前时间戳减去开始时间戳,这个开始的时间戳一定要是一个固定值,不能是一个可变的值。TIMESTAMP_LEFT_SHIFT表示时间差值向左移的位数,固定为22。

可以看到最后计算出来的3个数据值部分(除了序列号)都进行左移操作,这主要是为了实现雪花算法所生成的唯一ID是由64位二进制所组成的这么一种结构。

3.时钟回拨问题

其实这个问题,对于Hutool这个工具类,它其实已经做了一个优化,如果回拨差是在可容忍范围以内,则将当前时间时间戳更新后上一次记录的时间戳,这个主要是为了避免NTP校时所造成的影响;否则的话才会抛出一个异常。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐