雪花算法ID生成器:IdAssignUtil详解

package com.csot.kms.common.utill;

import lombok.extern.slf4j.Slf4j;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

/**
 * @Author dj
 * @Date 2021/12/1
 **/
@Slf4j
public class IdAssignUtil {
    private final static long twepoch = 12888349746579L;
    // 机器标识位数
    private final static long workerIdBits = 5L;
    // 数据中心标识位数
    private final static long datacenterIdBits = 5L;

    // 毫秒内自增位数
    private final static long sequenceBits = 12L;
    // 机器ID偏左移12位
    private final static long workerIdShift = sequenceBits;
    // 数据中心ID左移17位
    private final static long datacenterIdShift = sequenceBits + workerIdBits;
    // 时间毫秒左移22位
    private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    //sequence掩码,确保sequnce不会超出上限
    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
    //上次时间戳
    private static long lastTimestamp = -1L;
    private static long workerMask = -1L ^ (-1L << workerIdBits);
    private static long processMask = -1L ^ (-1L << datacenterIdBits);
    private static IdAssignUtil snowFlake = null;

    static {
        snowFlake = new IdAssignUtil();
    }

    //序列
    private long sequence = 0L;
    //服务器ID
    private long workerId = 1L;
    //进程编码
    private long processId = 1L;

    private IdAssignUtil() {

        //获取机器编码
        this.workerId = this.getMachineNum();
        //获取进程编码
        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        this.processId = Long.valueOf(runtimeMXBean.getName().split("@")[0]).longValue();

        //避免编码超出最大值
        this.workerId = workerId & workerMask;
        this.processId = processId & processMask;
    }

    public static synchronized long nextId() {
        return snowFlake.getNextId();
    }

    public synchronized long getNextId() {
        //获取时间戳
        long timestamp = timeGen();
        //如果时间戳小于上次时间戳则报错
        if (timestamp < lastTimestamp) {
            try {
                throw new Exception("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //如果时间戳与上次时间戳相同
        if (lastTimestamp == timestamp) {
            // 当前毫秒内,则+1,与sequenceMask确保sequence不会超出上限
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                // 当前毫秒内计数满了,则等待下一秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }
        lastTimestamp = timestamp;
        // ID偏移组合生成最终的ID,并返回ID
        long nextId = ((timestamp - twepoch) << timestampLeftShift) | (processId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
        return nextId;
    }

    /**
     * 再次获取时间戳直到获取的时间戳与现有的不同
     *
     * @param lastTimestamp
     * @return 下一个时间戳
     */
    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

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

    /**
     * 获取机器编码
     *
     * @return
     */
    private long getMachineNum() {
        long machinePiece;
        StringBuilder sb = new StringBuilder();
        Enumeration<NetworkInterface> e = null;
        try {
            e = NetworkInterface.getNetworkInterfaces();
        } catch (SocketException e1) {
            e1.printStackTrace();
        }
        while (e.hasMoreElements()) {
            NetworkInterface ni = e.nextElement();
            sb.append(ni.toString());
        }
        machinePiece = sb.toString().hashCode();
        return machinePiece;
    }

}

在分布式系统中,生成唯一的ID是一个常见的需求。为了解决这一问题,Twitter开发了雪花算法(Snowflake Algorithm),它可以在不依赖数据库的情况下生成唯一的ID。本文将介绍一个基于雪花算法的Java工具类IdAssignUtil,它可以帮助我们在分布式系统中生成唯一的ID。

概述
IdAssignUtil是一个使用雪花算法实现的ID生成器。它通过组合时间戳、机器ID和序列号来生成唯一的ID。这种算法的优点是性能高、依赖少,并且可以保证在分布式系统中生成不重复的ID。

核心概念
时间戳:使用当前时间戳来保证ID的时序性。
机器ID:标识ID生成器所在的机器。
序列号:在同一毫秒内,如果有多个ID生成请求,序列号用于区分它们。
类设计
IdAssignUtil类中定义了一些常量和变量来支持算法的实现:

twepoch:时间戳的起始点,用于减少ID的存储空间。
workerIdBits、datacenterIdBits、sequenceBits:分别表示机器ID、数据中心ID和序列号的位数。
workerIdShift、datacenterIdShift、timestampLeftShift:用于在64位ID中定位各个部分的位置。
sequenceMask、workerMask、processMask:用于确保各个部分的值不会超出预设的位数。
lastTimestamp:上一次生成ID的时间戳。
sequence:同一毫秒内的序列号。
workerId、processId:机器ID和进程ID。
方法详解
构造函数
IdAssignUtil的构造函数是私有的,确保不能直接实例化。它在静态初始化块中创建了一个单例对象。

getMachineNum()
这是一个私有方法,用于获取机器的标识码。它通过遍历网络接口来生成一个基于机器MAC地址的字符串,然后将这个字符串转换为哈希值作为机器ID。

timeGen()
这是一个私有方法,用于生成当前的时间戳。

tilNextMillis()
这是一个私有方法,用于等待直到获取到下一个不同的时间戳。

getNextId()
这是一个公共方法,用于生成下一个ID。它首先获取当前的时间戳,然后检查是否与上一次的时间戳相同。如果相同,它会尝试增加序列号。如果序列号也达到了上限,它会等待直到下一个毫秒。

使用场景
IdAssignUtil适用于需要生成唯一ID的分布式系统,如订单ID、用户ID、会话ID等。它可以确保在高并发环境下生成不重复的ID。

结论
IdAssignUtil是一个基于雪花算法的高效ID生成器。它通过合理的设计和实现,确保了ID的唯一性和时序性。使用这个工具类,我们可以在分布式系统中轻松地生成唯一的ID,从而提高系统的可扩展性和可维护性。

posted @   djπ  阅读(673)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示