From 1b6049f02f09230660334ac25e1b1b075a7d7e50 Mon Sep 17 00:00:00 2001 From: alis-lc Date: Fri, 15 Sep 2023 06:59:59 +0000 Subject: [PATCH] =?UTF-8?q?:art:=20=E9=9B=AA=E8=8A=B1=E7=AE=97=E6=B3=95?= =?UTF-8?q?=E5=8F=98=E7=A7=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/alis/rpc/common/util/IdWorker.java | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 alis-rpc/alis-rpc-common/src/main/java/org/alis/rpc/common/util/IdWorker.java diff --git a/alis-rpc/alis-rpc-common/src/main/java/org/alis/rpc/common/util/IdWorker.java b/alis-rpc/alis-rpc-common/src/main/java/org/alis/rpc/common/util/IdWorker.java new file mode 100644 index 0000000..495ebd0 --- /dev/null +++ b/alis-rpc/alis-rpc-common/src/main/java/org/alis/rpc/common/util/IdWorker.java @@ -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 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); + } +} \ No newline at end of file