从零开始设计并实现分布式ID生成工具

说明:本文参考MongoDB_id的生成方式。PS:预祝大家新年快乐–

需求

  • 分布式:请求量较大,得满足分布式需求(即:同时向不同机器请求得到的ID冲突的概率低)
  • 具有一定的信息量(例如:能看出插入时间)

MongoDB_id生成介绍

先来看看MongoDB中使用的_id,由12个字节组成,有以下格式:
– 前4个字节表示时间戳(精确到秒)
– 接下来3个字节表示机器标识码(一般由Mac地址确定)
– 接下来2个字节表示进程ID(PID
– 最后3个字节是计数值(计数器,递增值)

|5a712a85|6760c6|386c|4fb587|
|--时间戳-|-机器ID|进程ID|计数值|

首先大致介绍下使用到的工具类:
– 分别获取整数的前8位/9-16位/16-24位/后8位

    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);
    }
  • 分别获取18位二进制的前8位和后8位
    private static byte short1(final short x) {
        return (byte) (x >> 8);
    }

    private static byte short0(final short x) {
        return (byte) (x);
    }

接下来具体介绍一下如何生成各个部分:

时间戳

注意:精确到秒,填充前4位。

        bytes[0] = int3(timestamp);
        bytes[1] = int2(timestamp);
        bytes[2] = int1(timestamp);
        bytes[3] = int0(timestamp);
/**
     * Get the second unit of timestamp
     *
     * @param time the creation time
     * @return seconds of creation time
     */
    private static int dateToTimestampSeconds(final Date time){
        return (int) ((time.getTime())/1000);
    }

机器ID

        bytes[4] = int2(machinePiece);
        bytes[5] = int1(machinePiece);
        bytes[6] = int0(machinePiece);
    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){
                        // mac with less than 6 bytes. continue
                    }
                }
            }
            machinePiece = sb.toString().hashCode();
        }catch (Throwable t){
            // exception sometimes happens with IBM JVM, use random
            machinePiece = (new SecureRandom().nextInt());
            LOGGER.log(Level.WARN, "Failed to get machine identifier from network interface, using random number instead.",  t);
        }
        machinePiece = machinePiece & LOW_ORDER_THREE_BYTES;
        return machinePiece;
    }

进程ID

        bytes[7] = short1(processIdentifier);
        bytes[8] = short0(processIdentifier);
// Creates the process identifier. This does not have to be unique per class loader because
    // NEXT_COUNTER will provide the uniqueness.
    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.getClassLoadingMXBean().hashCode();
            }
        } catch (Throwable t){
            processId = (short) new SecureRandom().nextInt();
            LOGGER.log(Level.WARN, "Failed to get process identifier from JMX, using random number instead", t);
        }
        return processId;
    }

计数器

    bytes[9] = int2(counter);
    bytes[10] = int1(counter);
    bytes[11] = int0(counter);
    private static final AtomicInteger NEXT_COUNTER = new AtomicInteger(new SecureRandom().nextInt());
    counter = NEXT_COUNTER.getAndIncrement();

_id的解析

 public static boolean isValid(final String hexString){
        if (hexString == null){
            throw new IllegalArgumentException();
        }
        int len = hexString.length();
        if (len != STR_LEN){
            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;
    }

    private static byte[] parseHexString(final String s){
        if (!isValid(s)){
            throw new IllegalArgumentException("invalid hexadecimal representaion of an ObjectId: [" + s + "]");
        }
        byte[] b = new byte[STR_LEN/2];
        for (int i = 0; i < b.length; i++){
            b[i] = (byte) Integer.parseInt(s.substring(i * 2, i*2 + 2), 16);
        }
        return b;
    }
打赏

mickey

记录生活,写给几十年后的自己。