java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java雪花算法生成全局唯一ID

java中使用雪花算法(Snowflake)为分布式系统生成全局唯一ID代码示例

作者:Jack_abu

Java雪花算法是一种用于生成唯一ID的算法,它可以在分布式系统中生成全局唯一的ID,这篇文章主要给大家介绍了关于java中使用雪花算法(Snowflake)为分布式系统生成全局唯一ID的相关资料,需要的朋友可以参考下

(全局唯一ID的解决方案有很多种,这里主要是介绍和学习Snowflake算法)

什么是雪花算法(Snowflake)

雪花算法(Snowflake Algorithm)是由Twitter公司在2010年左右提出的一种分布式ID生成算法,主要用于生成全局唯一且趋势递增的ID。这种算法生成的ID是一个64位的长整型数字,具有很高的性能与扩展性,特别适合于分布式环境下的主键生成场景,比如数据库表主键、消息队列的Message ID等。

实现原理

Snowflake算法的原理主要体现在它生成64位ID的结构上,主要划分为如下几个部分:

0 | 00000000000000000000000000000000000000000 | 00000 | 00000 | 000000000000

第1位通常固定为0,表示生成的ID都是正数。

从第2位到第42位(共41位)存储时间戳信息,精确到毫秒级别。时间戳可以是自定义的一个起始时间点(如Twitter使用的是2010-11-04的某一时刻),这样可以通过比较ID中的时间戳部分来判断事件发生的先后顺序。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69。

从第43位到第52位(共10位)存储工作机器ID或者数据中心ID。这部分可以进一步细分为两部分,例如前5位标识数据中心ID,后5位标识工作节点ID。这样可以支持32(0~31)个数据中心以及每个数据中心内部的32(0~31)个工作节点,足够覆盖大规模分布式系统的节点标识。

从第53位到第64位(共12位)存储同一节点同一毫秒内生成的序列号,这意味着同一个节点在同毫秒内可以生成最多4096个不同的ID(2^12)。

当生成ID时,首先获取当前时间戳,然后加上工作节点ID以及序列号。如果在同一毫秒内有新的请求,则序列号加1。若序列号达到最大值,则等待下一毫秒再进行分配,从而确保在同一节点内生成的ID是唯一的

雪花算法的优缺点

优点:

缺点:

因此,为了更全面地解决雪花算法的缺陷问题,可能需要采取额外的措施,例如:

综上所述,雪花算法在分布式系统中具有广泛的应用价值,其全局唯一性和递增有序性使得它成为生成唯一ID的优选方案之一。然而,在使用雪花算法时也需要注意其潜在的缺点,并根据实际需求进行配置和优化。

Snowflake算法生成ID的Java代码示例

以下是Snowflake算法的一个java简化版实现:

public class SnowflakeIdWorker {  
    // 起始的时间戳(自定义,例如系统上线时间)  
    private final long twepoch = 1288834974657L;  
  
    // 机器id所占的位数  
    private final long workerIdBits = 5L;  
  
    // 数据标识id所占的位数  
    private final long datacenterIdBits = 5L;  
  
    // 最大机器ID  
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);  
  
    // 最大数据标识ID  
    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=4095)  
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);  
  
    // 上次生成ID的时间截  
    private long lastTimestamp = -1L;  
  
    // 序列号  
    private long sequence = 0L;  
  
    // 工作机器ID  
    private final long workerId;  
  
    // 数据中心ID  
    private final long datacenterId;  
  
    public SnowflakeIdWorker(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;  
    }    
  
    // 生成ID  
    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 {  
            // 时间戳改变,序列号重置为0  
            sequence = 0L;  
        }  
  
        // 更新最后的时间戳  
        lastTimestamp = timestamp;  
  
        // 移位并通过或运算拼到一起组成64位的ID  
        return ((timestamp - twepoch) << timestampLeftShift) |  
               (datacenterId << datacenterIdShift) |  
               (workerId << workerIdShift) |  
               sequence;  
    }  
  
    // 获取当前时间戳  
    protected long timeGen() {  
        return System.currentTimeMillis();  
    }  
  
    // 等待下一个毫秒  
    protected long tilNextMillis(long lastTimestamp) {  
        long timestamp = timeGen();  
        while (timestamp <= lastTimestamp) {  
            timestamp = timeGen();  
        }  
        return timestamp;  
    }

    public static void main(String[] args) {  
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1);  
        for (int i = 0; i < 5; i++) {  
            long id = idWorker.nextId();  
            System.out.println(Long.toBinaryString(id));  
            System.out.println(id);  
        }  
    }  
}

代码输出:

这段代码实现了雪花算法的核心逻辑。在nextId()方法中,它首先获取当前时间戳,然后检查时间戳是否小于上一次生成ID时的时间戳,如果是,则抛出异常,因为这意味着系统时钟回退,可能会导致ID生成出现混乱。如果时间戳相同,则序列号自增,并检查是否溢出,如果溢出则等待下一个毫秒。如果时间戳不同,则重置序列号。最后,将时间戳、数据中心ID、机器ID和序列号按照各自的偏移量左移,然后进行位或运算,组合成一个64位的ID。

(注:关于数据中心ID、机器ID,根据实际情况来进行配置。)

总结

到此这篇关于java中使用雪花算法(Snowflake)为分布式系统生成全局唯一ID的文章就介绍到这了,更多相关java雪花算法生成全局唯一ID内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文