小型分布式微服务Long型ID生成器

本文提出一种基于改进雪花算法的ID生成策略,适用于小型企业TPS约100万的应用场景。通过调整ID长度,确保前端接收的ID不超过15位,避免精度丢失。采用40位时间戳、3位机器位和10位序列号,支持35年内不重复的ID生成,每毫秒最多产生1024个ID。

在微服务开发中,ID生成一直是一个十分头疼的问题,网上常见的方式有三种:

  1. 基于数据库的主键自增
  2. 基于UUID
  3. 雪花算法

对于第1种基于数据库主键自增的,当数据量比较少时无所谓,但是当数据量很大需要进行分表分库是扩展时,对于ID的处理就会出现各种麻烦

对于第2种基于UUID的主键,理论上不会出现问题,但是由于UUID是字符串形式,在Mysql数据库中对于字符串的查询没有数值型的效率高

对于第3种基于雪花算法的ID生成器,这种方式也是比较主流的,各大厂的ID生成器也是在此基础之上进行优化改造后实现的。但是由于雪花算法生成的ID是基于64位Long型数值产生的,传递给前端时会出现精度丢失的问题。

本文介绍的方式是在雪花算法的基础之上对ID长度进行调整后生成的, *适用于TPS在100W左右的应用。为了保证返回给前端的ID不会出现溢出,返回给前端的数值不超过9007199254740991,即二进制的53位全1数值,其中使用前40位用作时间戳位,能够保证在35年不会重复,3位作为机器位,即最多允许部署8台机器,对于小型服务基本够用,后10位作为序号位,允许每毫秒最多产生1024个ID。

由于是适用于小型企业的,故不设置数据中心节点。可以自己定制注册中心进行工作机器注册。


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * 适用于TPS在100W左右的应用
 * 为了保证返回给前端的ID不会出现溢出,返回给前端的整型不能超过15位
 * 后端限制最高返回值是53位二进制的全1
 * 使用40位用作时间戳位,能够保证在35年不会重复
 * 3位作为机器位,即最多允许部署8台机器,对于小型服务基本够用
 * 10位作为序号位,允许每毫秒最多产生1024个ID
 *
 * @author: LiuMeng
 * @date: 2019/11/19
 * TODO:
 */
@Slf4j
public class Sequence {

    /**
     * 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
     * 2019-11-20 00:00:00
     */
    private final long edenPoint = 1574179200000L;
    private final long workerIdBits = 3L;
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long sequenceBits = 10L;
    private final long workerIdShift = sequenceBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    private final long workerId;
    private final long idBits = 53;
    private final long maxLimit = -1L ^ (-1L << idBits);
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    private final ISequenceAdapter sequenceAdapter;
    private static Sequence instance;

    /**
     * 有参构造器
     */
    private Sequence(ISequenceAdapter sequenceAdapter) {
        this.sequenceAdapter = sequenceAdapter;
        long nextWorkId = this.sequenceAdapter.getNextWorkId();
        if (nextWorkId > maxWorkerId) {
            throw new IllegalStateException("The work Id has reached the maximum");
        }
        this.workerId = nextWorkId;
        this.startSequenceIdRegisterScheduled();
        instance = this;
    }

    /**
     * 获取全局对象
     *
     * @return
     */
    public static Sequence getInstance(ISequenceAdapter sequenceAdapter) {
        if (null == instance) {
            synchronized (Sequence.class) {
                if (null == instance) {
                    instance = new Sequence(sequenceAdapter);
                }
            }
        }
        return instance;
    }

    /**
     * 获取全局对象
     *
     * @return
     */
    public static Sequence getInstance() throws SequenceInstanceException {
        if (null == instance) {
            throw new SequenceInstanceException("the sequence is not instance, you can use [getInstance(ISequenceAdapter)] to get or init.");
        }
        return instance;
    }

    public synchronized long nextId() {
        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 {
            sequence = ThreadLocalRandom.current().nextLong(1, 3);
        }
        lastTimestamp = timestamp;
        // 时间戳部分 | 机器标识部分 | 序列号部分
        return (((timestamp - edenPoint) << timestampLeftShift)
                | (workerId << workerIdShift)
                | sequence) & maxLimit;
    }

    /**
     * 同一毫秒内Id数量达到最大,等待下一毫秒
     *
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        //自旋获取下毫秒
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return sequenceAdapter.getTimestamp();
    }

    private void startSequenceIdRegisterScheduled() {
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
            Thread thread = new Thread(runnable, "Sequence-Register-Schedule");
            thread.setDaemon(true);
            return thread;
        });
        scheduler.scheduleAtFixedRate(() -> this.sequenceAdapter.register(this.workerId), 1, 1, TimeUnit.MINUTES);
    }
}

/**
 * @author: LiuMeng
 * @date: 2019/11/20
 * TODO:
 */
public interface ISequenceAdapter {

    /**
     * 注册服务的工作ID,每分钟一次
     * @param workId
     */
    void register(long workId);

    /**
     * 获取服务的下一个工作Id
     */
    long getNextWorkId();

    /**
     * 获取当前的时间戳
     * @return
     */
    long getTimestamp();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值