Additional documentation and bounding checks

This commit is contained in:
2024-05-15 19:36:23 +02:00
parent 7ada4e6012
commit c52d80ff6a

View File

@@ -9,8 +9,9 @@ import java.util.Objects;
import static java.lang.System.currentTimeMillis; import static java.lang.System.currentTimeMillis;
/** /**
* Generates the SnowflakeIDs. Must be initialized with a machine ID between 0 and 1023. * Generates the SnowflakeIDs. Must be initialized with a machine ID between 0 and 1023.<br>
* Can generate up to 4096 IDs per millisecond. If this limit is hit, generation will be halted until the next millisecond. * Can generate up to 4096 IDs per millisecond. If this limit is hit, generation will be halted until the next millisecond.<br>
* Snowflakes always have a max size of 63 bits and can therefore be saved as a {@code long} (64-bit integer).
* *
* @author Alix von Schirp * @author Alix von Schirp
* @since 1.0.0 * @since 1.0.0
@@ -37,7 +38,11 @@ public class SnowflakeIDGenerator {
* Default is 12. * Default is 12.
*/ */
private final long SEQUENCE_BITS; private final long SEQUENCE_BITS;
/**
* Amount of bits reserved for timestamp.
* Default is 41.
*/
private final long TIMESTAMP_BITS;
/** /**
* The calculated maximum machine ID. * The calculated maximum machine ID.
* Defaults to 1023. * Defaults to 1023.
@@ -48,7 +53,11 @@ public class SnowflakeIDGenerator {
* Defaults to 4095. * Defaults to 4095.
*/ */
private final long MAX_SEQUENCE; private final long MAX_SEQUENCE;
/**
* The calculated maximum timestamp.
* Defaults to 2199023255551, which gives roughly 69 years.
*/
private final long MAX_TIMESTAMP;
/** /**
* How far to shift the machine ID (timestamp + machine + <b><i>sequence</i></b>) * How far to shift the machine ID (timestamp + machine + <b><i>sequence</i></b>)
*/ */
@@ -72,17 +81,16 @@ public class SnowflakeIDGenerator {
*/ */
private long lastTimestamp = -1L; private long lastTimestamp = -1L;
// Constructor
/** /**
* Used for initiating constants and state and performing bound checks on {@code EPOCH}, {@code MAX_TIMESTAMP} and {@code machineId}.
* *
* * @param machineId The machineID used for generation // expected to be null if not initialized by orchestrator, loaded from config
* @param machineId The machineID used for generation * @param epoch The snowflake epoch used for generation // expected to be null if not initialized by orchestrator, loaded from config
* @param epoch The snowflake epoch used for generation * @param machineIdBits The amount of bits used for the machineID // expected to be null if not initialized by orchestrator, loaded from config
* @param machineIdBits The amount of bits used for the machineID * @param sequenceBits The amount of bits used for sequence counter // expected to be null if not initialized by orchestrator, loaded from config
* @param sequenceBits The amount of bits used for sequence counter * @throws IllegalArgumentException if any bound checks fail.
*/ */
private SnowflakeIDGenerator(long machineId, @Nullable Long epoch, @Nullable Long machineIdBits, @Nullable Long sequenceBits) { private SnowflakeIDGenerator(@Nullable Long machineId, @Nullable Long epoch, @Nullable Long machineIdBits, @Nullable Long sequenceBits) throws IllegalArgumentException{
//init constants //init constants
MACHINE_ID_BITS = Objects.requireNonNullElse(machineIdBits, MACHINE_ID_BITS = Objects.requireNonNullElse(machineIdBits,
Long.parseLong(Configuration.getInstance().get(ConfigurationValues.MACHINE_ID_BITS))); Long.parseLong(Configuration.getInstance().get(ConfigurationValues.MACHINE_ID_BITS)));
@@ -92,23 +100,63 @@ public class SnowflakeIDGenerator {
Long.parseLong(Configuration.getInstance().get(ConfigurationValues.SEQUENCE_BITS))); Long.parseLong(Configuration.getInstance().get(ConfigurationValues.SEQUENCE_BITS)));
MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1; MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;
TIMESTAMP_BITS = 63L - MACHINE_ID_BITS - SEQUENCE_BITS;
MAX_TIMESTAMP = (1L << TIMESTAMP_BITS) -1;
EPOCH = Objects.requireNonNullElse(epoch, EPOCH = Objects.requireNonNullElse(epoch,
Long.parseLong(Configuration.getInstance().get(ConfigurationValues.EPOCH))); Long.parseLong(Configuration.getInstance().get(ConfigurationValues.EPOCH)));
MACHINE_ID_SHIFT = SEQUENCE_BITS; MACHINE_ID_SHIFT = SEQUENCE_BITS;
TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS; TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
//init state
this.machineId = Objects.requireNonNullElse(machineId,
Long.parseLong(Configuration.getInstance().get(ConfigurationValues.MACHINE_ID)));
//Bound checks
if(EPOCH > currentTimeMillis()){ if(EPOCH > currentTimeMillis()){
throw new IllegalArgumentException("Epoch may not be in the future."); throw new IllegalArgumentException("Epoch may not be in the future.");
} }
if(EPOCH+MAX_TIMESTAMP <= currentTimeMillis()){
if (machineId < 0 || machineId > MAX_MACHINE_ID) { throw new IllegalArgumentException("Latest possible timestamp may not be in the past.");
}
if (this.machineId < 0 || this.machineId > MAX_MACHINE_ID) {
throw new IllegalArgumentException("Machine ID must be between 0 and " + MAX_MACHINE_ID); throw new IllegalArgumentException("Machine ID must be between 0 and " + MAX_MACHINE_ID);
} }
this.machineId = machineId;
} }
// Method to generate a new ID /**
* Generates a new snowflake ID.<br>
* It uses a long (64-bit signed int) made up of (in default configuration):
* <table>
* <tr>
* <th>bit(s)</th>
* <th>content</th>
* </tr>
* <tr>
* <td>0</td>
* <td>0 (signed positive)</td>
* </tr>
* <tr>
* <td>1-41</td>
* <td>timestamp (<code>current unix time</code> - <code>epoch</code>)</td>
* </tr>
* <tr>
* <td>42-51</td>
* <td>machineID</td>
* </tr>
* <tr>
* <td>52-63</td>
* <td>sequence</td>
* </tr>
* </table>
* Generation will be halted
* <ul>
* <li>if the clock moves backwards (e.g. fast clock after ntp synchronization) until {@code last generated milli}<{@code current milli}</li>
* <li>if {@code count(generated IDs this millisecond)} is greater than {@code MAX_SEQUENCE} until next millisecond</li>
* </ul>
* @return the generated ID
*/
public synchronized long generateID() { public synchronized long generateID() {
long currentTimestamp = currentTimeMillis(); long currentTimestamp = currentTimeMillis();
@@ -130,7 +178,7 @@ public class SnowflakeIDGenerator {
lastTimestamp = currentTimestamp; lastTimestamp = currentTimestamp;
return ((currentTimestamp - EPOCH) << TIMESTAMP_SHIFT) return ((currentTimestamp - EPOCH) << TIMESTAMP_SHIFT)
| (machineId << MACHINE_ID_SHIFT) | (machineId << MACHINE_ID_SHIFT)
| sequence; | sequence;
} }
@@ -150,8 +198,8 @@ public class SnowflakeIDGenerator {
INSTANCE = new SnowflakeIDGenerator(machineId, epoch, machineIdBits, sequenceBits); INSTANCE = new SnowflakeIDGenerator(machineId, epoch, machineIdBits, sequenceBits);
} }
public static void init(long machineId){ public static void init(){
INSTANCE = new SnowflakeIDGenerator(machineId, null, null, null); INSTANCE = new SnowflakeIDGenerator(null, null, null, null);
} }
public static SnowflakeIDGenerator getInstance() { public static SnowflakeIDGenerator getInstance() {
@@ -190,6 +238,14 @@ public class SnowflakeIDGenerator {
public long getMachineId() { public long getMachineId() {
return machineId; return machineId;
} }
public long getTIMESTAMP_BITS() {
return TIMESTAMP_BITS;
}
public long getMAX_TIMESTAMP() {
return MAX_TIMESTAMP;
}
} }