详解Java中雪花算法的实现

详解Java中雪花算法的实现

需求概述

在分布式系统中,为了保证业务数据的唯一性,需要生成唯一的ID。传统的ID生成方式可能出现因为高并发而重复的情况,而雪花算法(Snowflake)正是为了解决这个问题而出现的。

本文会详细介绍Java中雪花算法的实现,及其原理。

雪花算法的基本原理

雪花算法是Twitter开源的分布式ID生成算法,采用一个64位的long类型作为唯一ID,其中各个部分表示的含义如下:

  1. 1位不用,可以用作别的标识
  2. 41位表示毫秒数,可以使用69年,到最高位是2199023255551(因为使用long存储,所以需要使用L作为后缀)
  3. 10位表示机器的id(5位机房id + 5位机器id),可以使用1024台机器
  4. 12位表示某一毫秒内的序列号,同一毫秒内最多可以生成4096个ID

Java中雪花算法的实现

下面是Java中实现雪花算法的代码示例:

public class SnowflakeIdGenerator {

    /**
     * 雪花算法的起始时间,用于计算时间戳
     */
    private static final long START_TIME = 1609459200000L;

    private static final long WORKER_ID_BITS = 5L;
    private static final long DATACENTER_ID_BITS = 5L;
    private static final long SEQUENCE_BITS = 12L;
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);

    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS; 

    /**
     * 工作节点ID
     */
    private final long workerId;

    /**
     * 数据中心ID
     */
    private final long datacenterId;

    /**
     * 当前毫秒生成的序列号
     */
    private long sequence = 0L;

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

    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID));
        }

        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATACENTER_ID));
        }

        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    /**
     * 生成一个唯一ID
     *
     * @return 雪花算法生成的唯一ID
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id");
        }

        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & ~(-1L << SEQUENCE_BITS);
            if (sequence == 0L) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - START_TIME) << TIMESTAMP_LEFT_SHIFT)
                | (datacenterId << DATACENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }

    /**
     * 获取当前的时间戳,并减去起始时间,保证不同数据中心和机器的时间戳序列递增
     */
    private long timeGen() {
        return System.currentTimeMillis() - START_TIME;
    }

    /**
     * 如果当前时间戳与上次生成的时间戳相同,则等待下一毫秒再生成
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
}

雪花算法生成ID的过程中,通过timeGen()方法获取当前的时间戳,并减去起始时间。通过&运算保证序列号不溢出。

示例说明

下面分别是两个雪花算法的生成ID的例子:

public class SnowflakeIdGeneratorTest {

    /**
     * 单机生成ID的示例
     */
    @Test
    public void testSingleMachine() {
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
        System.out.println(idGenerator.nextId());
    }

    /**
     * 多机房、多机器生成ID的示例
     */
    @Test
    public void testMultiMachine() {
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(15, 64);
        System.out.println(idGenerator.nextId());
    }
}

其中testSingleMachine()方法是在单机器上生成ID的示例,testMultiMachine()方法是在多机器、多机房的情况下生成ID的示例。

总结

通过本文的介绍,我们了解到了雪花算法的基本原理和Java中的实现方式,同时还提供了多机房、多机器和单机器模式下的示例。

在实际开发中,我们可以根据具体的业务需求,选择合适的方式生成唯一ID,保证业务数据的唯一性。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解Java中雪花算法的实现 - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • 在Spring Boot应用程序中使用Apache Kafka的方法步骤详解

    下面是在Spring Boot应用程序中使用Apache Kafka的方法步骤详解: 1. 引入Kafka相关依赖 在Spring Boot应用程序中使用Apache Kafka,我们首先需要在pom.xml文件中引入相应的依赖。这里我们使用Spring Boot提供的Kafka依赖,具体如下: <dependency> <groupId&…

    Java 2023年5月20日
    00
  • Java使用正则表达式进行匹配且对匹配结果逐个替换

    Java中使用正则表达式进行匹配和替换是非常常见的操作,可以用来处理各种文本数据。下面是Java使用正则表达式进行匹配且对匹配结果逐个替换的攻略。 正则表达式基础 在Java中,使用正则表达式的功能主要是通过java.util.regex包提供的类实现的,常用的类包括Pattern和Matcher。在使用之前,我们需要先了解正则表达式的基本语法: .:匹配任…

    Java 2023年5月27日
    00
  • 深入理解Hibernate中的flush机制

    介绍 Hibernate是一个流行的Java对象关系映射(ORM)框架,具有自己的缓存机制来提高性能。但是,当对象状态发生改变时,Hibernate缓存的值可能会与数据库的值不一致。因此,为了确保一致性,Hibernate借助flush机制将所有未保存的更改与数据库同步。本文将详细介绍Hibernate中的flush机制和如何使用它。 flush方法 flu…

    Java 2023年5月20日
    00
  • Java程序执行过程及内存机制详解

    下面是“Java程序执行过程及内存机制详解”的完整攻略: Java程序执行过程 编译器将代码转换成字节码 当我们编写Java程序时,使用的是Java语言,而计算机并不能理解Java语言,所以我们需要将Java源代码通过Java编译器(例如javac命令)转换成一种中间形式的代码,叫做字节码(Byte Code),也称为类文件(class file)。这个过程…

    Java 2023年5月23日
    00
  • 浅谈java二进制、十进制、十六进制、字符串之间的相互转换

    Java进制转换攻略 Java中提供了十进制、二进制、八进制、十六进制的进制表示,同时也支持将不同进制之间进行转换,并提供字符串和数字之间的转换方法。 十进制转换为其他进制 十进制转二进制 Java中可以使用 Integer 类的 toBinaryString 方法将十进制数转换为二进制字符串,例如: int decimalNum = 123; String…

    Java 2023年5月27日
    00
  • shiro 与 SpringMVC的整合完美示例

    以下是关于“shiro 与 SpringMVC的整合完美示例”的完整攻略,其中包含两个示例。 shiro 与 SpringMVC的整合完美示例 shiro是一个强大的Java安全框架,可以用于身份验证、授权、加密等。在本文中,我们将讲解如何将shiro与SpringMVC整合,以实现安全的Web应用程序。 整合步骤 将shiro与SpringMVC整合的步骤…

    Java 2023年5月17日
    00
  • C#编程自学之开篇介绍

    C#编程自学之开篇介绍 本文将为大家介绍如何通过自学的方式学习C#编程语言。C#是一种面向对象的程序设计语言,它主要用于开发Windows桌面应用程序、Web应用程序、游戏、移动应用程序等应用领域。相信大家在学习C#编程过程中会遇到各种各样的问题,如何处理这些问题是自学过程中最关键的一点。 确定学习C#编程的目的和方向 在开始自学之前,首先需要明确自己想要学…

    Java 2023年5月23日
    00
  • 什么是synchronized关键字?

    当一个方法或一个代码块被synchronized关键字修饰时,该方法或代码块会被称为“同步方法”或“同步代码块”。synchronized是Java中实现线程同步的一种基本机制,可用于保证多个线程并发访问共享数据时的安全性,防止数据的不一致或线程间的资源竞争。下面分别就方法和代码块的同步使用方式做详细介绍。 同步方法 被synchronized关键字修饰的方…

    Java 2023年5月10日
    00
合作推广
合作推广
分享本页
返回顶部