李锋镝的博客

  • 首页
  • 时间轴
  • 留言
  • 插件
  • 左邻右舍
  • 关于我
    • 关于我
    • 另一个网站
    • 我的导航站
  • 赞助
Destiny
自是人生长恨水长东
  1. 首页
  2. 原创
  3. 正文

分布式服务生成唯一不重复ID(24位字符串)

2019年7月13日 19232点热度 0人点赞 0条评论

参照mongoDB的ID生成策略,生成唯一ID。

一共12个字节(示例:5cecf0a8a7573ea439fa1a89)

前4个字节表示时间戳
再往后数3个字节表示运行当前程序的机器ID
再往后2个字节表示生成此id的进程的ID
最后3个字节表示由一个随机数开始的计数器生成的值

然后转为16进制的字符串。

源码如下:

import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.net.NetworkInterface;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author 李锋镝
* @date Create at 20:26 2018/12/21
*/
@Slf4j
public final class GeneratorId implements Comparable<GeneratorId>, Serializable {
private static final long serialVersionUID = 440848443996694196L;
private static final int LOW_ORDER_THREE_BYTES = 0x00ffffff;
private static final int MACHINE_IDENTIFIER;
private static final short PROCESS_IDENTIFIER;
private static final AtomicInteger NEXT_COUNTER = new AtomicInteger(new SecureRandom().nextInt());
private static final char[] HEX_CHARS = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private final int timestamp;
private final int machineIdentifier;
private final short processIdentifier;
private final int counter;
public static GeneratorId get() {
return new GeneratorId();
}
public static boolean isValid(final String hexString) {
if (hexString == null) {
throw new IllegalArgumentException();
}
int len = hexString.length();
if (len != 24) {
return false;
}
for (int i = 0; i < len; i++) {
char c = hexString.charAt(i);
if (c >= '0' && c <= '9') {
continue;
}
if (c >= 'a' && c <= 'f') {
continue;
}
if (c >= 'A' && c <= 'F') {
continue;
}
return false;
}
return true;
}
public static int getGeneratedMachineIdentifier() {
return MACHINE_IDENTIFIER;
}
public static int getGeneratedProcessIdentifier() {
return PROCESS_IDENTIFIER;
}
public static int getCurrentCounter() {
return NEXT_COUNTER.get();
}
public static GeneratorId createFromLegacyFormat(final int time, final int machine, final int inc) {
return new GeneratorId(time, machine, inc);
}
public GeneratorId() {
this(new Date());
}
public GeneratorId(final Date date) {
this(dateToTimestampSeconds(date), MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, NEXT_COUNTER.getAndIncrement(), false);
}
public GeneratorId(final Date date, final int counter) {
this(date, MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, counter);
}
public GeneratorId(final Date date, final int machineIdentifier, final short processIdentifier, final int counter) {
this(dateToTimestampSeconds(date), machineIdentifier, processIdentifier, counter);
}
public GeneratorId(final int timestamp, final int machineIdentifier, final short processIdentifier, final int counter) {
this(timestamp, machineIdentifier, processIdentifier, counter, true);
}
private GeneratorId(final int timestamp, final int machineIdentifier, final short processIdentifier, final int counter,
final boolean checkCounter) {
if ((machineIdentifier & 0xff000000) != 0) {
throw new IllegalArgumentException("The machine identifier must be between 0 and 16777215 (it must fit in three bytes).");
}
if (checkCounter && ((counter & 0xff000000) != 0)) {
throw new IllegalArgumentException("The counter must be between 0 and 16777215 (it must fit in three bytes).");
}
this.timestamp = timestamp;
this.machineIdentifier = machineIdentifier;
this.processIdentifier = processIdentifier;
this.counter = counter & LOW_ORDER_THREE_BYTES;
}
public GeneratorId(final String hexString) {
this(parseHexString(hexString));
}
public GeneratorId(final byte[] bytes) {
this(ByteBuffer.wrap(notNull("bytes", bytes)));
}
GeneratorId(final int timestamp, final int machineAndProcessIdentifier, final int counter) {
this(legacyToBytes(timestamp, machineAndProcessIdentifier, counter));
}
public GeneratorId(final ByteBuffer buffer) {
notNull("buffer", buffer);
isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= 12);
timestamp = makeInt(buffer.get(), buffer.get(), buffer.get(), buffer.get());
machineIdentifier = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get());
processIdentifier = (short) makeInt((byte) 0, (byte) 0, buffer.get(), buffer.get());
counter = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get());
}
private static byte[] legacyToBytes(final int timestamp, final int machineAndProcessIdentifier, final int counter) {
byte[] bytes = new byte[12];
bytes[0] = int3(timestamp);
bytes[1] = int2(timestamp);
bytes[2] = int1(timestamp);
bytes[3] = int0(timestamp);
bytes[4] = int3(machineAndProcessIdentifier);
bytes[5] = int2(machineAndProcessIdentifier);
bytes[6] = int1(machineAndProcessIdentifier);
bytes[7] = int0(machineAndProcessIdentifier);
bytes[8] = int3(counter);
bytes[9] = int2(counter);
bytes[10] = int1(counter);
bytes[11] = int0(counter);
return bytes;
}
public byte[] toByteArray() {
ByteBuffer buffer = ByteBuffer.allocate(12);
putToByteBuffer(buffer);
return buffer.array();  // using .allocate ensures there is a backing array that can be returned
}
public void putToByteBuffer(final ByteBuffer buffer) {
notNull("buffer", buffer);
isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= 12);
buffer.put(int3(timestamp));
buffer.put(int2(timestamp));
buffer.put(int1(timestamp));
buffer.put(int0(timestamp));
buffer.put(int2(machineIdentifier));
buffer.put(int1(machineIdentifier));
buffer.put(int0(machineIdentifier));
buffer.put(short1(processIdentifier));
buffer.put(short0(processIdentifier));
buffer.put(int2(counter));
buffer.put(int1(counter));
buffer.put(int0(counter));
}
public int getTimestamp() {
return timestamp;
}
public int getMachineIdentifier() {
return machineIdentifier;
}
public short getProcessIdentifier() {
return processIdentifier;
}
public int getCounter() {
return counter;
}
public Date getDate() {
return new Date(timestamp * 1000L);
}
public String toHexString() {
char[] chars = new char[24];
int i = 0;
for (byte b : toByteArray()) {
chars[i++] = HEX_CHARS[b >> 4 & 0xF];
chars[i++] = HEX_CHARS[b & 0xF];
}
return new String(chars);
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GeneratorId GeneratorId = (GeneratorId) o;
if (counter != GeneratorId.counter) {
return false;
}
if (machineIdentifier != GeneratorId.machineIdentifier) {
return false;
}
if (processIdentifier != GeneratorId.processIdentifier) {
return false;
}
if (timestamp != GeneratorId.timestamp) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = timestamp;
result = 31 * result + machineIdentifier;
result = 31 * result + (int) processIdentifier;
result = 31 * result + counter;
return result;
}
@Override
public int compareTo(final GeneratorId other) {
if (other == null) {
throw new NullPointerException();
}
byte[] byteArray = toByteArray();
byte[] otherByteArray = other.toByteArray();
for (int i = 0; i < 12; i++) {
if (byteArray[i] != otherByteArray[i]) {
return ((byteArray[i] & 0xff) < (otherByteArray[i] & 0xff)) ? -1 : 1;
}
}
return 0;
}
@Override
public String toString() {
return toHexString();
}
static {
try {
MACHINE_IDENTIFIER = createMachineIdentifier();
PROCESS_IDENTIFIER = createProcessIdentifier();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static int createMachineIdentifier() {
int machinePiece;
try {
StringBuilder sb = new StringBuilder();
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
NetworkInterface ni = e.nextElement();
sb.append(ni.toString());
byte[] mac = ni.getHardwareAddress();
if (mac != null) {
ByteBuffer bb = ByteBuffer.wrap(mac);
try {
sb.append(bb.getChar());
sb.append(bb.getChar());
sb.append(bb.getChar());
} catch (BufferUnderflowException shortHardwareAddressException) { //NOPMD
// mac with less than 6 bytes. continue
}
}
}
machinePiece = sb.toString().hashCode();
} catch (Throwable t) {
machinePiece = (new SecureRandom().nextInt());
log.error("Failed to get machine identifier from network interface, using random number instead", t);
}
machinePiece = machinePiece & LOW_ORDER_THREE_BYTES;
return machinePiece;
}
private static short createProcessIdentifier() {
short processId;
try {
String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
if (processName.contains("@")) {
processId = (short) Integer.parseInt(processName.substring(0, processName.indexOf('@')));
} else {
processId = (short) java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode();
}
} catch (Throwable t) {
processId = (short) new SecureRandom().nextInt();
log.error("Failed to get process identifier from JMX, using random number instead", t);
}
return processId;
}
private static byte[] parseHexString(final String s) {
if (!isValid(s)) {
throw new IllegalArgumentException("invalid hexadecimal representation of an GeneratorId: [" + s + "]");
}
byte[] b = new byte[12];
for (int i = 0; i < b.length; i++) {
b[i] = (byte) Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16);
}
return b;
}
private static int dateToTimestampSeconds(final Date time) {
return (int) (time.getTime() / 1000);
}
private static int makeInt(final byte b3, final byte b2, final byte b1, final byte b0) {
return (((b3) << 24) |
((b2 & 0xff) << 16) |
((b1 & 0xff) << 8) |
((b0 & 0xff)));
}
private static byte int3(final int x) {
return (byte) (x >> 24);
}
private static byte int2(final int x) {
return (byte) (x >> 16);
}
private static byte int1(final int x) {
return (byte) (x >> 8);
}
private static byte int0(final int x) {
return (byte) (x);
}
private static byte short1(final short x) {
return (byte) (x >> 8);
}
private static byte short0(final short x) {
return (byte) (x);
}
private static <T> T notNull(final String name, final T value) {
if (value == null) {
throw new IllegalArgumentException(name + " can not be null");
}
return value;
}
private static void isTrueArgument(final String name, final boolean condition) {
if (!condition) {
throw new IllegalArgumentException("state should be: " + name);
}
}
}

 

调用示例:

import com.lifengdi.util.GeneratorId;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.StringJoiner;
/**
* 生成唯一key
* @author 李锋镝
* @date Create at 13:45 2018/12/6
*/
@Component
@Slf4j
public class GeneratedKey {
@Value("${spring.application.name}")
private String applicationName;
/**
* 生成24位的16进制的key
* @return key
*/
public String generatorKey() {
return GeneratorId.get().toString();
}
public static void main(String[] args) {
GeneratedKey generatedKey = new GeneratedKey();
System.out.println(generatedKey.generatorKey());
}
/**
* 获取当前机器ID
* @return MachineIdentifier
*/
public int getMachineIdentifier() {
return GeneratorId.getGeneratedMachineIdentifier();
}
/**
* 获取当前进程ID
* @return ProcessIdentifier
*/
public int getProcessIdentifier() {
return GeneratorId.getGeneratedProcessIdentifier();
}
/**
* 获取当前项目唯一ID
* @return ProjectIdentifier
*/
public String getProjectIdentifier() {
StringJoiner joiner = new StringJoiner("_");
joiner.add(applicationName);
joiner.add(String.valueOf(getMachineIdentifier()));
joiner.add(String.valueOf(getProcessIdentifier()));
return joiner.toString();
}
}

 

其他分布式ID生成算法:分布式ID生成算法SnowFlake(雪花算法)Java源码

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/article/507

相关文章

  • SpringBoot基于redis的分布式锁的实现(源码)
  • SpringBoot常用注解
  • SpringBoot 中内置的 49 个常用工具类
  • SpringBoot 实现接口防刷的 5 种实现方案
  • mybatis-plus-join-boot-starter介绍及用法
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: JAVA MySQL Spring SpringBoot 分布式 数据库
最后更新:2019年7月13日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇
下一篇 >

文章评论

1 2 3 4 5 6 7 8 9 11 12 13 14 15 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 46 47 48 49 50 51 52 53 54 55 57 58 60 61 62 63 64 65 66 67 69 72 74 76 77 78 79 80 81 82 85 86 87 90 92 93 94 95 96 97 98 99
取消回复

COPYRIGHT © 2025 lifengdi.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Dylan

津ICP备2024022503号-3