diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b6d9fa30..e8ab173e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.3 (2021-06-26) +# 5.7.3 (2021-06-28) ### 🐣新特性 * 【core 】 增加Convert.toSet方法(issue#I3XFG2@Gitee) @@ -14,6 +14,7 @@ * 【core 】 增加可自定义日期格式GlobalCustomFormat * 【jwt 】 JWT修改默认有序,并规定payload日期格式为秒数 * 【json 】 增加JSONWriter +* 【core 】 IdUtil增加getWorkerId和getDataCenterId(issueI3Y5NI@Gitee) ### 🐞Bug修复 * 【json 】 修复XML转义字符的问题(issue#I3XH09@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java b/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java index 8b4740296..e4a8c06fa 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java @@ -1,16 +1,16 @@ package cn.hutool.core.lang; -import java.lang.management.ManagementFactory; -import java.net.NetworkInterface; -import java.nio.ByteBuffer; -import java.util.Enumeration; -import java.util.concurrent.atomic.AtomicInteger; - import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.RuntimeUtil; import cn.hutool.core.util.StrUtil; +import java.net.NetworkInterface; +import java.nio.ByteBuffer; +import java.util.Enumeration; +import java.util.concurrent.atomic.AtomicInteger; + /** * MongoDB ID生成策略实现
* ObjectId由以下几部分组成: @@ -156,14 +156,7 @@ public class ObjectId { // 进程ID初始化 int processId; try { - // 获取进程ID - final String processName =ManagementFactory.getRuntimeMXBean().getName(); - final int atIndex = processName.indexOf('@'); - if (atIndex > 0) { - processId = Integer.parseInt(processName.substring(0, atIndex)); - } else { - processId = processName.hashCode(); - } + processId = RuntimeUtil.getPid(); } catch (Throwable t) { processId = RandomUtil.randomInt(); } @@ -180,4 +173,4 @@ public class ObjectId { return processPiece; } // ----------------------------------------------------------------------------------------- Private method end -} \ No newline at end of file +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java index 9183fe5d0..63f7897d9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java @@ -1,6 +1,7 @@ package cn.hutool.core.lang; import cn.hutool.core.date.SystemClock; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; @@ -14,6 +15,7 @@ import java.util.Date; * snowflake的结构如下(每部分用-分开):
* *
+ * 符号位(1bit)- 时间戳相对值(41bit)- 数据中心标志(5bit)- 机器标志(5bit)- 递增序号(12bit)
  * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
  * 
*

@@ -23,7 +25,8 @@ import java.util.Date; *

* 并且可以通过生成的id反推出生成时间,datacenterId和workerId *

- * 参考:http://www.cnblogs.com/relucent/p/4955340.html + * 参考:http://www.cnblogs.com/relucent/p/4955340.html
+ * 关于长度是18还是19的问题见:https://blog.csdn.net/unifirst/article/details/80408050 * * @author Looly * @since 3.0.1 @@ -31,33 +34,63 @@ import java.util.Date; public class Snowflake implements Serializable { private static final long serialVersionUID = 1L; - private final long twepoch; - private final long workerIdBits = 5L; + + /** + * 默认的起始时间,为Thu, 04 Nov 2010 01:42:54 GMT + */ + public static long DEFAULT_TWEPOCH = 1288834974657L; + /** + * 默认回拨时间,2S + */ + public static long DEFAULT_TIME_OFFSET = 2000L; + + private static final long WORKER_ID_BITS = 5L; // 最大支持机器节点数0~31,一共32个 @SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"}) - private final long maxWorkerId = -1L ^ (-1L << workerIdBits); - private final long dataCenterIdBits = 5L; + private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS); + private static final long DATA_CENTER_ID_BITS = 5L; // 最大支持数据中心节点数0~31,一共32个 @SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"}) - private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits); - // 序列号12位 - private final long sequenceBits = 12L; + private static final long MAX_DATA_CENTER_ID = -1L ^ (-1L << DATA_CENTER_ID_BITS); + // 序列号12位(表示只允许workId的范围为:0-4095) + private static final long SEQUENCE_BITS = 12L; // 机器节点左移12位 - private final long workerIdShift = sequenceBits; + private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; // 数据中心节点左移17位 - private final long dataCenterIdShift = sequenceBits + workerIdBits; + private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; // 时间毫秒数左移22位 - private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits; + private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS; // 序列掩码,用于限定序列最大值不能超过4095 @SuppressWarnings("FieldCanBeLocal") - private final long sequenceMask = ~(-1L << sequenceBits);// 4095 + private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);// 4095 + private final long twepoch; private final long workerId; private final long dataCenterId; private final boolean useSystemClock; + // 允许的时钟回拨数 + private final long timeOffset; + private long sequence = 0L; private long lastTimestamp = -1L; + /** + * 构造,使用自动生成的工作节点ID和数据中心ID + */ + public Snowflake() { + this(IdUtil.getWorkerId(IdUtil.getDataCenterId(MAX_DATA_CENTER_ID), MAX_WORKER_ID) + , IdUtil.getDataCenterId(MAX_DATA_CENTER_ID)); + } + + /** + * 构造 + * + * @param workerId 终端ID + */ + public Snowflake(long workerId) { + this(workerId, IdUtil.getDataCenterId(MAX_DATA_CENTER_ID)); + } + /** * 构造 * @@ -87,21 +120,34 @@ public class Snowflake implements Serializable { * @since 5.1.3 */ public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) { + this(epochDate, workerId, dataCenterId, isUseSystemClock, DEFAULT_TIME_OFFSET); + } + + /** + * @param epochDate 初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用 + * @param workerId 工作机器节点id + * @param dataCenterId 数据中心id + * @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳 + * @param timeOffset 允许时间回拨的毫秒数 + * @since 5.7.3 + */ + public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) { if (null != epochDate) { this.twepoch = epochDate.getTime(); } else{ // Thu, 04 Nov 2010 01:42:54 GMT - this.twepoch = 1288834974657L; + this.twepoch = DEFAULT_TWEPOCH; } - if (workerId > maxWorkerId || workerId < 0) { - throw new IllegalArgumentException(StrUtil.format("worker Id can't be greater than {} or less than 0", maxWorkerId)); + if (workerId > MAX_WORKER_ID || workerId < 0) { + throw new IllegalArgumentException(StrUtil.format("worker Id can't be greater than {} or less than 0", MAX_WORKER_ID)); } - if (dataCenterId > maxDataCenterId || dataCenterId < 0) { - throw new IllegalArgumentException(StrUtil.format("datacenter Id can't be greater than {} or less than 0", maxDataCenterId)); + if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) { + throw new IllegalArgumentException(StrUtil.format("datacenter Id can't be greater than {} or less than 0", MAX_DATA_CENTER_ID)); } this.workerId = workerId; this.dataCenterId = dataCenterId; this.useSystemClock = isUseSystemClock; + this.timeOffset = timeOffset; } /** @@ -111,7 +157,7 @@ public class Snowflake implements Serializable { * @return 所属机器的id */ public long getWorkerId(long id) { - return id >> workerIdShift & ~(-1L << workerIdBits); + return id >> WORKER_ID_SHIFT & ~(-1L << WORKER_ID_BITS); } /** @@ -121,7 +167,7 @@ public class Snowflake implements Serializable { * @return 所属数据中心 */ public long getDataCenterId(long id) { - return id >> dataCenterIdShift & ~(-1L << dataCenterIdBits); + return id >> DATA_CENTER_ID_SHIFT & ~(-1L << DATA_CENTER_ID_BITS); } /** @@ -131,7 +177,7 @@ public class Snowflake implements Serializable { * @return 生成的时间 */ public long getGenerateDateTime(long id) { - return (id >> timestampLeftShift & ~(-1L << 41L)) + twepoch; + return (id >> TIMESTAMP_LEFT_SHIFT & ~(-1L << 41L)) + twepoch; } /** @@ -142,8 +188,8 @@ public class Snowflake implements Serializable { public synchronized long nextId() { long timestamp = genTime(); if (timestamp < this.lastTimestamp) { - if(this.lastTimestamp - timestamp < 2000){ - // 容忍2秒内的回拨,避免NTP校时造成的异常 + if(this.lastTimestamp - timestamp < timeOffset){ + // 容忍指定的回拨,避免NTP校时造成的异常 timestamp = lastTimestamp; } else{ // 如果服务器时间有问题(时钟后退) 报错。 @@ -152,7 +198,7 @@ public class Snowflake implements Serializable { } if (timestamp == this.lastTimestamp) { - final long sequence = (this.sequence + 1) & sequenceMask; + final long sequence = (this.sequence + 1) & SEQUENCE_MASK; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } @@ -163,7 +209,10 @@ public class Snowflake implements Serializable { lastTimestamp = timestamp; - return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence; + return ((timestamp - twepoch) << TIMESTAMP_LEFT_SHIFT) + | (dataCenterId << DATA_CENTER_ID_SHIFT) + | (workerId << WORKER_ID_SHIFT) + | sequence; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java index cc137f926..03e876c8d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java @@ -544,15 +544,7 @@ public class NetUtil { return null; } - byte[] mac = null; - try { - final NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress); - if (null != networkInterface) { - mac = networkInterface.getHardwareAddress(); - } - } catch (SocketException e) { - throw new UtilException(e); - } + final byte[] mac = getHardwareAddress(inetAddress); if (null != mac) { final StringBuilder sb = new StringBuilder(); String s; @@ -570,6 +562,39 @@ public class NetUtil { return null; } + /** + * 获得本机物理地址 + * + * @return 本机物理地址 + * @since 5.7.3 + */ + public static byte[] getLocalHardwareAddress() { + return getHardwareAddress(getLocalhost()); + } + + /** + * 获得指定地址信息中的硬件地址 + * + * @param inetAddress {@link InetAddress} + * @return 硬件地址 + * @since 5.7.3 + */ + public static byte[] getHardwareAddress(InetAddress inetAddress) { + if (null == inetAddress) { + return null; + } + + try { + final NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress); + if (null != networkInterface) { + return networkInterface.getHardwareAddress(); + } + } catch (SocketException e) { + throw new UtilException(e); + } + return null; + } + /** * 获取主机名称,一次获取会缓存名称 * diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java index ee31a7fa0..b83944e75 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java @@ -1,9 +1,11 @@ package cn.hutool.core.util; +import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.ObjectId; import cn.hutool.core.lang.Singleton; import cn.hutool.core.lang.Snowflake; import cn.hutool.core.lang.UUID; +import cn.hutool.core.net.NetUtil; /** * ID生成器工具类,此工具类中主要封装: @@ -134,4 +136,53 @@ public class IdUtil { public static Snowflake getSnowflake(long workerId, long datacenterId) { return Singleton.get(Snowflake.class, workerId, datacenterId); } + + /** + * 获取数据中心ID
+ * 数据中心ID依赖于本地网卡MAC地址。 + *

+ * 此算法来自于mybatis-plus#Sequence + *

+ * + * @param maxDatacenterId 最大的中心ID + * @return 数据中心ID + * @since 5.7.3 + */ + public static long getDataCenterId(long maxDatacenterId) { + long id = 1L; + final byte[] mac = NetUtil.getLocalHardwareAddress(); + if (null != mac) { + id = ((0x000000FF & (long) mac[mac.length - 2]) + | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; + id = id % (maxDatacenterId + 1); + } + + return id; + } + + /** + * 获取机器ID,使用进程ID配合数据中心ID生成
+ * 机器依赖于本进程ID或进程名的Hash值。 + * + *

+ * 此算法来自于mybatis-plus#Sequence + *

+ * + * @param datacenterId 数据中心ID + * @param maxWorkerId 最大的机器节点ID + * @since 5.7.3 + */ + public static long getWorkerId(long datacenterId, long maxWorkerId) { + final StringBuilder mpid = new StringBuilder(); + mpid.append(datacenterId); + try { + mpid.append(RuntimeUtil.getPid()); + } catch (UtilException igonre) { + //ignore + } + /* + * MAC + PID 的 hashcode 获取16个低位 + */ + return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index 4cedf7426..d5fc4c9f5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.StrBuilder; @@ -7,6 +8,7 @@ import cn.hutool.core.text.StrBuilder; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.management.ManagementFactory; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -279,12 +281,33 @@ public class RuntimeUtil { return getMaxMemory() - getTotalMemory() + getFreeMemory(); } + /** + * 获取当前进程ID,首先获取进程名称,读取@前的ID值,如果不存在,则读取进程名的hash值 + * + * @return 进程ID + * @throws UtilException 进程名称为空 + * @since 5.7.3 + */ + public static int getPid() throws UtilException { + final String processName = ManagementFactory.getRuntimeMXBean().getName(); + if (StrUtil.isBlank(processName)) { + throw new UtilException("Process name is blank!"); + } + final int atIndex = processName.indexOf('@'); + if (atIndex > 0) { + return Integer.parseInt(processName.substring(0, atIndex)); + } else { + return processName.hashCode(); + } + } + /** * 处理命令,多行命令原样返回,单行命令拆分处理 + * * @param cmds 命令 * @return 处理后的命令 */ - private static String[] handleCmds(String... cmds){ + private static String[] handleCmds(String... cmds) { if (ArrayUtil.isEmpty(cmds)) { throw new NullPointerException("Command is empty !"); } @@ -306,7 +329,7 @@ public class RuntimeUtil { * @param cmd 命令,如 git commit -m 'test commit' * @return 分割后的命令 */ - private static String[] cmdSplit(String cmd){ + private static String[] cmdSplit(String cmd) { final List cmds = new ArrayList<>(); final int length = cmd.length(); @@ -317,27 +340,27 @@ public class RuntimeUtil { char c; for (int i = 0; i < length; i++) { c = cmd.charAt(i); - switch (c){ + switch (c) { case CharUtil.SINGLE_QUOTE: case CharUtil.DOUBLE_QUOTES: - if(inWrap){ - if(c == stack.peek()){ + if (inWrap) { + if (c == stack.peek()) { //结束包装 stack.pop(); inWrap = false; } cache.append(c); - } else{ + } else { stack.push(c); cache.append(c); inWrap = true; } break; case CharUtil.SPACE: - if(inWrap){ + if (inWrap) { // 处于包装内 cache.append(c); - } else{ + } else { cmds.add(cache.toString()); cache.reset(); } @@ -348,7 +371,7 @@ public class RuntimeUtil { } } - if(cache.hasContent()){ + if (cache.hasContent()) { cmds.add(cache.toString()); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java index ba4eba07b..bded322ae 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java @@ -134,4 +134,10 @@ public class IdUtilTest { } Assert.assertEquals(threadCount * idCountPerThread, set.size()); } + + @Test + public void getDataCenterIdTest(){ + final long dataCenterId = IdUtil.getDataCenterId(Long.MAX_VALUE); + Assert.assertTrue(dataCenterId > 1); + } }