🎨 雪花算法变种
parent
eb3e3fa72b
commit
1b6049f02f
@ -0,0 +1,171 @@
|
||||
package org.alis.rpc.common.util;
|
||||
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Enumeration;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author lc
|
||||
* @date 2023/9/15 14:55
|
||||
*/
|
||||
public class IdWorker {
|
||||
/**
|
||||
* Start time cut (2020-05-03)
|
||||
*/
|
||||
private final long twepoch = 1588435200000L;
|
||||
|
||||
/**
|
||||
* The number of bits occupied by workerId
|
||||
*/
|
||||
private final int workerIdBits = 10;
|
||||
|
||||
/**
|
||||
* The number of bits occupied by timestamp
|
||||
*/
|
||||
private final int timestampBits = 41;
|
||||
|
||||
/**
|
||||
* The number of bits occupied by sequence
|
||||
*/
|
||||
private final int sequenceBits = 12;
|
||||
|
||||
/**
|
||||
* Maximum supported machine id, the result is 1023
|
||||
*/
|
||||
private final int maxWorkerId = ~(-1 << workerIdBits);
|
||||
|
||||
/**
|
||||
* business meaning: machine ID (0 ~ 1023)
|
||||
* actual layout in memory:
|
||||
* highest 1 bit: 0
|
||||
* middle 10 bit: workerId
|
||||
* lowest 53 bit: all 0
|
||||
*/
|
||||
private long workerId;
|
||||
|
||||
/**
|
||||
* timestamp and sequence mix in one Long
|
||||
* highest 11 bit: not used
|
||||
* middle 41 bit: timestamp
|
||||
* lowest 12 bit: sequence
|
||||
*/
|
||||
private AtomicLong timestampAndSequence;
|
||||
|
||||
/**
|
||||
* mask that help to extract timestamp and sequence from a long
|
||||
*/
|
||||
private final long timestampAndSequenceMask = ~(-1L << (timestampBits + sequenceBits));
|
||||
|
||||
/**
|
||||
* instantiate an IdWorker using given workerId
|
||||
* @param workerId if null, then will auto assign one
|
||||
*/
|
||||
public IdWorker(Long workerId) {
|
||||
initTimestampAndSequence();
|
||||
initWorkerId(workerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* init first timestamp and sequence immediately
|
||||
*/
|
||||
private void initTimestampAndSequence() {
|
||||
long timestamp = getNewestTimestamp();
|
||||
long timestampWithSequence = timestamp << sequenceBits;
|
||||
this.timestampAndSequence = new AtomicLong(timestampWithSequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* init workerId
|
||||
* @param workerId if null, then auto generate one
|
||||
*/
|
||||
private void initWorkerId(Long workerId) {
|
||||
if (workerId == null) {
|
||||
workerId = generateWorkerId();
|
||||
}
|
||||
if (workerId > maxWorkerId || workerId < 0) {
|
||||
String message = String.format("worker Id can't be greater than %d or less than 0", maxWorkerId);
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
this.workerId = workerId << (timestampBits + sequenceBits);
|
||||
}
|
||||
|
||||
/**
|
||||
* get next UUID(base on snowflake algorithm), which look like:
|
||||
* highest 1 bit: always 0
|
||||
* next 10 bit: workerId
|
||||
* next 41 bit: timestamp
|
||||
* lowest 12 bit: sequence
|
||||
* @return UUID
|
||||
*/
|
||||
public long nextId() {
|
||||
waitIfNecessary();
|
||||
long next = timestampAndSequence.incrementAndGet();
|
||||
long timestampWithSequence = next & timestampAndSequenceMask;
|
||||
return workerId | timestampWithSequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* block current thread if the QPS of acquiring UUID is too high
|
||||
* that current sequence space is exhausted
|
||||
*/
|
||||
private void waitIfNecessary() {
|
||||
long currentWithSequence = timestampAndSequence.get();
|
||||
long current = currentWithSequence >>> sequenceBits;
|
||||
long newest = getNewestTimestamp();
|
||||
if (current >= newest) {
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException ignore) {
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get newest timestamp relative to twepoch
|
||||
*/
|
||||
private long getNewestTimestamp() {
|
||||
return System.currentTimeMillis() - twepoch;
|
||||
}
|
||||
|
||||
/**
|
||||
* auto generate workerId, try using mac first, if failed, then randomly generate one
|
||||
* @return workerId
|
||||
*/
|
||||
private long generateWorkerId() {
|
||||
try {
|
||||
return generateWorkerIdBaseOnMac();
|
||||
} catch (Exception e) {
|
||||
return generateRandomWorkerId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* use lowest 10 bit of available MAC as workerId
|
||||
* @return workerId
|
||||
* @throws Exception when there is no available mac found
|
||||
*/
|
||||
private long generateWorkerIdBaseOnMac() throws Exception {
|
||||
Enumeration<NetworkInterface> all = NetworkInterface.getNetworkInterfaces();
|
||||
while (all.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = all.nextElement();
|
||||
boolean isLoopback = networkInterface.isLoopback();
|
||||
boolean isVirtual = networkInterface.isVirtual();
|
||||
if (isLoopback || isVirtual) {
|
||||
continue;
|
||||
}
|
||||
byte[] mac = networkInterface.getHardwareAddress();
|
||||
return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF);
|
||||
}
|
||||
throw new RuntimeException("no available mac found");
|
||||
}
|
||||
|
||||
/**
|
||||
* randomly generate one as workerId
|
||||
* @return workerId
|
||||
*/
|
||||
private long generateRandomWorkerId() {
|
||||
return ThreadLocalRandom.current().nextInt(maxWorkerId + 1);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue