-
Notifications
You must be signed in to change notification settings - Fork 703
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ae20007
commit a81feca
Showing
2 changed files
with
113 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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
110 changes: 110 additions & 0 deletions
110
spring-social/src/main/java/com/example/springsocial/controller/SequenceGenerator.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package com.example.springsocial.controller; | ||
|
||
|
||
import java.net.NetworkInterface; | ||
import java.security.SecureRandom; | ||
import java.time.Instant; | ||
import java.util.Enumeration; | ||
|
||
/** | ||
* Distributed Sequence Generator. | ||
* Inspired by Twitter snowflake: https://github.com/twitter/snowflake/tree/snowflake-2010 | ||
* | ||
* This class should be used as a Singleton. | ||
* Make sure that you create and reuse a Single instance of SequenceGenerator per machine in your distributed system cluster. | ||
*/ | ||
public class SequenceGenerator { | ||
private static final int TOTAL_BITS = 64; | ||
private static final int EPOCH_BITS = 42; | ||
private static final int MACHINE_ID_BITS = 10; | ||
private static final int SEQUENCE_BITS = 12; | ||
|
||
private static final int maxMachineId = (int)(Math.pow(2, MACHINE_ID_BITS) - 1); | ||
private static final int maxSequence = (int)(Math.pow(2, SEQUENCE_BITS) - 1); | ||
|
||
// Custom Epoch (January 1, 2015 Midnight UTC = 2015-01-01T00:00:00Z) | ||
private static final long CUSTOM_EPOCH = 1420070400000L; | ||
|
||
private final int machineId; | ||
|
||
private long lastTimestamp = -1L; | ||
private long sequence = 0L; | ||
|
||
// Create Snowflake with a machineId | ||
public SequenceGenerator(int machineId) { | ||
if(machineId < 0 || machineId > maxMachineId) { | ||
throw new IllegalArgumentException(String.format("MachineId must be between %d and %d", 0, maxMachineId)); | ||
} | ||
this.machineId = machineId; | ||
} | ||
|
||
// Let Snowflake generate a machineId | ||
public SequenceGenerator() { | ||
this.machineId = createMachineId(); | ||
} | ||
|
||
|
||
public long nextId() { | ||
long currentTimestamp = timestamp(); | ||
|
||
synchronized (this) { | ||
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 for next millisecond | ||
sequence = 0; | ||
} | ||
|
||
lastTimestamp = currentTimestamp; | ||
} | ||
|
||
long id = currentTimestamp << (TOTAL_BITS - EPOCH_BITS); | ||
id |= (machineId << (TOTAL_BITS - EPOCH_BITS - MACHINE_ID_BITS)); | ||
id |= sequence; | ||
return id; | ||
} | ||
|
||
|
||
// Get current timestamp in milliseconds, adjust for the custom epoch. | ||
private static long timestamp() { | ||
return Instant.now().toEpochMilli() - CUSTOM_EPOCH; | ||
} | ||
|
||
// Block and wait till next millisecond | ||
private long waitNextMillis(long currentTimestamp) { | ||
while (currentTimestamp == lastTimestamp) { | ||
currentTimestamp = timestamp(); | ||
} | ||
return currentTimestamp; | ||
} | ||
|
||
private int createMachineId() { | ||
int machineId; | ||
try { | ||
StringBuilder sb = new StringBuilder(); | ||
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); | ||
while (networkInterfaces.hasMoreElements()) { | ||
NetworkInterface networkInterface = networkInterfaces.nextElement(); | ||
byte[] mac = networkInterface.getHardwareAddress(); | ||
if (mac != null) { | ||
for(int i = 0; i < mac.length; i++) { | ||
sb.append(String.format("%02X", mac[i])); | ||
} | ||
} | ||
} | ||
machineId = sb.toString().hashCode(); | ||
} catch (Exception ex) { | ||
machineId = (new SecureRandom().nextInt()); | ||
} | ||
machineId = machineId & maxMachineId; | ||
return machineId; | ||
} | ||
} |