diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdConstants.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdConstants.java new file mode 100644 index 000000000..bf47a07d4 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdConstants.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024. looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * https://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.data.id; + +import org.dromara.hutool.core.util.RandomUtil; + +/** + * ID相关常量 + * + * @author Looly + * @since 5.8.28 + */ +public class IdConstants { + /** + * 默认的数据中心ID。 + *

此常量通过调用{@link IdUtil#getDataCenterId(long)}方法,传入{@link Snowflake#MAX_DATA_CENTER_ID}作为参数, + * 来获取一个默认的数据中心ID。它在系统中作为一个全局配置使用,标识系统默认运行在一个最大数据中心ID限定的环境中。

+ * + * @see IdUtil#getDataCenterId(long) + * @see Snowflake#MAX_DATA_CENTER_ID + */ + public static final long DEFAULT_DATACENTER_ID = IdUtil.getDataCenterId(Snowflake.MAX_DATA_CENTER_ID); + + /** + * 默认的Worker ID生成。 + *

这个静态常量是通过调用IdUtil的getWorkerId方法,使用默认的数据中心ID和Snowflake算法允许的最大Worker ID来获取的。

+ * + * @see IdUtil#getWorkerId(long, long) 获取Worker ID的具体实现方法 + * @see Snowflake#MAX_WORKER_ID Snowflake算法中定义的最大Worker ID + */ + public static final long DEFAULT_WORKER_ID = IdUtil.getWorkerId(DEFAULT_DATACENTER_ID, Snowflake.MAX_WORKER_ID); + + /** + * 默认的节点ID。 + * 这个方法是静态的,且最终返回的节点ID是基于数据中心ID生成,生成失败则使用随机数 + */ + public static final long DEFAULT_SEATA_NODE_ID = generateNodeId(); + + /** + * 默认的Snowflake单例,使用默认的Worker ID和数据中心ID。
+ * 传入{@link #DEFAULT_WORKER_ID}和{@link #DEFAULT_DATACENTER_ID}作为参数。
+ * 此单例对象保证在同一JVM实例中获取ID唯一,唯一性使用进程ID和MAC地址保证。 + */ + public static final Snowflake DEFAULT_SNOWFLAKE = new Snowflake(DEFAULT_WORKER_ID, DEFAULT_DATACENTER_ID); + + /** + * 默认的Seata单例,使用默认的节点ID。
+ * 传入{@link #DEFAULT_SEATA_NODE_ID}作为参数。
+ * 此单例对象保证在同一JVM实例中获取ID唯一,唯一性使用进程ID和MAC地址保证。 + */ + public static final SeataSnowflake DEFAULT_SEATA_SNOWFLAKE = new SeataSnowflake(DEFAULT_SEATA_NODE_ID); + + /** + * 基于网卡MAC地址生成节点ID,失败则使用随机数 + * + * @return nodeId + */ + private static long generateNodeId() { + try { + return IdUtil.getDataCenterId(SeataSnowflake.MAX_NODE_ID); + } catch (final Exception e) { + return RandomUtil.randomLong(SeataSnowflake.MAX_NODE_ID + 1); + } + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdUtil.java index dd3da0cf0..786747f78 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdUtil.java @@ -33,7 +33,7 @@ import org.dromara.hutool.core.util.RuntimeUtil; */ public class IdUtil { - // ------------------------------------------------------------------- UUID + // region ----- UUID /** * 获取随机UUID @@ -73,6 +73,10 @@ public class IdUtil { return UUID.fastUUID().toString(true); } + // endregion + + // region ----- ObjectId + /** * 创建MongoDB ID生成策略实现
* ObjectId由以下几部分组成: @@ -92,6 +96,10 @@ public class IdUtil { return ObjectId.next(); } + // endregion + + // region ----- Snowflake + /** * 获取单例的Twitter的Snowflake 算法生成器对象
* 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。 @@ -164,12 +172,64 @@ public class IdUtil { * 参考:http://www.cnblogs.com/relucent/p/4955340.html * * @return {@link Snowflake} + * @see IdConstants#DEFAULT_SNOWFLAKE * @since 5.7.3 */ public static Snowflake getSnowflake() { - return Singleton.get(Snowflake.class); + return IdConstants.DEFAULT_SNOWFLAKE; } + /** + * 简单获取Snowflake 的 nextId
+ * 终端ID 数据中心ID 默认为根据PID和MAC地址生成 + * + * @return nextId + * @since 5.7.18 + */ + public static long getSnowflakeNextId() { + return getSnowflake().next(); + } + + /** + * 简单获取Snowflake 的 nextId
+ * 终端ID 数据中心ID 默认为根据PID和MAC地址生成 + * + * @return nextIdStr + * @since 5.7.18 + */ + public static String getSnowflakeNextIdStr() { + return getSnowflake().nextStr(); + } + + // endregion + + // region ----- NanoId + + /** + * 获取随机NanoId + * + * @return 随机NanoId + * @since 5.7.5 + */ + public static String nanoId() { + return NanoId.randomNanoId(); + } + + /** + * 获取随机NanoId + * + * @param size ID中的字符数量 + * @return 随机NanoId + * @since 5.7.5 + */ + public static String nanoId(final int size) { + return NanoId.randomNanoId(size); + } + + // endregion + + // region ----- DataCenterId and WorkerId + /** * 获取数据中心ID
* 数据中心ID依赖于本地网卡MAC地址。 @@ -183,19 +243,19 @@ public class IdUtil { */ public static long getDataCenterId(long maxDatacenterId) { Assert.isTrue(maxDatacenterId > 0, "maxDatacenterId must be > 0"); - if(maxDatacenterId == Long.MAX_VALUE){ + if (maxDatacenterId == Long.MAX_VALUE) { maxDatacenterId -= 1; } long id = 1L; byte[] mac = null; - try{ + try { mac = Ipv4Util.getLocalHardwareAddress(); - }catch (final HutoolException ignore){ + } catch (final HutoolException ignore) { // ignore } if (null != mac) { id = ((0x000000FF & (long) mac[mac.length - 2]) - | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; + | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; id = id % (maxDatacenterId + 1); } @@ -229,49 +289,59 @@ public class IdUtil { return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1); } - // ------------------------------------------------------------------- NanoId + // endregion + + // region ----- SeateSnowflake /** - * 获取随机NanoId + * 获取默认的SeataSnowflake单例实例。 * - * @return 随机NanoId - * @since 5.7.5 + *

该方法为静态方法,无需实例化对象即可调用。它返回的是一个默认配置的SeataSnowflake实例, + * 可以直接用于生成分布式ID。在应用程序中,如果未显式设置自定义的SeataSnowflake实例, + * 则会使用此默认实例。 + * + * @return SeataSnowflake 返回一个默认配置的SeataSnowflake实例。 + * + * @see IdConstants#DEFAULT_SNOWFLAKE */ - public static String nanoId() { - return NanoId.randomNanoId(); + public static SeataSnowflake getSeataSnowflake() { + return IdConstants.DEFAULT_SEATA_SNOWFLAKE; } /** - * 获取随机NanoId + * 获取SeataSnowflake单例实例。 * - * @param size ID中的字符数量 - * @return 随机NanoId - * @since 5.7.5 + *

该方法为静态方法,无需实例化对象即可调用。它返回的是一个自定义配置的SeataSnowflake实例, + * 可以用于生成具有特定节点ID的分布式ID。 + * + * @param nodeId 节点ID + * @return SeataSnowflake 返回一个自定义配置的SeataSnowflake实例。 + * + * @see IdConstants#DEFAULT_SEATA_SNOWFLAKE */ - public static String nanoId(final int size) { - return NanoId.randomNanoId(size); + public static SeataSnowflake getSeataSnowflake(final long nodeId) { + return Singleton.get(SeataSnowflake.class, nodeId); } /** - * 简单获取Snowflake 的 nextId - * 终端ID 数据中心ID 默认为1 + * 简单获取SeataSnowflake 的 nextId
+ * NodeId默认为DataCenterId * * @return nextId - * @since 5.7.18 */ - public static long getSnowflakeNextId() { - return getSnowflake().next(); + public static long getSeataSnowflakeNextId() { + return getSeataSnowflake().next(); } /** - * 简单获取Snowflake 的 nextId - * 终端ID 数据中心ID 默认为1 + * 简单获取SeataSnowflake 的 nextId
+ * NodeId默认为DataCenterId * * @return nextIdStr - * @since 5.7.18 */ - public static String getSnowflakeNextIdStr() { - return getSnowflake().nextStr(); + public static String getSeataSnowflakeNextIdStr() { + return getSeataSnowflake().nextStr(); } + // endregion } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/SeataSnowflake.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/SeataSnowflake.java index afa25c01d..e723442e6 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/SeataSnowflake.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/SeataSnowflake.java @@ -14,7 +14,6 @@ package org.dromara.hutool.core.data.id; import org.dromara.hutool.core.lang.generator.Generator; import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.core.util.RandomUtil; import java.io.Serializable; import java.util.Date; @@ -48,7 +47,7 @@ public class SeataSnowflake implements Generator, Serializable { // 节点ID长度 private static final int NODE_ID_BITS = 10; // 节点ID的最大值,1023 - private final int MAX_NODE_ID = ~(-1 << NODE_ID_BITS); + protected static final int MAX_NODE_ID = ~(-1 << NODE_ID_BITS); // 时间戳长度 private static final int TIMESTAMP_BITS = 41; // 序列号12位(表示只允许序号的范围为:0-4095) @@ -79,7 +78,7 @@ public class SeataSnowflake implements Generator, Serializable { * 构造 * * @param epochDate 初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用 - * @param nodeId 节点ID + * @param nodeId 节点ID, 默认为DataCenterId或随机生成 */ public SeataSnowflake(final Date epochDate, final Long nodeId) { final long twepoch = (null == epochDate) ? DEFAULT_TWEPOCH : epochDate.getTime(); @@ -117,7 +116,7 @@ public class SeataSnowflake implements Generator, Serializable { */ private void initNodeId(Long nodeId) { if (nodeId == null) { - nodeId = generateNodeId(); + nodeId = IdConstants.DEFAULT_SEATA_NODE_ID; } if (nodeId > MAX_NODE_ID || nodeId < 0) { final String message = StrUtil.format("worker Id can't be greater than {} or less than 0", MAX_NODE_ID); @@ -125,17 +124,4 @@ public class SeataSnowflake implements Generator, Serializable { } this.nodeId = nodeId << (TIMESTAMP_BITS + SEQUENCE_BITS); } - - /** - * 基于网卡MAC地址生成节点ID,失败则使用随机数 - * - * @return workerId - */ - private long generateNodeId() { - try { - return IdUtil.getDataCenterId(MAX_NODE_ID); - } catch (final Exception e) { - return RandomUtil.randomLong(MAX_NODE_ID + 1); - } - } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/Snowflake.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/Snowflake.java index e3ef942de..3c8e7cb4e 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/Snowflake.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/Snowflake.java @@ -54,10 +54,10 @@ public class Snowflake implements Generator, Serializable { public static final long DEFAULT_TWEPOCH = 1288834974657L; private static final long WORKER_ID_BITS = 5L; // 最大支持机器节点数0~31,一共32个 - private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); + protected static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); private static final long DATA_CENTER_ID_BITS = 5L; // 最大支持数据中心节点数0~31,一共32个 - private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS); + protected static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS); // 序列号12位(表示只允许序号的范围为:0-4095) private static final long SEQUENCE_BITS = 12L; // 机器节点左移12位 @@ -94,7 +94,7 @@ public class Snowflake implements Generator, Serializable { * 构造,使用自动生成的工作节点ID和数据中心ID */ public Snowflake() { - this(IdUtil.getWorkerId(IdUtil.getDataCenterId(MAX_DATA_CENTER_ID), MAX_WORKER_ID)); + this(IdConstants.DEFAULT_WORKER_ID); } /** @@ -103,7 +103,7 @@ public class Snowflake implements Generator, Serializable { * @param workerId 终端ID */ public Snowflake(final long workerId) { - this(workerId, IdUtil.getDataCenterId(MAX_DATA_CENTER_ID)); + this(workerId, IdConstants.DEFAULT_DATACENTER_ID); } /** diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/data/id/SnowflakeTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/data/id/SnowflakeTest.java index 80e2da7fd..d146a2fe3 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/data/id/SnowflakeTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/data/id/SnowflakeTest.java @@ -13,8 +13,6 @@ package org.dromara.hutool.core.data.id; import org.dromara.hutool.core.collection.ConcurrentHashSet; -import org.dromara.hutool.core.data.id.IdUtil; -import org.dromara.hutool.core.data.id.Snowflake; import org.dromara.hutool.core.exception.HutoolException; import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.lang.tuple.Pair; @@ -83,6 +81,22 @@ public class SnowflakeTest { }); } + @Test + @Disabled + public void uniqueTest2(){ + // 测试并发环境下生成ID是否重复 + final Snowflake snowflake = IdUtil.getSnowflake(); + + final Set ids = new ConcurrentHashSet<>(); + ThreadUtil.concurrencyTest(100, () -> { + for (int i = 0; i < 50000; i++) { + if(!ids.add(snowflake.next())){ + throw new HutoolException("重复ID!"); + } + } + }); + } + @Test public void getSnowflakeLengthTest(){ for (int i = 0; i < 1000; i++) {