根据时间范围得出雪花算法(snowflake)ID的工具类-基于时间反推ID范围
前言
有一个数据迁移的小活:从备库提取需要的数据,历史数据量较大,别人的库还不方便加索引,好在主键都是 MyBatis-Plus 雪花算法生成的,因此我有一个 Big 胆的想法,查询一定时间范围内的数据,我只需要起始ID和结束 ID 之间
的数据即可。
根据时间范围反推 ID 范围,高效不迷路!!!
MyBatis-Plus 的雪花算法
废话不多说,贴上生产雪花 ID 的妈妈,如下:
/*
* Copyright (c) 2011-2022, baomidou (jobob@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.mybatisplus.core.toolkit;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.concurrent.ThreadLocalRandom;
/**
* 分布式高效有序 ID 生产黑科技(sequence)
*
* <p>优化开源项目:https://gitee.com/yu120/sequence</p>
*
* @author hubin
* @since 2016-08-18
*/
public class Sequence {
private static final Log logger = LogFactory.getLog(Sequence.class);
/**
* 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
*/
private static final long twepoch = 1288834974657L;
/**
* 机器标识位数
*/
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/**
* 毫秒内自增位
*/
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
/**
* 时间戳左移动位
*/
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private final long workerId;
/**
* 数据标识 ID 部分
*/
private final long datacenterId;
/**
* 并发控制
*/
private long sequence = 0L;
/**
* 上次生产 ID 时间戳
*/
private long lastTimestamp = -1L;
/**
* IP 地址
*/
private InetAddress inetAddress;
public Sequence(InetAddress inetAddress) {
this.inetAddress = inetAddress;
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
* 有参构造器
*
* @param workerId 工作机器 ID
* @param datacenterId 序列号
*/
public Sequence(long workerId, long datacenterId) {
Assert.isFalse(workerId > maxWorkerId || workerId < 0,
String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
Assert.isFalse(datacenterId > maxDatacenterId || datacenterId < 0,
String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获取 maxWorkerId
*/
protected long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuilder mpid = new StringBuilder();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (StringUtils.isNotBlank(name)) {
/*
* GET jvmPid
*/
mpid.append(name.split(StringPool.AT)[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
* 数据标识id部分
*/
protected long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
if (null == this.inetAddress) {
this.inetAddress = InetAddress.getLocalHost();
}
NetworkInterface network = NetworkInterface.getByInetAddress(this.inetAddress);
if (null == network) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
if (null != mac) {
id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
}
} catch (Exception e) {
logger.warn(" getDatacenterId: " + e.getMessage());
}
return id;
}
/**
* 获取下一个 ID
*
* @return 下一个 ID
*/
public synchronized long nextId() {
long timestamp = timeGen();
//闰秒
if (timestamp < lastTimestamp) {
long offset = lastTimestamp - timestamp;
if (offset <= 5) {
try {
wait(offset << 1);
timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
}
if (lastTimestamp == timestamp) {
// 相同毫秒内,序列号自增
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 同一毫秒的序列数已经达到最大
timestamp = tilNextMillis(lastTimestamp);
}
} else {
// 不同毫秒内,序列号置为 1 - 2 随机数
sequence = ThreadLocalRandom.current().nextLong(1, 3);
}
lastTimestamp = timestamp;
// 时间戳部分 | 数据中心部分 | 机器标识部分 | 序列号部分
return ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return SystemClock.now();
}
/**
* 反解id的时间戳部分
*/
public static long parseIdTimestamp(long id) {
return (id>>22)+twepoch;
}
}
时间范围生成最大ID和最小ID工具类
下面的参数,参考上面的雪花算法中的参数,必须一致
import com.baomidou.mybatisplus.core.toolkit.Sequence;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
/**
* 雪花算法id的时间范围工具类
* @author monkeyhi
*/
public class SequenceIdRangeUtil {
// 以下常量需与 Sequence 类定义一致
private static final long TWEPOCH = 1288834974657L; // 固定起始时间戳
private static final long WORKER_ID_BITS = 5L; // 机器ID位数
private static final long DATACENTER_ID_BITS = 5L; // 数据中心ID位数
private static final long SEQUENCE_BITS = 12L; // 序列号位数
private static final long TIMESTAMP_LEFT_SHIFT = 22; // 时间戳左移位数(12+5+5)
private static final long DATACENTER_ID_SHIFT = 17; // 数据中心ID左移位数(12+5)
private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; // 机器ID左移位数(12)
private static final long MAX_DATACENTER_ID = -1L ^ (-1L << WORKER_ID_BITS); // 5位最大值
private static final long MAX_WORKER_ID = -1L ^ (-1L << DATACENTER_ID_BITS); // 5位最大值
private static final long MAX_SEQUENCE = -1L ^ (-1L << WORKER_ID_SHIFT); // 12位最大值(0b111111111111)
/**
* 生成起始时间对应的最小ID(时间部分最小值,其他位全0)
*/
public static long genMinIdByStartDateTime(LocalDateTime startDateTime) {
long timestamp = startDateTime.atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli();
long timestampDelta = timestamp - TWEPOCH;
return timestampDelta << TIMESTAMP_LEFT_SHIFT;
}
/**
* 生成结束时间对应的最大ID(时间部分最大值,其他位全1)
*/
public static long genMaxIdByEndDateTime(LocalDateTime endDateTime) {
long timestamp = endDateTime.atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli();
long timestampDelta = timestamp - TWEPOCH;
// 时间戳部分 | 最大数据中心ID | 最大机器ID | 最大序列号
return (timestampDelta << TIMESTAMP_LEFT_SHIFT)
| (MAX_DATACENTER_ID << DATACENTER_ID_SHIFT)
| (MAX_WORKER_ID << WORKER_ID_SHIFT)
| MAX_SEQUENCE;
}
public static void main(String[] args) {
//2025-03-24 18:20:14 2025-03-24 18:20:13
LocalDateTime start = LocalDateTime.of(2025, 3, 24, 18, 20, 13);
LocalDateTime end = LocalDateTime.of(2025, 3, 24, 18, 20, LocalDateTime.MAX.getSecond());
// 生成ID范围
long minId = SequenceIdRangeUtil.genMinIdByStartDateTime(start);
long maxId = SequenceIdRangeUtil.genMaxIdByEndDateTime(end);
System.out.println("最小ID:" + minId);
System.out.println("最大ID:" + maxId);
System.out.println("参考ID:1904116032358764546L");
// 参考的id
long exampleId = Sequence.parseIdTimestamp(1904116032358764546L);
System.out.println("其时间戳:" + exampleId);
}
}
最终输出
最小ID:1904116030108598272
最大ID:1904116223050776575
参考ID:1904116032358764546
其时间戳:1742811613536 = 2025-03-24 18:20:13
最后,祝大家玩的愉快~~