package cn.demo.common;
/**
* Snowflake Algorithm ID Generator
* What is generated is a thread-safe incremental ID, which will not be repeated in the distributed system.
*/
public class IdWorker {
/**
* Start time, the time when the program starts to be used
*/
private final long twepoch = 1614316099758L;
/**
* The number of bits occupied by the machine id
*/
private final long workerIdBits = 5L;
/**
* Number of bits occupied by the data identification id
*/
private final long datacenterIdBits = 5L;
/**
* The maximum machine id supported, the result is 31 (this shift algorithm can quickly calculate the maximum decimal number that several binary numbers can represent)
*/
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/**
* The maximum data identification id supported, the result is 31
*/
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/**
* The number of digits in the id
*/
private final long sequenceBits = 12L;
/**
* The machine ID is shifted to the left by 12 bits
*/
private final long workerIdShift = sequenceBits;
/**
* The data identification id is shifted to the left by 17 bits (12+5)
*/
private final long datacenterIdShift = sequenceBits + workerIdBits;
/**
* Time cut-off shifts 22 bits to the left (5+5+12)
*/
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/**
* The mask of the generated sequence is 4095 (0b11111111111=0xfff=4095)
*/
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/**
* Working machine ID (0~31)
*/
private long workerId;
/**
* Data Center ID (0~31)
*/
private long datacenterId;
/**
* Sequence within milliseconds (0~4095)
*/
private long sequence = 0L;
/**
* The time of the last ID generated
*/
private long lastTimestamp = -1L;
/**
* Constructor
*
* @param workerId Work ID (0~31)
* @param datacenterId DatacenterID (0~31)
*/
private IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* Get the next ID (this method is thread-safe)
*
* @return long
*/
private synchronized long nextId() {
long timestamp = timeGen();
timestamp = generateId(timestamp);
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
}
private long generateId(long timestamp) {
//If the current time is less than the timestamp generated by the last ID, it means that an exception should be thrown when the system clock has fallen back after this time.
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//If it is generated at the same time, a sequence within milliseconds will be performed
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//Sequence overflow within milliseconds
if (sequence == 0)
//Block to the next millisecond to get a new time stamp
{
timestamp = tilNextMillis(lastTimestamp);
}
} else//The timestamp changes, the sequence is reset within milliseconds
{
sequence = 0L;
}
//The time of the last ID generated
lastTimestamp = timestamp;
return timestamp;
}
/**
* Get the next ID (string)
**/
private synchronized String generateNextId() {
long timestamp = timeGen();
timestamp = generateId(timestamp);
// Shift and use or compute to form a 64-bit ID
return String.valueOf(((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence);
}
/**
* Block to the next millisecond until a new timestamp is obtained
*
* @param lastTimestamp The time cutoff of the last ID generated
* @return Current timestamp
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* Returns the current time in milliseconds
*
* @return Current time (milliseconds)
*/
private long timeGen() {
return System.currentTimeMillis();
}
//The construction parameters of idWorker single-case, multiple cases or distributed systems cannot be the same as those of other instances, otherwise security problems will occur
private static IdWorker idWorker = new IdWorker(0L, 0L);
/**
* Disclosed method to generate an ID
* @return
*/
public static long getNextId() {
return idWorker.nextId();
}
}