实现高性能分布式 ID 生成器:Java 雪花算法详解

1、简介

雪花算法(Snowflake)是 Twitter 开源的分布式 ID 生成算法,可以生成不重复的、有序的、可自增的 64 位 ID,适用于分布式系统中的 ID 生成需求。

雪花算法的核心思想是将一个 64 位的 ID 按照一定的规则进行拆分,其中 41 位作为时间戳,10 位作为机器 ID,12 位作为序列号,保证了生成的 ID 全局唯一、有序、可自增。

雪花算法的 ID 由以下几个部分组成:

  • 符号位:1 个 bit,始终为 0,用于区分正数和负数。

  • 时间戳:41 个 bit,精确到毫秒级别。使用当前时间减去一个固定的开始时间,可以得到一个时间差值。由于时间戳占用了 41 个 bit,最大可表示的时间为 2^41 / (1000 * 60 * 60 * 24 * 365) = 69 年左右。

  • 数据中心 ID:5 个 bit,用于区分不同的数据中心。如果没有多个数据中心,可以将其设置为 0。

  • 机器 ID:5 个 bit,用于区分同一数据中心内不同的机器。同样地,如果没有多台机器,可以将其设置为 0。

  • 序列号:12 个 bit,用于区分同一毫秒内生成的不同 ID。由于序列号只有 12 个 bit,最大可表示的序列号为 2^12 - 1 = 4095。如果在同一毫秒内生成的序列号超过了 4095,需要等到下一毫秒再生成新的 ID。

综上所述,一个雪花算法生成的 ID 长度为 64 bit,可以保证在分布式系统中生成唯一的 ID。

2、雪花算法优缺点

2.1、雪花算法的优点包括:

  • 高效性:雪花算法生成 ID 的速度非常快,可以满足高并发场景下的需求。

  • 唯一性:通过使用时间戳和机器 ID 等信息来生成 ID,保证了生成的 ID 的唯一性。

  • 易于使用:使用雪花算法生成 ID 的代码相对简单,易于集成到现有系统中。

2.2、雪花算法的缺点包括:

  • 依赖时钟:雪花算法生成 ID 的唯一性和正确性依赖于时钟的正确性。如果机器的时钟出现问题,可能会导致生成的 ID 不唯一或不正确。

  • 受限于机器数量:雪花算法的唯一性也受限于机器数量,如果集群中的机器数量超过了算法中机器 ID 的范围,可能会导致生成的 ID 不唯一。

  • 受限于机器性能:如果机器性能较差,可能会导致生成 ID 的速度变慢,无法满足高并发的需求。

3、Java 实现雪花算法

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

/**
 * 

Java 雪花算法

* Created by woniu */
public class SnowFlake { // 起始的时间戳,这个时间戳可以是你的系统初始时间,一般取当前时间戳 private final static long START_TIMESTAMP = 1672502400000L; // 2023-01-01 00:00:00 // 每一部分占用的位数,可以根据自己的需求进行调整,这里是按照默认的占位数进行分配 private final static long SEQUENCE_BIT = 12; // 序列号占用的位数 private final static long MACHINE_BIT = 5; // 机器标识占用的位数 private final static long DATA_CENTER_BIT = 5; // 数据中心占用的位数 // 每一部分的最大值,可以根据占用的位数进行计算得到 private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT); private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT); private final static long MAX_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT); // 每一部分向左的位移,计算出来的值是为了后面生成 ID 做准备 private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT; private long dataCenterId; // 数据中心 ID private long machineId; // 机器 ID private long sequence = 0L; // 序列号 private long lastTimeStamp = -1L; // 上一次时间戳 /** *

构造方法

* @param dataCenterId 数据中心 ID * @param machineId 机器 ID * */
public SnowFlake(long dataCenterId, long machineId) { if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) { throw new IllegalArgumentException("数据中心标识不能大于等于 " + MAX_DATA_CENTER_NUM + " 或小于 0"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("机器标识不能大于等于 " + MAX_MACHINE_NUM + " 或小于 0"); } this.dataCenterId = dataCenterId; this.machineId = machineId; } /** *

雪花算法核心方法

* 通过调用 nextId() 方法,让当前这台机器上的 snowflake 算法程序生成一个全局唯一的 id * */
public synchronized long nextId() { // 获取系统当前时间戳 long currentTimeStamp = getSystemCurrentTimeMillis(); if (currentTimeStamp < lastTimeStamp) { throw new RuntimeException("时钟向后移动,拒绝生成雪花算法ID"); } if (currentTimeStamp == lastTimeStamp) { // 当前毫秒内,序列号自增 sequence = (sequence + 1) & MAX_SEQUENCE; // 序列号超出范围,需要等待下一毫秒 if (sequence == 0L) { // 获取下一毫秒 currentTimeStamp = getNextMill(lastTimeStamp); } } else { // 不同毫秒内,序列号置为 0 sequence = 0L; } lastTimeStamp = currentTimeStamp; // 使用位运算生成最终的 ID return (currentTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | dataCenterId << DATA_CENTER_LEFT | machineId << MACHINE_LEFT | sequence; } /** *

获取系统当前时间戳

* @return 当前时间(毫秒) */
private long getSystemCurrentTimeMillis() { return System.currentTimeMillis(); } /** *

获取下一毫秒

* 当某一毫秒的时间,产生的 id 数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生 ID * @param lastTimestamp 上次生成 ID 的时间截 * @return 当前时间戳 */
private long getNextMill(long lastTimestamp) { long timeMillis = getSystemCurrentTimeMillis(); while(timeMillis <= lastTimestamp){ timeMillis = getSystemCurrentTimeMillis(); } return timeMillis; } /** *

测试类

*/
public static void main(String[] args) { SnowFlake worker1 = new SnowFlake(1,1); SnowFlake worker2 = new SnowFlake(2,1); SnowFlake worker3 = new SnowFlake(3,1); for (int i = 0; i < 30; i++){ System.out.println("数据中心1,雪花算法 ID:" + worker1.nextId()); System.out.println("数据中心2,雪花算法 ID:" + worker2.nextId()); System.out.println("数据中心3,雪花算法 ID:" + worker3.nextId()); } }

运行结果如下:

数据中心1,雪花算法 ID:32120788563922944
数据中心2,雪花算法 ID:32120788568248320
数据中心3,雪花算法 ID:32120788568379392
数据中心1,雪花算法 ID:32120788568117248
数据中心2,雪花算法 ID:32120788568248321
数据中心3,雪花算法 ID:32120788568379393
数据中心1,雪花算法 ID:32120788568117249
数据中心2,雪花算法 ID:32120788568248322
数据中心3,雪花算法 ID:32120788568379394
数据中心1,雪花算法 ID:32120788568117250
数据中心2,雪花算法 ID:32120788568248323
数据中心3,雪花算法 ID:32120788568379395
数据中心1,雪花算法 ID:32120788572311552
数据中心2,雪花算法 ID:32120788572442624
数据中心3,雪花算法 ID:32120788572573696
数据中心1,雪花算法 ID:32120788572311553
数据中心2,雪花算法 ID:32120788572442625
数据中心3,雪花算法 ID:32120788572573697
数据中心1,雪花算法 ID:32120788572311554
数据中心2,雪花算法 ID:32120788572442626
数据中心3,雪花算法 ID:32120788572573698
数据中心1,雪花算法 ID:32120788572311555
数据中心2,雪花算法 ID:32120788572442627
数据中心3,雪花算法 ID:32120788572573699
数据中心1,雪花算法 ID:32120788572311556
数据中心2,雪花算法 ID:32120788572442628
数据中心3,雪花算法 ID:32120788576768000
数据中心1,雪花算法 ID:32120788576505856

相比于传统的自增序列或 UUID,雪花算法可以在分布式系统中保证 ID 的唯一性,同时不依赖于中心节点的生成,而是通过机器 ID 和时间戳的组合实现分布式的 ID 生成。这样可以避免单点故障和性能瓶颈,提高系统的可伸缩性和性能。

雪花算法的实现方式也比较简单,只需要在每台机器上部署一个单独的 ID 生成器,通过配置不同的机器 ID 和数据中心 ID,就可以保证全局唯一性。目前,雪花算法已经广泛应用于各种分布式系统中,如 Hadoop、Zookeeper、Kafka、Elasticsearch 等。

需要注意的是,雪花算法并不是绝对安全的,由于时间戳精度的限制和机器 ID 的分配方式,可能会存在时间回拨、重复机器 ID 等问题。因此,在实际应用中,需要根据具体场景进行适当的调整和优化,以保证 ID 的全局唯一性和正确性。

本文教程到此结束,祝愿小伙伴们在编程之旅中能够愉快地探索、学习、成长!

你可能感兴趣的:(Java,算法,java,SnowFlake,雪花算法,分布式,分布式ID)