Java 实现 MongoDB ObjectId 算法

以下是 Java 实现 MongoDB ObjectId 算法的完整代码,该实现严格遵循其 12字节 结构规范,包含时间戳、机器标识、进程ID和计数器四部分。

java 复制代码
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.NetworkInterface;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicInteger;

public class MongoObjectId {

    private static final int COUNTER_MAX = 0xFFFFFF; // 3字节最大值
    private static final AtomicInteger counter = new AtomicInteger(new SecureRandom().nextInt());
    private static final byte[] machineId = createMachineId();
    private static final byte[] processId = createProcessId();

    // 生成12字节ObjectId
    public static byte[] generate() {
        byte[] objectId = new byte[12];

        // 4字节时间戳(秒级)
        int timestamp = (int) Instant.now().getEpochSecond();
        objectId[0] = (byte) (timestamp >> 24);
        objectId[1] = (byte) (timestamp >> 16);
        objectId[2] = (byte) (timestamp >> 8);
        objectId[3] = (byte) timestamp;

        // 3字节机器标识
        System.arraycopy(machineId, 0, objectId, 4, 3);

        // 2字节进程ID
        System.arraycopy(processId, 0, objectId, 7, 2);

        // 3字节计数器(线程安全)
        int cnt = counter.incrementAndGet() & COUNTER_MAX;
        objectId[9] = (byte) (cnt >> 16);
        objectId[10] = (byte) (cnt >> 8);
        objectId[11] = (byte) cnt;

        return objectId;
    }

    // 生成16进制字符串表示
    public static String toHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    // 获取3字节机器标识(MAC地址哈希)
    private static byte[] createMachineId() {
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface ni = interfaces.nextElement();
                if (!ni.isLoopback() && ni.getHardwareAddress() != null) {
                    byte[] mac = ni.getHardwareAddress();
                    return new byte[]{mac[0], mac[1], mac[2]};
                }
            }
        } catch (Exception e) {
            // 失败时使用随机数
        }
        return new byte[]{(byte) new SecureRandom().nextInt(),
            (byte) new SecureRandom().nextInt(),
            (byte) new SecureRandom().nextInt()};
    }

    // 获取2字节进程ID
    private static byte[] createProcessId() {
        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        String processName = runtimeMxBean.getName();
        System.out.println("当前进程: " + processName);
        int pid = Integer.parseInt(processName.split("@")[0]);
        System.out.println("当前进程ID: " + pid);
        return new byte[]{(byte) (pid >> 8), (byte) pid};
    }

    public static void main(String[] args) {
        byte[] objectId = generate();
        System.out.println("生成的ObjectId: " + toHexString(objectId));

        objectId = generate();
        System.out.println("生成的ObjectId: " + toHexString(objectId));

        objectId = generate();
        System.out.println("生成的ObjectId: " + toHexString(objectId));
    }
}

实现特点:

  1. 时间戳部分 :采用Unix秒级时间戳,确保时间有序性

  2. 机器标识 :通过MAC地址哈希生成,保证分布式环境唯一性

  3. 进程ID :通过ManagementFactoryRuntimeMXBean实现,这是最可靠且跨平台的方式

  4. 原子计数器 :确保单进程高并发下的唯一性

  5. 最终输出符合MongoDB标准的24位十六进制格式

该实现严格遵循MongoDB ObjectId的规范:

  • 总长度:12字节
  • 组成结构:4字节时间戳 + 3字节机器标识 + 2字节进程ID + 3字节计数器
  • 唯一性保证:同一进程、同一秒内最多可生成16777216(2^24)个不重复ID

实际应用场景:

  • 分布式系统主键生成
  • 日志追踪标识
  • 需要时间排序的数据记录
  • 替代UUIDv4的更高性能方案