feat(guid): added facade guid creator and snowflake guid generator

This commit is contained in:
Zihlu Wang
2023-07-28 19:32:46 +08:00
parent 44de3bcf69
commit 41544f231a
6 changed files with 391 additions and 0 deletions
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 CodeCraftersCN.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0"
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.org.codecrafters.guid;
/**
* The `GuidCreator` is a generic interface for generating globally unique
* identifiers (GUIDs) of a specific type.
* <p>
* The type of ID is determined by the class implementing this interface.
*
* @param <IdType> this represents the type of the Global Unique Identifier
*/
public interface GuidCreator<IdType> {
/**
* Generates and returns the next globally unique ID.
* The exact implementation of how the globally unique ID is generated and
* returned will depend on the class implementing this method.
*
* @return the next globally unique ID
*/
IdType nextId();
}
@@ -0,0 +1,200 @@
/*
* Copyright (C) 2023 CodeCraftersCN.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0"
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.org.codecrafters.guid;
import cn.org.codecrafters.guid.exceptions.TimingException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
/**
* SnowflakeGuidCreator is a GUID generator based on the Snowflake algorithm.
* It generates unique identifiers using a combination of timestamp, worker ID,
* and data center ID.
*
* <p>The Snowflake algorithm allows for the generation of 64-bit long integers,
* with the following bit distribution:
* - 1 bit for sign
* - 41 bits for timestamp (in milliseconds)
* - 5 bits for data center ID
* - 5 bits for worker ID
* - 12 bits for sequence number (per millisecond)
*
* <p>The worker ID and data center ID must be specified during initialization,
* and they must be within the valid range defined by the bit size.
* The generator maintains an internal sequence number that increments for IDs
* generated within the same millisecond. If the system clock moves backwards,
* an exception is thrown to avoid generating IDs with repeated timestamps.
*
* @author Zihlu Wang
* @version 1.0.0
* @since 14 Jul 2023
*/
public final class SnowflakeGuidCreator implements GuidCreator<Long> {
/**
* Default Custom Epoch (January 1, 2015, Midnight UTC = 2015-01-01T00:00:00Z)
*/
private static final long DEFAULT_CUSTOM_EPOCH = 1_420_070_400_000L;
/**
* The start epoch timestamp to generate IDs from.
*/
private final long startEpoch;
/**
* The number of bits reserved for the worker ID.
*/
private final long workerIdBits = 5L;
/**
* The number of bits reserved for the data center ID.
*/
private final long dataCentreIdBits = 5L;
/**
* The worker ID assigned to this generator.
*/
private final long workerId;
/**
* The data center ID assigned to this generator.
*/
private final long dataCentreId;
/**
* The current sequence number.
*/
private long sequence = 0L;
/**
* The timestamp of the last generated ID.
*/
private long lastTimestamp = -1L;
/**
* Constructs a SnowflakeGuidGenerator with the default start epoch and custom worker ID, data center ID.
*
* @param workerId the worker ID (between 0 and 31).
* @param dataCentreId the data center ID (between 0 and 31).
*/
public SnowflakeGuidCreator(long workerId, long dataCentreId) {
this(DEFAULT_CUSTOM_EPOCH, workerId, dataCentreId);
}
/**
* Constructs a SnowflakeGuidGenerator with a custom epoch, worker ID, and data center ID.
*
* @param startEpoch the custom epoch timestamp (in milliseconds) to start generating IDs from
* @param workerId the worker ID (between 0 and 31)
* @param dataCentreId the data center ID (between 0 and 31)
* @throws IllegalArgumentException if the start epoch is greater than the current timestamp,
* or if the worker ID or data center ID is out of range
*/
public SnowflakeGuidCreator(long startEpoch, long workerId, long dataCentreId) {
if (startEpoch > currentTimestamp()) {
throw new IllegalArgumentException("Start Epoch can not be greater than current timestamp!");
}
var maxWorkerId = ~(-1L << workerIdBits);
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("Worker Id can't be greater than %d or less than 0",
maxWorkerId));
}
var maxDataCentreId = ~(-1L << dataCentreIdBits);
if (dataCentreId > maxDataCentreId || dataCentreId < 0) {
throw new IllegalArgumentException(String.format("Data Centre Id can't be greater than %d or less than 0",
maxDataCentreId));
}
this.startEpoch = startEpoch;
this.workerId = workerId;
this.dataCentreId = dataCentreId;
}
/**
* Generates the next unique ID.
*
* @return the generated unique ID
* @throws TimingException if the system clock moves backwards,
* indicating an invalid sequence of timestamps.
*/
@Override
public synchronized Long nextId() {
var timestamp = currentTimestamp();
// If the current time is less than the timestamp of the last ID generation, it means that the system clock
// has been set back and an exception should be thrown.
if (timestamp < lastTimestamp) {
throw new TimingException("Clock moved backwards. Refusing to generate id for %d milliseconds"
.formatted(lastTimestamp - timestamp));
}
// If generated at the same time, perform intra-millisecond sequences
long sequenceBits = 12L;
if (lastTimestamp == timestamp) {
long sequenceMask = ~(-1L << sequenceBits);
sequence = (sequence + 1) & sequenceMask;
// Sequence overflow in milliseconds
if (sequence == 0) {
// Block to the next millisecond, get a new timestamp
timestamp = awaitToNextMillis(lastTimestamp);
}
}
// Timestamp change, sequence reset in milliseconds
else {
sequence = 0L;
}
// Timestamp of last ID generation
lastTimestamp = timestamp;
// shifted and put together by or operations to form a 64-bit ID
var timestampLeftShift = sequenceBits + workerIdBits + dataCentreIdBits;
var dataCentreIdShift = sequenceBits + workerIdBits;
return ((timestamp - startEpoch) << timestampLeftShift)
| (dataCentreId << dataCentreIdShift)
| (workerId << sequenceBits)
| sequence;
}
/**
* Blocks until the next millisecond to obtain a new timestamp.
*
* @param lastTimestamp the timestamp when the last ID was generated
* @return the current timestamp
*/
private long awaitToNextMillis(long lastTimestamp) {
var timestamp = currentTimestamp();
while (timestamp <= lastTimestamp) {
timestamp = currentTimestamp();
}
return timestamp;
}
/**
* Returns the current timestamp in milliseconds.
*
* @return the current timestamp
*/
private long currentTimestamp() {
return LocalDateTime.now().toInstant(ZoneOffset.UTC).toEpochMilli();
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2023 CodeCraftersCN.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0"
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.org.codecrafters.guid.exceptions;
/**
* The TimingException class represents an exception that is thrown when there is an error related
* to time sequence.
* <p>
* This class extends the RuntimeException class, which means that instances of TimingException
* do not need to be declared in a method or constructor's throws clause.
* <p>
* Instances of TimingException can be created with or without a message and a cause. The message
* provides a description of the exception, while the cause represents the underlying cause of the
* exception and provides additional information about the error.
* <p>
* TimingException is typically used to handle exceptions related to timing, such as timeouts or
* synchronization issues. It is a subclass of RuntimeException, which means it is an unchecked
* exception and does not need to be caught or declared.
* <p>
*
* @author Zihlu Wang
* @since 1.0.0
*/
public class TimingException extends RuntimeException {
/**
* A custom exception that is thrown when there is an issue with timing or scheduling.
*/
public TimingException() {
}
/**
* A custom exception that is thrown when there is an issue with timing or scheduling with
* customized error message.
*
* @param message customized message
*/
public TimingException(String message) {
super(message);
}
/**
* A custom exception that is thrown when there is an issue with timing or scheduling with
* customized error message.
*
* @param message customized message
* @param cause the cause of this exception
*/
public TimingException(String message, Throwable cause) {
super(message, cause);
}
/**
* A custom exception that is thrown when there is an issue with timing or scheduling with
* customized error message.
*
* @param cause the cause of this exception
*/
public TimingException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 CodeCraftersCN.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0"
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* The package provides a set of tools for generating globally unique
* identifiers (GUIDs).
* <p>
* The goal of this library is to provide an efficient, reliable way to
* generate globally unique identifiers without requiring any specific
* environment or configuration.
* <p>
* Key features include:
* <ul>
* <li>Efficient generation of globally unique identifiers</li>
* <li>High performance and quick response</li>
* <li>Easy to integrate</li>
* </ul>
*
* @since 2023.2
*/
package cn.org.codecrafters.guid;