001/* 002 * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.mybatisflex.core.keygen.impl; 017 018import com.mybatisflex.core.exception.FlexExceptions; 019import com.mybatisflex.core.keygen.IKeyGenerator; 020import com.mybatisflex.core.util.StringUtil; 021 022import java.lang.management.ManagementFactory; 023import java.net.InetAddress; 024import java.net.NetworkInterface; 025 026/** 027 * <p>雪花算法 ID 生成器。 028 * 029 * <ul> 030 * <li>最高 1 位固定值 0,因为生成的 ID 是正整数; 031 * <li>接下来 41 位存储毫秒级时间戳,2 ^ 41 / ( 1000 * 60 * 60 * 24 * 365) = 69,大概可以使用 69 年; 032 * <li>再接下 10 位存储机器码,包括 5 位 dataCenterId 和 5 位 workerId,最多可以部署 2 ^ 10 = 1024 台机器; 033 * <li>最后 12 位存储序列号,同一毫秒时间戳时,通过这个递增的序列号来区分,即对于同一台机器而言,同一毫秒时间戳下,可以生成 2 ^ 12 = 4096 个不重复 ID。 034 * </ul> 035 * 036 * <p>优化自开源项目:<a href="https://gitee.com/yu120/sequence">Sequence</a> 037 * 038 * @author 王帅 039 * @since 2023-05-12 040 */ 041public class SnowFlakeIDKeyGenerator implements IKeyGenerator { 042 043 /** 044 * 工作机器 ID 占用的位数(5bit)。 045 */ 046 private static final long WORKER_ID_BITS = 5L; 047 /** 048 * 数据中心 ID 占用的位数(5bit)。 049 */ 050 private static final long DATA_CENTER_ID_BITS = 5L; 051 /** 052 * 序号占用的位数(12bit)。 053 */ 054 private static final long SEQUENCE_BITS = 12L; 055 /** 056 * 工作机器 ID 占用 5bit 时的最大值 31。 057 */ 058 private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); 059 /** 060 * 数据中心 ID 占用 5bit 时的最大值 31。 061 */ 062 private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS); 063 /** 064 * 序号掩码,用于与自增后的序列号进行位“与”操作,如果值为 0,则代表自增后的序列号超过了 4095。 065 */ 066 private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); 067 /** 068 * 工作机器 ID 位需要左移的位数(12bit)。 069 */ 070 private static final long WORK_ID_SHIFT = SEQUENCE_BITS; 071 /** 072 * 数据中心 ID 位需要左移的位数(12bit + 5bit)。 073 */ 074 private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; 075 /** 076 * 时间戳需要左移的位数(12bit + 5bit + 5bit)。 077 */ 078 private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS; 079 /** 080 * 时间起始标记点,一旦确定不能变动(2023-04-02 13:01:00)。 081 */ 082 protected static long twepoch = 1680411660000L; 083 /** 084 * 可容忍的时间偏移量。 085 */ 086 protected static long offsetPeriod = 5L; 087 /** 088 * 工作机器 ID。 089 */ 090 private final long workerId; 091 /** 092 * 数据中心 ID。 093 */ 094 private final long dataCenterId; 095 /** 096 * IP 地址信息,用来生成工作机器 ID 和数据中心 ID。 097 */ 098 protected InetAddress address; 099 /** 100 * 同一毫秒内的最新序号,最大值可为(2^12 - 1 = 4095)。 101 */ 102 private long sequence; 103 /** 104 * 上次生产 ID 时间戳。 105 */ 106 private long lastTimeMillis = -1L; 107 108 /** 109 * 雪花算法 ID 生成器。 110 */ 111 public SnowFlakeIDKeyGenerator() { 112 this(null); 113 } 114 115 /** 116 * 根据 IP 地址计算数据中心 ID 和工作机器 ID 生成数据库 ID。 117 * 118 * @param address IP 地址 119 */ 120 public SnowFlakeIDKeyGenerator(InetAddress address) { 121 this.address = address; 122 this.dataCenterId = getDataCenterId(MAX_DATA_CENTER_ID); 123 this.workerId = getWorkerId(dataCenterId, MAX_WORKER_ID); 124 } 125 126 /** 127 * 根据数据中心 ID 和工作机器 ID 生成数据库 ID。 128 * 129 * @param workerId 工作机器 ID 130 * @param dataCenterId 数据中心 ID 131 */ 132 public SnowFlakeIDKeyGenerator(long workerId, long dataCenterId) { 133 if (workerId > MAX_WORKER_ID || workerId < 0) { 134 throw new IllegalArgumentException( 135 String.format("workerId must be greater than 0 and less than %d.", MAX_WORKER_ID)); 136 } 137 if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) { 138 throw new IllegalArgumentException( 139 String.format("dataCenterId must be greater than 0 and less than %d.", MAX_DATA_CENTER_ID)); 140 } 141 this.workerId = workerId; 142 this.dataCenterId = dataCenterId; 143 } 144 145 /** 146 * 根据 MAC + PID 的 hashCode 获取 16 个低位生成工作机器 ID。 147 */ 148 protected long getWorkerId(long dataCenterId, long maxWorkerId) { 149 StringBuilder mpId = new StringBuilder(); 150 mpId.append(dataCenterId); 151 String name = ManagementFactory.getRuntimeMXBean().getName(); 152 if (StringUtil.isNotBlank(name)) { 153 // GET jvmPid 154 mpId.append(name.split("@")[0]); 155 } 156 // MAC + PID 的 hashCode 获取16个低位 157 return (mpId.toString().hashCode() & 0xffff) % (maxWorkerId + 1); 158 } 159 160 /** 161 * 根据网卡 MAC 地址计算余数作为数据中心 ID。 162 */ 163 protected long getDataCenterId(long maxDataCenterId) { 164 long id = 0L; 165 try { 166 if (address == null) { 167 address = InetAddress.getLocalHost(); 168 } 169 NetworkInterface network = NetworkInterface.getByInetAddress(address); 170 if (null == network) { 171 id = 1L; 172 } else { 173 byte[] mac = network.getHardwareAddress(); 174 if (null != mac) { 175 id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; 176 id = id % (maxDataCenterId + 1); 177 } 178 } 179 } catch (Exception e) { 180 throw FlexExceptions.wrap(e, "dataCenterId: %s", e.getMessage()); 181 } 182 return id; 183 } 184 185 @Override 186 public Object generate(Object entity, String keyColumn) { 187 return nextId(); 188 } 189 190 /** 191 * 获取下一个 ID。 192 */ 193 public synchronized long nextId() { 194 long currentTimeMillis = System.currentTimeMillis(); 195 // 当前时间小于上一次生成 ID 使用的时间,可能出现服务器时钟回拨问题。 196 if (currentTimeMillis < lastTimeMillis) { 197 long offset = lastTimeMillis - currentTimeMillis; 198 // 在可容忍的时间差值之内等待时间恢复正常 199 if (offset <= offsetPeriod) { 200 try { 201 wait(offset << 1L); 202 currentTimeMillis = System.currentTimeMillis(); 203 if (currentTimeMillis < lastTimeMillis) { 204 throw FlexExceptions.wrap("Clock moved backwards, please check the time. Current timestamp: %d, last used timestamp: %d", currentTimeMillis, lastTimeMillis); 205 } 206 } catch (InterruptedException e) { 207 throw FlexExceptions.wrap(e); 208 } 209 } else { 210 throw FlexExceptions.wrap("Clock moved backwards, please check the time. Current timestamp: %d, last used timestamp: %d", currentTimeMillis, lastTimeMillis); 211 } 212 } 213 214 if (currentTimeMillis == lastTimeMillis) { 215 // 相同毫秒内,序列号自增 216 sequence = (sequence + 1) & SEQUENCE_MASK; 217 if (sequence == 0) { 218 // 同一毫秒的序列数已经达到最大 219 currentTimeMillis = tilNextMillis(lastTimeMillis); 220 } 221 } else { 222 // 不同毫秒内,序列号置为 0。 223 sequence = 0L; 224 } 225 226 // 记录最后一次使用的毫秒时间戳 227 lastTimeMillis = currentTimeMillis; 228 229 // 时间戳部分 | 数据中心部分 | 机器标识部分 | 序列号部分 230 return ((currentTimeMillis - twepoch) << TIMESTAMP_SHIFT) 231 | (dataCenterId << DATA_CENTER_ID_SHIFT) 232 | (workerId << WORK_ID_SHIFT) 233 | sequence; 234 } 235 236 /** 237 * 获取指定时间戳的接下来的时间戳。 238 */ 239 private long tilNextMillis(long lastTimestamp) { 240 long currentTimeMillis = System.currentTimeMillis(); 241 while (currentTimeMillis <= lastTimestamp) { 242 currentTimeMillis = System.currentTimeMillis(); 243 } 244 return currentTimeMillis; 245 } 246 247}