Created
July 23, 2025 09:55
-
-
Save AlexMAS/165d3ad3706c1806ce4b2eecaff0769c to your computer and use it in GitHub Desktop.
How to create a unique integer sequence in a distributed system by using the Snowflake ID.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.time.Instant; | |
import static java.lang.System.currentTimeMillis; | |
public class SnowflakeID { | |
private static final byte DATACENTER_ID_BITS = 5; | |
private static final byte MACHINE_ID_BITS = 5; | |
private static final byte SEQUENCE_BITS = 12; | |
private static final byte TIMESTAMP_SHIFT = DATACENTER_ID_BITS + MACHINE_ID_BITS + SEQUENCE_BITS; | |
private static final byte DATACENTER_ID_SHIFT = MACHINE_ID_BITS + SEQUENCE_BITS; | |
private static final byte MACHINE_ID_SHIFT = SEQUENCE_BITS; | |
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS); | |
private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS); | |
private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); | |
private final long epochMillis; | |
private final long datacenterId; | |
private final long machineId; | |
private volatile long lastTimestamp; | |
private volatile long lastSequence; | |
public SnowflakeID(Instant epoch, short datacenterId, short machineId) { | |
if (datacenterId < 0 || datacenterId > MAX_DATACENTER_ID) { | |
throw new IllegalArgumentException(String.format("DatacenterID must be between 0 and %d.", MAX_DATACENTER_ID)); | |
} | |
if (machineId < 0 || machineId > MAX_MACHINE_ID) { | |
throw new IllegalArgumentException(String.format("MachineID must be between 0 and %d.", MAX_MACHINE_ID)); | |
} | |
this.epochMillis = epoch.toEpochMilli(); | |
this.datacenterId = ((long) datacenterId << DATACENTER_ID_SHIFT); | |
this.machineId = ((long) machineId << MACHINE_ID_SHIFT); | |
this.lastTimestamp = -1; | |
this.lastSequence = -1; | |
} | |
public long next() { | |
var timestamp = currentTimeMillis(); | |
if (timestamp < lastTimestamp) { | |
throw new IllegalStateException(String.format("Clock moved backwards. Refusing to generate ID for %d milliseconds.", | |
lastTimestamp - timestamp)); | |
} | |
long sequence; | |
synchronized (this) { | |
if (timestamp == lastTimestamp) { | |
sequence = (lastSequence + 1) & SEQUENCE_MASK; | |
// Sequence overflow | |
if (sequence == 0) { | |
timestamp = waitForNextMillis(timestamp); | |
} | |
} else { | |
sequence = 0; | |
} | |
lastTimestamp = timestamp; | |
lastSequence = sequence; | |
} | |
return ((timestamp - epochMillis) << TIMESTAMP_SHIFT) | |
| datacenterId | |
| machineId | |
| sequence; | |
} | |
public Instant decodeTimestamp(long snowflakeId) { | |
return Instant.ofEpochMilli((snowflakeId >>> TIMESTAMP_SHIFT) + epochMillis); | |
} | |
public short decodeDatacenterId(long snowflakeId) { | |
return (short) ((snowflakeId >>> DATACENTER_ID_SHIFT) & MAX_DATACENTER_ID); | |
} | |
public short decodeMachineId(long snowflakeId) { | |
return (short) ((snowflakeId >>> MACHINE_ID_SHIFT) & MAX_MACHINE_ID); | |
} | |
private static long waitForNextMillis(long lastTimestamp) { | |
var timestamp = currentTimeMillis(); | |
while (timestamp <= lastTimestamp) { | |
timestamp = currentTimeMillis(); | |
} | |
return timestamp; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage example: