上千star的分布式ID生产黑科技,让你再也不用为生成ID发愁了

sequence介绍

sequence是一个基于雪花算法(Snowflake)实现的64位自增ID算法,实现语言是JAVA。

其在雪花算法的基础上,做了一些优化,解决了原生算法的一些痛点:

  1. 支持用户自定义允许时间回拨的范围;
  2. 解决了跨毫秒时起始值从0开始增长的问题;
  3. 解决了高并发场景中获取时间戳性能的问题;

雪花算法的介绍可以参考博主上篇文章:还在用数据库自增ID做主键?建议了解一下雪花算法生成的分布式ID

性能

从官方的性能测试数据来看,sequence每秒可以生成最多418万个有序的ID,即TPS=400w/s,已经基本满足绝大部分的业务场景。

image

源代码及使用

核心代码一共两个类,分别是Sequence.java和SystemClock.java。

Sequence是产生分布式ID的核心,SystemClock主要解决了高并发场景下System.currentTimeMills()的性能问题。

大家也可根据自己的需要,修改其中的部署属性值,比如自定义机器标识,起始时间戳等。

Sequence.java :

public class Sequence {

    private static final Logger log = LoggerFactory.getLogger(Sequence.class);
    /**
     * 时间起始标记点,作为基准,一般取系统的最近时间
     * 一旦确定不能变动,确定后改变此值可能造成id重复
     */
    private final long twepoch = 1519740777809L;
    //5位的机房id
    private final long datacenterIdBits = 5L;
    //5位的机器id
    private final long workerIdBits = 5L;
    //每毫秒内产生的id数: 2的12次方个
    private final long sequenceBits = 12L;
    protected final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    protected final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    //所属机房id
    private final long datacenterId;
    //所属机器id
    private final long workerId;
    //并发控制序列
    private long sequence = 0L;
    //上次生产 ID 时间戳
    private long lastTimestamp = -1L;
    //获取IP
    private static volatile InetAddress LOCAL_ADDRESS = null;
    private static final Pattern IP_PATTERN = Pattern.compile("\d{1,3}(\.\d{1,3}){3,5}$");
    //默认的无参构造
    public Sequence() {
        this.datacenterId = getDatacenterId();
        this.workerId = getMaxWorkerId(datacenterId);
    }

    //有参构造器,我们可以自定义机器id和机房id
    public Sequence(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("Worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("Datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }

        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    //基于网卡MAC地址计算余数作为数据中心,如果不想用网卡可以自定义
    protected long getDatacenterId() {
        long id = 0L;
        try {
            NetworkInterface network = NetworkInterface.getByInetAddress(getLocalAddress());
            if (null == network) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                if (null != mac) {
                    id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
                    id = id % (maxDatacenterId + 1);
                }
            }
        } catch (Exception e) {
            log.warn(" getDatacenterId: " + e.getMessage());
        }

        return id;
    }

    //基于 MAC + PID 的 hashcode 获取16个低位
    protected long getMaxWorkerId(long datacenterId) {
        StringBuilder mpId = new StringBuilder();
        mpId.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (name != null && name.length() > 0) {
            // GET jvmPid
            mpId.append(name.split("@")[0]);
        }

        // MAC + PID 的 hashcode 获取16个低位
        return (mpId.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }

    //获取下一个 ID
    public synchronized long nextId() {
      //获取当前时间戳,这里通过SystemClock优化获取性能
        long timestamp = timeGen();
        // 时间回拨了
        if (timestamp < lastTimestamp) {
            long offset = lastTimestamp - timestamp;
            if (offset <= 5) {
                try {
                    // 休眠双倍差值后重新获取,再次校验
                    wait(offset << 1);
                    timestamp = timeGen();
                    if (timestamp < lastTimestamp) {
                        throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            } else {
                throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
            }
        }
        if (lastTimestamp == timestamp) {
            // 相同毫秒内,序列号自增
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                // 同一毫秒的序列数已经达到最大
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 不同毫秒内,序列号置为 1 - 3 随机数
            sequence = ThreadLocalRandom.current().nextLong(1, 3);
        }
        lastTimestamp = timestamp;
        // 时间戳部分 | 数据中心部分 | 机器标识部分 | 序列号部分
        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }

        return timestamp;
    }

    protected long timeGen() {
        return SystemClock.INSTANCE.currentTimeMillis();
    }

    /**
     * Find first valid IP from local network card
     *
     * @return first valid local IP
     */
    public static InetAddress getLocalAddress() {
        if (LOCAL_ADDRESS != null) {
            return LOCAL_ADDRESS;
        }

        LOCAL_ADDRESS = getLocalAddress0();
        return LOCAL_ADDRESS;
    }

    private static InetAddress getLocalAddress0() {
        InetAddress localAddress = null;
        try {
            localAddress = InetAddress.getLocalHost();
            if (isValidAddress(localAddress)) {
                return localAddress;
            }
        } catch (Throwable e) {
            log.warn("Failed to retrieving ip address, " + e.getMessage(), e);
        }

        try {
            Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
            if (interfaces != null) {
                while (interfaces.hasMoreElements()) {
                    try {
                        NetworkInterface network = interfaces.nextElement();
                        Enumeration addresses = network.getInetAddresses();
                        while (addresses.hasMoreElements()) {
                            try {
                                InetAddress address = addresses.nextElement();
                                if (isValidAddress(address)) {
                                    return address;
                                }
                            } catch (Throwable e) {
                                log.warn("Failed to retrieving ip address, " + e.getMessage(), e);
                            }
                        }
                    } catch (Throwable e) {
                        log.warn("Failed to retrieving ip address, " + e.getMessage(), e);
                    }
                }
            }
        } catch (Throwable e) {
            log.warn("Failed to retrieving ip address, " + e.getMessage(), e);
        }
        log.error("Could not get local host ip address, will use 127.0.0.1 instead.");
        return localAddress;
    }

    private static boolean isValidAddress(InetAddress address) {
        if (address == null || address.isLoopbackAddress()) {
            return false;
        }
        String name = address.getHostAddress();
        return (name != null && !"0.0.0.0".equals(name) && !"127.0.0.1".equals(name) && IP_PATTERN.matcher(name).matches());
    }
}

SystemClock.java:

//利用ScheduledExecutorService实现高并发场景下
//System.curentTimeMillis()的性能问题的优化.
public enum SystemClock {

    INSTANCE(1);
    private final long period;
    private final AtomicLong nowTime;
    private boolean started = false;
    private ScheduledExecutorService executorService;

    SystemClock(long period) {
        this.period = period;
        this.nowTime = new AtomicLong(System.currentTimeMillis());
    }

    public void initialize() {
        if (started) {
            return;
        }

        this.executorService = new ScheduledThreadPoolExecutor(1, r -> {
            Thread thread = new Thread(r, "system-clock");
            thread.setDaemon(true);
            return thread;
        });
        executorService.scheduleAtFixedRate(() -> nowTime.set(System.currentTimeMillis()),
                this.period, this.period, TimeUnit.MILLISECONDS);
        Runtime.getRuntime().addShutdownHook(new Thread(this::destroy));
        started = true;
    }

    public long currentTimeMillis() {
        return started ? nowTime.get() : System.currentTimeMillis();
    }

    public String currentTime() {
        return new Timestamp(currentTimeMillis()).toString();
    }

    public void destroy() {
        if (executorService != null) {
            executorService.shutdown();
        }
    }

}

最后

sequence虽然大幅提升了性能,但是在某些情况下仍然可能出现重复的情况,比如机器标识重复、起始时间戳被修改重置等,这些问题需要我们特别注意。

总得来说,sequence被称为分布式高效ID生产黑科技并不为过,著名的ORM框架mybatis plus用的也是这个组件,大家有兴趣也可以去了解一下。

学习技术,分享技术,期待与大家共同进步,也感谢您的点赞与关注。

你可能感兴趣的:(上千star的分布式ID生产黑科技,让你再也不用为生成ID发愁了)