雪花算法工具

什么是雪花算法

SnowFlake 中文意思为雪花,故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。

雪花算法

雪花算法的原理就是生成一个的 64 位比特位(即64位二进制)的 long 类型的唯一 id。

  • 最高 1 位固定值 0,因为生成的 id 是正整数,如果是 1 就是负数了。(因为long是64位整形)
  • 接下来 41 位存储毫秒级时间戳,2^41/(1000606024365)=69,大概可以使用 69 年。
  • 再接下 10 位存储机器码,包括 5 位 datacenterId 和 5 位 workerId。最多可以部署 2^10=1024 台机器。
  • 最后 12 位存储序列号。同一毫秒时间戳时,通过这个递增的序列号来区分。即对于同一台机器而言,同一毫秒时间戳下,可以生成 2^12=4096 个不重复 id。

算法优缺点

优点:

  • 高并发分布式环境下生成不重复 id,每秒可生成百万个不重复 id。
  • 基于时间戳,以及同一时间戳下序列号自增,基本保证 id 有序递增。
  • 不依赖第三方库或者中间件。
  • 算法简单,在内存中进行,效率高。

缺点:

  • 依赖服务器时间,服务器时钟回拨时可能会生成重复 id。算法中可通过记录最后一个生成 id 时的时间戳来解决,每次生成 id 之前比较当前服务器时钟是否被回拨,避免生成重复 id。
  • 其实雪花算法每一部分占用的比特位数量并不是固定死的。例如你的业务可能达不到 69 年之久,那么可用减少时间戳占用的位数,雪花算法服务需要部署的节点超过1024 台,那么可将减少的位数补充给机器码用。
  • 注意,雪花算法中 41 位比特位不是直接用来存储当前服务器毫秒时间戳的,而是需要当前服务器时间戳减去某一个初始时间戳值,一般可以使用服务上线时间作为初始时间戳值。

代码

1、工具类使用方式

package cn.jy.common;

/**
 * @author jy
 * @date 2023/03/13 17:53
 **/
public class Snowflake {
    /** 初始时间,目前为2023年1月1日0时0分0秒 */
    private final long twepoch = 1672502400000l;
    /** 服务id位数 */
    private int workerIdBits = 5;
    /** 机器id位数 */
    private int datacenterIdBits = 5;
    /** 最大服务位数 */
    private int maxWorkerId = -1 ^ (-1 << workerIdBits);
    /** 最大机器id位数 */
    private int maxDatacenterId = -1 ^ (-1 << datacenterIdBits);
    /** 序号位数 */
    private int sequenceBits = 12;
    /** 服务id需要左移位数 */
    private int workerIdShift = sequenceBits;
    /** 机器id需要左移位数 */
    private int datacenterIdShift = sequenceBits + workerIdBits;
    /** 时间戳需要左移的位数 */
    private int timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private int sequenceMask = -1 ^ (-1 << sequenceBits);
    /** 服务id */
    private int workerId;
    /** 机器id */
    private int datacenterId;
    private long lastTimestamp = -1;
    /** 每秒生成id序号 */
    private int sequence = 0;

    public Snowflake(){

    }
    /**
     * 获取自定义雪花算法类
     * @param workerIdBits 服务id比特位数
     * @param datacenterIdBits 机器id比特位数
     * @param sequenceBits 序号比特位数
     * @param workerId 服务id
     * @param datacenterId 机器id
     */
    public Snowflake(int workerIdBits, int datacenterIdBits, int sequenceBits, int workerId, int datacenterId) {
        if(workerIdBits + datacenterIdBits + sequenceBits > 22){
            throw new IllegalArgumentException(String.format("workerIdBits, datacenterIdBits, sequenceBits 之和不能超过22"));
        }
        int maxWorkerId = -1 ^ (-1 << workerIdBits);
        int maxDatacenterId = -1 ^ (-1 << datacenterIdBits);

        if (workerId < 0 || workerId > maxWorkerId) {
            throw new IllegalArgumentException(String.format("workerId 必须在 0 和 %d 之间", maxWorkerId));
        }
        if (datacenterId < 0 || datacenterId > maxDatacenterId) {
            throw new IllegalArgumentException(String.format("datacenterId 必须在 0 和 %d 之间", maxDatacenterId));
        }

        this.workerIdBits = workerIdBits;
        this.datacenterIdBits = datacenterIdBits;
        this.sequenceBits = sequenceBits;
        this.maxWorkerId = maxWorkerId;
        this.maxDatacenterId = maxDatacenterId;
        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.workerIdShift = sequenceBits;
        this.datacenterIdShift = sequenceBits + workerIdBits;
        this.timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
        this.sequenceMask = -1 ^ (-1 << sequenceBits);
    }

    /**
     * 获取默认雪花算法类
     * @param workerId 服务id,最大值为31
     * @param datacenterId 机器id,最大值为31
     */
    public Snowflake(int workerId, int datacenterId) {
        if (workerId < 0 || workerId > maxWorkerId) {
            throw new IllegalArgumentException(String.format("workerId 必须在 0 和 %d 之间", maxWorkerId));
        }
        if (datacenterId < 0 || datacenterId > maxDatacenterId) {
            throw new IllegalArgumentException(String.format("datacenterId 必须在 0 和 %d 之间", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("时间回调了,无法在 %d 毫秒内生成id", lastTimestamp - timestamp));
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }
}

2、springboot中依赖注入方式

package com.jy.commom.util;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * @author jy
 * @date 2023/03/13 17:53
 **/

@Configuration
public class Snowflake{

    /** 初始时间,目前为2023年1月1日0时0分0秒 */
    @Value("${snowflake.twepoch:1672502400000}")
    private long twepoch;
    /** 服务id位数 */
    @Value("${snowflake.workerIdBits:5}")
    private int workerIdBits;
    /** 机器id位数 */
    @Value("${snowflake.datacenterIdBits:5}")
    private int datacenterIdBits;
    /** 最大服务位数 */
    private int maxWorkerId;
    /** 最大机器id位数 */
    private int maxDatacenterId;
    /** 序号位数 */
    @Value("${snowflake.sequenceBits:5}")
    private int sequenceBits;
    /** 服务id需要左移位数 */
    private int workerIdShift;
    /** 机器id需要左移位数 */
    private int datacenterIdShift;
    /** 时间戳需要左移的位数 */
    private int timestampLeftShift;
    private int sequenceMask;
    /** 服务id */
    @Value("${snowflake.workerId}")
    private int workerId;
    /** 机器id */
    @Value("${snowflake.datacenterId}")
    private int datacenterId;
    private long lastTimestamp = -1;
    /** 每秒生成id序号 */
    private int sequence = 0;

    /**
     * 使用 @PostConstruct 注解,在类对象初始化之后立即执行,初始化Snowflake
     * 不使用普通依赖注入的原因是,即使在无参构造中计算各属性的值,但是由于@Value注解还未取值,导致各字段数据计算结果都为0,初始化失败
     * @author jy
     */
    @PostConstruct
    private void init(){
        if(workerIdBits + datacenterIdBits + sequenceBits > 22){
            throw new IllegalArgumentException(String.format("workerIdBits, datacenterIdBits, sequenceBits 之和不能超过22"));
        }
        int maxWorkerId = -1 ^ (-1 << workerIdBits);
        int maxDatacenterId = -1 ^ (-1 << datacenterIdBits);

        if (workerId < 0 || workerId > maxWorkerId) {
            throw new IllegalArgumentException(String.format("workerId 必须在 0 和 %d 之间", maxWorkerId));
        }
        if (datacenterId < 0 || datacenterId > maxDatacenterId) {
            throw new IllegalArgumentException(String.format("datacenterId 必须在 0 和 %d 之间", maxDatacenterId));
        }
        this.workerIdShift = sequenceBits;
        this.datacenterIdShift = sequenceBits + workerIdBits;
        this.timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
        this.sequenceMask = -1 ^ (-1 << sequenceBits);
        this.maxWorkerId = -1 ^ (-1 << workerIdBits);
        this.maxDatacenterId = -1 ^ (-1 << datacenterIdBits);
    }

    public Snowflake(){}

    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("时间回调了,无法在 %d 毫秒内生成id", lastTimestamp - timestamp));
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }
}

使用第二种方法时,需要在配置文件中配置一下参数:

snowflake.workerIdBits=6
snowflake.datacenterIdBits=7
snowflake.sequenceBits=9
snowflake.twepoch=1672502400000
snowflake.workerId=12
snowflake.datacenterId=15

snowflake:
  workerIdBits: 6
  datacenterIdBits: 7
  sequenceBits: 9
  twepoch: 1672502400000
  # 以下两项为必填项
  workerId: 12
  datacenterId: 15
  • workerIdBits:该参数配置为服务id的比特位数,默认配置为5
  • datacenterIdBits:该参数配置为机器id的比特位数,默认配置为5
  • sequenceBits:该参数配置为机器id的比特位数,默认配置为12
  • workerId:该参数配置为服务id,根据实际情况填写,最大值小于1 << workerIdBits,如果workerIdBits为默认配置,此处服务id最大为31
  • sdatacenterId:该参数配置为服务id,根据实际情况填写,最大值小于 1 << sequenceBits,如果datacenterIdBits为默认配置,此处服务id最大为31
  • twepoch:该参数为时间戳的初始时间,默认配置为1672502400000,即以2023/1/1 0:00:00开始计时
  • 注意:workerIdBits + datacenterIdBits + sequenceBits 之和不能超过22
  • 注意:由于该工具类使用@Configuration和@PostConstruct注解,所以需要项目为springboot项目才可以用第二种方式依赖

雪花算法工具_雪花算法在线工具-CSDN博客

posted @ 2024-06-27 16:01  CharyGao  阅读(3)  评论(0编辑  收藏  举报