feat(guid): added facade guid creator and snowflake guid generator
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>cn.org.codecrafters</groupId>
|
||||||
|
<artifactId>jdevkit</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>guid</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -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;
|
||||||
@@ -15,6 +15,9 @@
|
|||||||
<version>1.0.0</version>
|
<version>1.0.0</version>
|
||||||
|
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
<modules>
|
||||||
|
<module>guid</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
|||||||
Reference in New Issue
Block a user