This commit is contained in:
Looly 2023-12-17 00:55:42 +08:00
parent 5bf7dcf88e
commit 11e194e80a
13 changed files with 184 additions and 96 deletions

View File

@ -260,7 +260,7 @@ public class IdUtil {
* @since 5.7.18
*/
public static long getSnowflakeNextId() {
return getSnowflake().nextId();
return getSnowflake().next();
}
/**
@ -271,7 +271,7 @@ public class IdUtil {
* @since 5.7.18
*/
public static String getSnowflakeNextIdStr() {
return getSnowflake().nextIdStr();
return getSnowflake().nextStr();
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2023. 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.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;
import java.util.concurrent.atomic.AtomicLong;
/**
* Seata改进的雪花算法ID<br>
* 改进主要是更换了节点和时间戳的位置以实现在单节点中单调递增
* 来自https://github.com/seata/seata/blob/2.x/common/src/main/java/io/seata/common/util/IdWorker.java
* 相关说明见
* <ul>
* <li>https://zhuanlan.zhihu.com/p/648460337</li>
* <li>http://seata.io/zh-cn/blog/seata-snowflake-explain.html</li>
* </ul>
*
* <pre>
* 符号位1bit - 节点标志ID10bit- 时间戳相对值41bit - 递增序号12bit
* (0) - (0000000000) - (0000000000 0000000000 0000000000 0000000000 0) - (000000000000)
* </pre>
*
* @author funkyeselfishlover
*/
public class SeataSnowflake implements Generator<Long>, Serializable {
private static final long serialVersionUID = 1L;
/**
* 默认的起始时间为2020-05-03
*/
public static long DEFAULT_TWEPOCH = 1588435200000L;
// 节点ID长度
private static final int NODE_ID_BITS = 10;
// 节点ID的最大值1023
private final int MAX_NODE_ID = ~(-1 << NODE_ID_BITS);
// 时间戳长度
private static final int TIMESTAMP_BITS = 41;
// 序列号12位表示只允许序号的范围为0-4095
private static final int SEQUENCE_BITS = 12;
// 时间戳+序号的最大值
private static final long timestampAndSequenceMask = ~(-1L << (TIMESTAMP_BITS + SEQUENCE_BITS));
private long nodeId;
private final AtomicLong timestampAndSequence;
/**
* 构造
*/
public SeataSnowflake(){
this(null);
}
/**
* 构造
*
* @param nodeId 节点ID
*/
public SeataSnowflake(final Long nodeId){
this(null, nodeId);
}
/**
* 构造
*
* @param epochDate 初始化时间起点null表示默认起始日期,后期修改会导致id重复,如果要修改连workerId dataCenterId慎用
* @param nodeId 节点ID
*/
public SeataSnowflake(final Date epochDate, final Long nodeId) {
final long twepoch = (null == epochDate) ? DEFAULT_TWEPOCH : epochDate.getTime();
final long timestampWithSequence = (System.currentTimeMillis() - twepoch) << SEQUENCE_BITS;
this.timestampAndSequence = new AtomicLong(timestampWithSequence);
initNodeId(nodeId);
}
/**
* 获取下一个雪花ID
*
* @return id
*/
@Override
public Long next() {
final long next = timestampAndSequence.incrementAndGet();
final long timestampWithSequence = next & timestampAndSequenceMask;
return nodeId | timestampWithSequence;
}
/**
* 下一个ID字符串形式
*
* @return ID 字符串形式
*/
public String nextStr() {
return Long.toString(next());
}
/**
* 初始化节点ID
*
* @param nodeId 节点ID
*/
private void initNodeId(Long nodeId) {
if (nodeId == null) {
nodeId = generateNodeId();
}
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);
throw new IllegalArgumentException(message);
}
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);
}
}
}

View File

@ -14,6 +14,7 @@ package org.dromara.hutool.core.data.id;
import org.dromara.hutool.core.date.SystemClock;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.generator.Generator;
import org.dromara.hutool.core.lang.tuple.Pair;
import org.dromara.hutool.core.util.RandomUtil;
@ -29,7 +30,7 @@ import java.util.Date;
*
* <pre>
* 符号位1bit- 时间戳相对值41bit- 数据中心标志5bit- 机器标志5bit- 递增序号12bit
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
* (0) - (0000000000 0000000000 0000000000 0000000000 0) - (00000) - (00000) - (000000000000)
* </pre>
* <p>
* 第一位为未使用(符号位表示正数)接下来的41位为毫秒级时间(41位的长度可以使用69年)<br>
@ -44,23 +45,20 @@ import java.util.Date;
* @author Looly
* @since 3.0.1
*/
public class Snowflake implements Serializable {
public class Snowflake implements Generator<Long>, Serializable {
private static final long serialVersionUID = 1L;
/**
* 默认的起始时间为Thu, 04 Nov 2010 01:42:54 GMT
*/
public static long DEFAULT_TWEPOCH = 1288834974657L;
private static final long WORKER_ID_BITS = 5L;
// 最大支持机器节点数0~31一共32个
@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
private static final long DATA_CENTER_ID_BITS = 5L;
// 最大支持数据中心节点数0~31一共32个
@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
private static final long MAX_DATA_CENTER_ID = -1L ^ (-1L << DATA_CENTER_ID_BITS);
// 序列号12位表示只允许workId的范围为0-4095
private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
// 序列号12位表示只允许序号的范围为0-4095
private static final long SEQUENCE_BITS = 12L;
// 机器节点左移12位
private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
@ -193,7 +191,8 @@ public class Snowflake implements Serializable {
*
* @return ID
*/
public synchronized long nextId() {
@Override
public synchronized Long next() {
long timestamp = genTime();
if (timestamp < this.lastTimestamp) {
timestamp = lastTimestamp;
@ -209,12 +208,8 @@ public class Snowflake implements Serializable {
}
this.sequence = sequence;
} else {
// issue#I51EJY
if (randomSequenceLimit > 1) {
sequence = RandomUtil.randomLong(randomSequenceLimit);
} else {
sequence = 0L;
}
// issue#I51EJY通过随机数避免低频生成ID序号始终为0的问题
sequence = randomSequenceLimit > 1 ? RandomUtil.randomLong(randomSequenceLimit) : 0L;
}
lastTimestamp = timestamp;
@ -230,8 +225,8 @@ public class Snowflake implements Serializable {
*
* @return ID 字符串形式
*/
public String nextIdStr() {
return Long.toString(nextId());
public String nextStr() {
return Long.toString(next());
}
/**

View File

@ -1,50 +0,0 @@
/*
* Copyright (c) 2023. 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.lang.generator.Generator;
/**
* Snowflake生成器<br>
* 注意默认此生成器必须单例使用否则会有重复<br>
* 默认构造的终端ID和数据中心ID都为0不适用于分布式环境
*
* @author looly
* @since 5.4.3
*/
public class SnowflakeGenerator implements Generator<Long> {
private final Snowflake snowflake;
/**
* 构造
*/
public SnowflakeGenerator() {
this(0, 0);
}
/**
* 构造
*
* @param workerId 终端ID
* @param dataCenterId 数据中心ID
*/
public SnowflakeGenerator(final long workerId, final long dataCenterId) {
snowflake = new Snowflake(workerId, dataCenterId);
}
@Override
public Long next() {
return this.snowflake.nextId();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Copyright (c) 2023. 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:
@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.lang;
package org.dromara.hutool.core.cache;
import org.dromara.hutool.core.cache.SimpleCache;
import org.dromara.hutool.core.thread.ConcurrencyTester;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Copyright (c) 2023. 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:
@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.lang;
package org.dromara.hutool.core.data.id;
import org.dromara.hutool.core.data.id.NanoId;
import org.junit.jupiter.api.Assertions;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Copyright (c) 2023. 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:
@ -10,9 +10,10 @@
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.lang;
package org.dromara.hutool.core.data.id;
import org.dromara.hutool.core.data.id.ObjectId;
import org.dromara.hutool.core.lang.Console;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Copyright (c) 2023. 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:
@ -10,12 +10,13 @@
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.lang;
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;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.thread.ThreadUtil;
@ -38,7 +39,7 @@ public class SnowflakeTest {
public void snowflakeTest1(){
//构建Snowflake提供终端ID和数据中心ID
final Snowflake idWorker = new Snowflake(0, 0);
final long nextId = idWorker.nextId();
final long nextId = idWorker.next();
Assertions.assertTrue(nextId > 0);
}
@ -49,7 +50,7 @@ public class SnowflakeTest {
//构建Snowflake提供终端ID和数据中心ID
final Snowflake idWorker = new Snowflake(0, 0);
for (int i = 0; i < 1000; i++) {
final long id = idWorker.nextId();
final long id = idWorker.next();
hashSet.add(id);
}
Assertions.assertEquals(1000L, hashSet.size());
@ -59,7 +60,7 @@ public class SnowflakeTest {
public void snowflakeGetTest(){
//构建Snowflake提供终端ID和数据中心ID
final Snowflake idWorker = new Snowflake(1, 2);
final long nextId = idWorker.nextId();
final long nextId = idWorker.next();
Assertions.assertEquals(1, idWorker.getWorkerId(nextId));
Assertions.assertEquals(2, idWorker.getDataCenterId(nextId));
@ -75,7 +76,7 @@ public class SnowflakeTest {
final Set<Long> ids = new ConcurrentHashSet<>();
ThreadUtil.concurrencyTest(100, () -> {
for (int i = 0; i < 50000; i++) {
if(!ids.add(snowflake.nextId())){
if(!ids.add(snowflake.next())){
throw new HutoolException("重复ID");
}
}
@ -85,7 +86,7 @@ public class SnowflakeTest {
@Test
public void getSnowflakeLengthTest(){
for (int i = 0; i < 1000; i++) {
final long l = IdUtil.getSnowflake(0, 0).nextId();
final long l = IdUtil.getSnowflake(0, 0).next();
Assertions.assertEquals(19, StrUtil.toString(l).length());
}
}
@ -96,7 +97,7 @@ public class SnowflakeTest {
final Snowflake snowflake = new Snowflake(null, 0, 0,
false, 2);
for (int i = 0; i < 1000; i++) {
final long id = snowflake.nextId();
final long id = snowflake.next();
Console.log(id);
ThreadUtil.sleep(10);
}
@ -112,7 +113,7 @@ public class SnowflakeTest {
final Set<Long> ids = new ConcurrentHashSet<>();
ThreadUtil.concurrencyTest(100, () -> {
for (int i = 0; i < 50000; i++) {
if(!ids.add(snowflake.nextId())){
if(!ids.add(snowflake.next())){
throw new HutoolException("重复ID");
}
}
@ -127,7 +128,7 @@ public class SnowflakeTest {
final long workerId = RandomUtil.randomLong(31);
final long dataCenterId = RandomUtil.randomLong(31);
final Snowflake idWorker = new Snowflake(workerId, dataCenterId);
final long generatedId = idWorker.nextId();
final long generatedId = idWorker.next();
// 随机忽略数据中心和工作机器的占位
final boolean ignore = RandomUtil.randomBoolean();
final long createTimestamp = idWorker.getGenerateDateTime(generatedId);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Copyright (c) 2023. 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:
@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.lang;
package org.dromara.hutool.core.data.id;
import org.dromara.hutool.core.collection.ConcurrentHashSet;
import org.dromara.hutool.core.data.id.UUID;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Copyright (c) 2023. 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:
@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.lang;
package org.dromara.hutool.core.lang.tuple;
import org.dromara.hutool.core.lang.tuple.Tuple;
import org.junit.jupiter.api.Assertions;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Copyright (c) 2023. 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:
@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.lang;
package org.dromara.hutool.core.map;
import org.dromara.hutool.core.lang.builder.GenericBuilder;
import org.dromara.hutool.core.date.DateTime;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Copyright (c) 2023. 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:
@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.lang;
package org.dromara.hutool.core.text;
import org.dromara.hutool.core.text.placeholder.StrFormatter;
import org.junit.jupiter.api.Assertions;

View File

@ -86,7 +86,7 @@ public class IdUtilTest {
@Test
public void getSnowflakeTest() {
final Snowflake snowflake = IdUtil.getSnowflake(1, 1);
final long id = snowflake.nextId();
final long id = snowflake.next();
Assertions.assertTrue(id > 0);
}
@ -104,7 +104,7 @@ public class IdUtilTest {
for(int i =0; i < threadCount; i++) {
ThreadUtil.execute(() -> {
for(int i1 = 0; i1 < idCountPerThread; i1++) {
final long id = snowflake.nextId();
final long id = snowflake.next();
set.add(id);
// Console.log("Add new id: {}", id);
}
@ -134,7 +134,7 @@ public class IdUtilTest {
for(int i =0; i < threadCount; i++) {
ThreadUtil.execute(() -> {
for(int i1 = 0; i1 < idCountPerThread; i1++) {
final long id = IdUtil.getSnowflake(1, 1).nextId();
final long id = IdUtil.getSnowflake(1, 1).next();
set.add(id);
// Console.log("Add new id: {}", id);
}