![JAR search and dependency download from the Maven repository](/logo.png)
io.geewit.snowflake.impl.DefaultUidGenerator Maven / Gradle / Ivy
package io.geewit.snowflake.impl;
import io.geewit.snowflake.BitsAllocator;
import io.geewit.snowflake.UidGenerator;
import io.geewit.snowflake.exception.UidGenerateException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Represents an implementation of {@link UidGenerator}
*
* The unique id has 64bits (long), default allocated as blow:
*
sign: The highest bit is 0
* delta seconds: The next 28 bits, represents delta seconds since a customer epoch(2016-05-20 00:00:00.000).
* Supports about 8.7 years until to 2024-11-20 21:24:16
* worker id: The next 22 bits, represents the worker's id which assigns based on database, max id is about 420W
* sequence: The next 13 bits, represents a sequence within the same second, max for 8192/s
*
* The {@link DefaultUidGenerator#parseUID(long)} is a tool method to parse the bits
*
*
{@code
* +------+----------------------+----------------+-----------+
* | sign | delta seconds | worker node id | sequence |
* +------+----------------------+----------------+-----------+
* 1bit 28bits 22bits 13bits
* }
*
* You can also specified the bits by Spring property setting.
*
timeBits: default as 28
* workerBits: default as 22
* seqBits: default as 13
* epochStr: Epoch date string format 'yyyy-MM-dd'. Default as '2016-05-20'
*
* Note that: The total bits must be 64 -1
*
* @author geewit
*/
public class DefaultUidGenerator implements UidGenerator {
private static final Logger logger = LoggerFactory.getLogger(DefaultUidGenerator.class);
/**
* Bits allocate
*/
protected int timeBits = 28;
protected int workerBits = 22;
protected int seqBits = 13;
/**
* Customer epoch, unit as second. For example 2016-05-20 (ms: 1463673600000)
*/
protected String epochStr = "2016-05-20";
protected long epochSeconds = TimeUnit.MILLISECONDS.toSeconds(1463673600000L);
/**
* Stable fields after spring bean initializing
*/
protected BitsAllocator bitsAllocator;
protected long workerId;
/**
* Volatile fields caused by nextId()
*/
protected long sequence = 0L;
protected long lastSecond = -1L;
public DefaultUidGenerator(long workerId) {
// initialize bits allocator
bitsAllocator = new BitsAllocator(timeBits, workerBits, seqBits);
// initialize worker id
this.workerId = workerId;
if (this.workerId > bitsAllocator.getMaxWorkerId()) {
this.workerId = this.workerId & bitsAllocator.getMaxWorkerId();
}
logger.info("Initialized bits(1, {}, {}, {}) for workerID:{}", this.timeBits, this.workerBits, this.seqBits, this.workerId);
}
@Override
public long getUID() throws UidGenerateException {
try {
return this.nextId();
} catch (Exception e) {
logger.error("Generate unique id exception.", e);
throw new UidGenerateException(e);
}
}
@Override
public String parseUID(long uid) {
long totalBits = BitsAllocator.TOTAL_BITS;
long signBits = bitsAllocator.getSignBits();
long timestampBits = bitsAllocator.getTimestampBits();
long workerIdBits = bitsAllocator.getWorkerIdBits();
long sequenceBits = bitsAllocator.getSequenceBits();
// parse UID
long sequence = (uid << (totalBits - sequenceBits)) >>> (totalBits - sequenceBits);
long workerId = (uid << (timestampBits + signBits)) >>> (totalBits - workerIdBits);
long deltaSeconds = uid >>> (workerIdBits + sequenceBits);
Date thatTime = new Date(TimeUnit.SECONDS.toMillis(epochSeconds + deltaSeconds));
String thatTimeStr = String.format("%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS", thatTime);
// format as string
return String.format("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"workerId\":\"%d\",\"sequence\":\"%d\"}",
uid, thatTimeStr, workerId, sequence);
}
/**
* Get UID
*
* @return UID
* @throws UidGenerateException in the case: Clock moved backwards; Exceeds the max timestamp
*/
protected synchronized long nextId() {
long currentSecond = this.getCurrentSecond();
// Clock moved backwards, refuse to generate uid
if (currentSecond < lastSecond) {
long refusedSeconds = lastSecond - currentSecond;
throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds);
}
// At the same second, increase sequence
if (currentSecond == lastSecond) {
sequence = (sequence + 1) & bitsAllocator.getMaxSequence();
// Exceed the max sequence, we wait the next second to generate uid
if (sequence == 0) {
currentSecond = this.getNextSecond(lastSecond);
}
// At the different second, sequence restart from zero
} else {
sequence = 0L;
}
lastSecond = currentSecond;
// Allocate bits for UID
return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence);
}
/**
* Get next millisecond
*/
private long getNextSecond(long lastTimestamp) {
long timestamp = this.getCurrentSecond();
while (timestamp <= lastTimestamp) {
timestamp = this.getCurrentSecond();
}
return timestamp;
}
/**
* Get current second
*/
private long getCurrentSecond() {
long currentSecond = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
if (currentSecond - epochSeconds > bitsAllocator.getMaxDeltaSeconds()) {
throw new UidGenerateException("Timestamp bits is exhausted. Refusing UID generate. Now: " + currentSecond);
}
return currentSecond;
}
public void setTimeBits(int timeBits) {
if (timeBits > 0) {
this.timeBits = timeBits;
}
}
public void setWorkerBits(int workerBits) {
if (workerBits > 0) {
this.workerBits = workerBits;
}
}
public void setSeqBits(int seqBits) {
if (seqBits > 0) {
this.seqBits = seqBits;
}
}
public void setEpochStr(String epochStr) {
if (StringUtils.isNotBlank(epochStr)) {
this.epochStr = epochStr;
Date epochDate;
try {
epochDate = DateUtils.parseDate(epochStr, "yyyy-MM-dd");
this.epochSeconds = TimeUnit.MILLISECONDS.toSeconds(epochDate.getTime());
} catch (ParseException ignored) {
}
}
}
}