首页 > 编程语言 >全局唯一ID生成器(SnowFlakeId算法JAVA实现)

全局唯一ID生成器(SnowFlakeId算法JAVA实现)

时间:2022-09-20 17:44:28浏览次数:62  
标签:JAVA sequence timestamp 生成器 lastTimestamp long SnowFlakeId private ID

import org.apache.commons.lang3.RandomUtils;
import java.util.Random;

/**
 * @Description: 全局唯一Id生成器
 * @Author: yk
 * @Create: 2022-09-20 16:55
 */
public class SnowFlakeWorker {

    /** 开始时间截 (2015-01-01) */
    private final long twepoch = 1420041600000L;

    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;

    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;

    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;

    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;

    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 工作机器ID(0~31) */
    private long workerId;

    /** 数据中心ID(0~31) */
    private long datacenterId;

    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;

    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;


    //==============================Constructors=====================================
    /**
     * 构造函数
     * @param workerId 工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public SnowFlakeWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    // ==============================Methods==========================================
    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        //如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            sequence = generateRandom();
        }

        //上次生成ID的时间截
        lastTimestamp = timestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }
    //生成的ID,例如message-id/ order-id/ tiezi-id,在数据量大时往往需要分库分表,这些ID经常作为取模分库分表的依据,为了分库分表后数据均匀,ID生成往往有“取模随机性”的需求,所以我们通常把每秒内的序列号放在ID的最末位,保证生成的ID是随机的。
    //又如果,我们在跨毫秒时,序列号总是归0,会使得序列号为0的ID比较多,导致生成的ID取模后不均匀。解决方法是,序列号不是每次都归0,而是归一个0到9的随机数,这个地方。
    protected long generateRandom() {
        return RandomUtils.nextLong(0,10);
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    //==============================Test=============================================
    /** 测试 */
    public static void main(String[] args) {
        SnowFlakeWorker idWorker = new SnowFlakeWorker(0, 0);
        for (int i = 0; i < 100; i++) {
            long id = idWorker.nextId();
            //System.out.println(Long.toBinaryString(id));
            System.out.println(id);

        }
    }
}

 

标签:JAVA,sequence,timestamp,生成器,lastTimestamp,long,SnowFlakeId,private,ID
From: https://www.cnblogs.com/yk775879106/p/16711905.html

相关文章

  • Java 中 IO 流
    Java中IO流分为几种?按照流的流向分,可以分为输入流和输出流;按照操作单元划分,可以划分为字节流和字符流;按照流的角色划分为节点流和处理流。JavaIo流共涉及40......
  • Java处理Linux软连接文件
    这是几年前写的旧文,此前发布Wordpress小站上,现在又重新整理。算是温故知新,后续会继续整理。如有错误望及时指出,在此感谢。背景:运维同学反馈有一个部署很久的线上业务组......
  • 前端面试题JavaScript篇——2022-09-20
    每日3题1以下代码执行后,控制台中的输出内容为?//index.jsconsole.log(1);import{sum}from"./sum.js";console.log(sum(1,2));//sum.jsconsole.log(2);exp......
  • Java线程的join方法
    java线程中的join方法线程的join方法可以用来让本线程插队,强行占用cpu执行权;现有线程A在cpu上运行,另一个线程B调用自己的join方法,强行把正在运行的线程A退回到等待状态,......
  • Java基础之ClassFile文件结构
    本文相关知识均来自:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html.class文件都遵循ClassFile结构:ClassFile{u4magic;u2......
  • 【Java基础】System.out.println() 解析
    1.代码说明System类提供一些有用的属性和方法,包括标准输入输出和错误打印。有一个对象属性out,类型为PrintStream。setOut()方法使用static修饰,类加载时执行。该对象属......
  • java通过Throwable的printStackTrace方法将异常信息保存到字符串中
    java通过Throwable的printStackTrace方法将异常信息保存到字符串中   /***将异常信息转化成字符串*@paramt*@return*@throwsIOException*/priv......
  • 开发工具:第四章:Java开发必选工具
      更多内容请见原文,原文转载自:https://blog.csdn.net/weixin_44519496/article/details/120323616......
  • JavaScript中 with的用法
    文章是本人大三期间的学习笔记,一些论断取自书籍和网上博客,碍于当时的技术水平有一些写得不够好的地方,可以在评论处理智讨论~说起js中的with关键字,很多小伙伴们的第一印象......
  • JAVA入门基础_从零开始的培训_Redis
    目录Redis能够为我们解决什么问题Redis的下载与安装前台启动(不推荐)与后台启动常用五大数据类型Redis键常用命令(key)4个数据库操作命令String字符串命令String的内存结构Li......