
com.evento.common.utils.Snowflake Maven / Gradle / Ivy
Show all versions of evento-common Show documentation
package com.evento.common.utils;
import java.net.NetworkInterface;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Enumeration;
/**
* Distributed Sequence Generator. Inspired by Twitter snowflake:
* ...
*
* This class should be used as a Singleton. Make sure that you create and reuse a Single instance of Snowflake per node
* in your distributed system cluster.
*/
public class Snowflake {
private static final int UNUSED_BITS = 1; // Sign bit, Unused (always set to 0)
private static final int EPOCH_BITS = 41;
private static final int NODE_ID_BITS = 10;
private static final int SEQUENCE_BITS = 12;
private static final long maxNodeId = (1L << NODE_ID_BITS) - 1;
private static final long maxSequence = (1L << SEQUENCE_BITS) - 1;
private static final long DEFAULT_CUSTOM_EPOCH = 1420070400000L;
private final long nodeId;
private final long customEpoch;
private volatile long lastTimestamp = -1L;
private volatile long sequence = 0L;
/**
* Snowflake is a class that represents a unique identifier generator based on Twitter's snowflake algorithm.
* @param nodeId a node id to generate the snowflake
* @param customEpoch starting epoch
*/
// Create Snowflake with a nodeId and custom epoch
public Snowflake(long nodeId, long customEpoch) {
if (nodeId < 0 || nodeId > maxNodeId)
{
throw new IllegalArgumentException(String.format("NodeId must be between %d and %d", 0, maxNodeId));
}
this.nodeId = nodeId;
this.customEpoch = customEpoch;
}
/**
* Snowflake is a class that represents a unique identifier generator based on Twitter's snowflake algorithm.
*
* @param nodeId The nodeId used to generate the snowflake identifier.
*/
// Create Snowflake with a nodeId
public Snowflake(long nodeId) {
this(nodeId, DEFAULT_CUSTOM_EPOCH);
}
/**
* Snowflake is a class that represents a unique identifier generator based on Twitter's snowflake algorithm.
*/
// Let Snowflake generate a nodeId
public Snowflake() {
this.nodeId = createNodeId();
this.customEpoch = DEFAULT_CUSTOM_EPOCH;
}
/**
* Generates a unique identifier based on Twitter's snowflake algorithm.
* The identifier consists of a timestamp, a node ID, and a sequence number,
* which are combined to form a 64-bit long value.
* @return the next Snowflake ID
*/
public synchronized long nextId() {
long currentTimestamp = timestamp();
if (currentTimestamp < lastTimestamp)
{
throw new IllegalStateException("Invalid System Clock!");
}
if (currentTimestamp == lastTimestamp)
{
sequence = (sequence + 1) & maxSequence;
if (sequence == 0)
{
// Sequence Exhausted, wait till next millisecond.
currentTimestamp = waitNextMillis(currentTimestamp);
}
} else
{
// reset sequence to start with zero for the next millisecond
sequence = 0;
}
lastTimestamp = currentTimestamp;
return currentTimestamp << (NODE_ID_BITS + SEQUENCE_BITS)
| (nodeId << SEQUENCE_BITS)
| sequence;
}
// Get current timestamp in milliseconds, adjust for the custom epoch.
private long timestamp() {
return Instant.now().toEpochMilli() - customEpoch;
}
// Block and wait till next millisecond
private long waitNextMillis(long currentTimestamp) {
while (currentTimestamp == lastTimestamp)
{
currentTimestamp = timestamp();
}
return currentTimestamp;
}
private long createNodeId() {
long nodeId;
try
{
StringBuilder sb = new StringBuilder();
Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements())
{
NetworkInterface networkInterface = networkInterfaces.nextElement();
byte[] mac = networkInterface.getHardwareAddress();
if (mac != null)
{
for (byte macPort : mac)
{
sb.append(String.format("%02X", macPort));
}
}
}
nodeId = sb.toString().hashCode();
} catch (Exception ex)
{
nodeId = (new SecureRandom().nextInt());
}
nodeId = nodeId & maxNodeId;
return nodeId;
}
/**
* Parses a unique identifier generated by the Snowflake class.
* The identifier is a 64-bit long value consisting of a timestamp, a node ID, and a sequence number.
*
* @param id The unique identifier to parse.
* @return An array of long values [timestamp, nodeId, sequence].
*/
public long[] parse(long id) {
long maskNodeId = ((1L << NODE_ID_BITS) - 1) << SEQUENCE_BITS;
long maskSequence = (1L << SEQUENCE_BITS) - 1;
long timestamp = (id >> (NODE_ID_BITS + SEQUENCE_BITS)) + customEpoch;
long nodeId = (id & maskNodeId) >> SEQUENCE_BITS;
long sequence = id & maskSequence;
return new long[]{timestamp, nodeId, sequence};
}
@Override
public String toString() {
return "Snowflake Settings [EPOCH_BITS=" + EPOCH_BITS + ", NODE_ID_BITS=" + NODE_ID_BITS
+ ", SEQUENCE_BITS=" + SEQUENCE_BITS + ", CUSTOM_EPOCH=" + customEpoch
+ ", NodeId=" + nodeId + "]";
}
/**
* The forInstant method in the Snowflake class generates a unique identifier based on Twitter's snowflake algorithm.
* The identifier consists of a timestamp, a node ID, and a sequence number, which are combined to form a 64-bit long value.
*
* @param minus The Instant object representing the time to subtract from the current time to generate the timestamp.
* @return The generated unique identifier as a Long value.
*/
public Long forInstant(Instant minus) {
var timestamp = minus.toEpochMilli() - customEpoch;
return timestamp << (NODE_ID_BITS + SEQUENCE_BITS)
| (0L);
}
}