用于单体应用的,生成类型为Int的雪花算法

2023-12-25补充: 有反馈说并发高的项目使用时, 重启项目会导致重复id, 这是必然的, 原雪花算法支持每毫秒内4096个ID, 为了减少位数兼容int类型, 我这里阉割了数据中心和机器id, 并把并发压缩到每分钟128个ID, 虽然并发量大的时候可以提前支取未来的ID, 但这必然会导致重启项目后预支计数器重置, 如果不能接受请根据自己项目并发情况自行调整

起因

Twitter的雪花算法是出名的高效率,无网络和io开销的全局唯一ID生成算法,且相较于UUID、百度的UidGenerator相比,生成的id具有一定的时间顺序,不需要另起字段排序。


在开发过程中碰到对接硬件SDK时,唯一ID仅支持int类型,但无分布式需求且并发量低。所以动手改写了雪花算法的生成规则。


原雪花算法规则:
  • 正数位(1bit):一个符号位,永远是0。
  • 时间戳(41bit) :自从2012年以来的毫秒数,能撑139年。
  • 自增序列(12bit,最大值4096):毫秒之内的自增,过了一毫秒会重新置0。
  • DataCenter ID (5 bit, 最大值32):配置值,支持多机房。
  • Worker ID ( 5 bit, 最大值32),配置值,一个机房里最多32个机器
  • 生成结果为64bit的long类型数字,长度是18-19位。

改写后规则:
  • 正数位(1bit):一个符号位,永远是0。
  • 时间戳(24bit) :自从2019年以来的分钟数
  • 自增序列(7bit,最大值128):分钟之内的自增,过了一分钟会重新置0。
  • 生成结果为32bit的int类型数字,长度是11位以内。

话不多说,上源码

import com.google.common.collect.Sets;
import java.util.HashSet;

public class SnowFlowIntUtil {
    /** 开始时间戳 (2020-01-01) */
    private static final int twepoch = 26341440;// 1580486400401L/1000/60;

    /** 序列在id中占的位数 */
    private static final long sequenceBits = 6L;

    /** 时间截向左移6位 */
    private static final long timestampLeftShift = sequenceBits;

    /** 生成序列的掩码,这里为63 */
    private static final int sequenceMask = -1 ^ (-1 << sequenceBits);

    /** 分钟内序列(0~63) */
    private static int sequence = 0;
    private static int laterSequence = 0;

    /** 上次生成ID的时间戳 */
    private static int lastTimestamp = -1;

    private static final MinuteCounter counter = new MinuteCounter();

    /** 预支时间标志位 */
    static boolean isAdvance = false;

    // ==============================Constructors=====================================
    private SnowFlowIntUtil() {

    }

    // ==============================Methods==========================================
    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public static synchronized int nextId() {


        int timestamp = timeGen();
        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format(
                    "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        if(timestamp > counter.get()) {
            counter.set(timestamp);
            isAdvance = false;
        }

        // 如果是同一时间生成的,则进行分钟内序列
        if (lastTimestamp == timestamp || isAdvance) {
            if(!isAdvance) {
                sequence = (sequence + 1) & sequenceMask;
            }

            // 分钟内自增列溢出
            if (sequence == 0) {
                // 预支下一个分钟,获得新的时间戳
                isAdvance = true;
                int laterTimestamp = counter.get();
                if (laterSequence == 0) {
                    laterTimestamp = counter.incrementAndGet();
                }

                int nextId = ((laterTimestamp - twepoch) << timestampLeftShift) //
                        | laterSequence;
                laterSequence = (laterSequence + 1) & sequenceMask;
                return nextId;
            }
        }
        // 时间戳改变,分钟内序列重置
        else {
            sequence = 0;
            laterSequence = 0;
        }

        // 上次生成ID的时间截
        lastTimestamp = timestamp;

        // 移位并通过或运算拼到一起组成32位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | sequence;
    }

    /**
     * 返回以分钟为单位的当前时间
     *
     * @return 当前时间(分钟)
     */
    protected static int timeGen() {
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000 / 60);
        return Integer.valueOf(timestamp);
    }

    // ==============================Test=============================================
    public static void main(String[] args) {
        
        HashSet<Object> set = Sets.newHashSet();
        for (int i = 0; i < 10000; i++) {
            int id = SnowFlowIntUtil.nextId();
            set.add(id);
            String s = String.valueOf(id);
            if (s.length()!=8){
                System.out.println(i + ": " + id);
            }
        }
        System.out.println(set.size());
    }
}

public class MinuteCounter {
    private static final int MASK = 0x7FFFFFFF;
    private final AtomicInteger atom;

    public MinuteCounter() {
        atom = new AtomicInteger(0);
    }

    public final int incrementAndGet() {
        return atom.incrementAndGet() & MASK;
    }

    public int get() {
        return atom.get() & MASK;
    }

    public void set(int newValue) {
        atom.set(newValue & MASK);
    }
}

文末贴上Twitter原生的雪花算法及位运算解析

public class SnowFlakeUtil {

//    private static final Logger logger = LoggerFactory.getLogger(SnowFlakeUtil.class);


    //下面两个每个5位,加起来就是10位的工作机器id

    //    @Getter
//    @Setter
//    private static long workerId;    //工作id
//    @Getter
//    @Setter
//    private static long datacenterId;   //数据id
    //12位的序列号
    private static long sequence = 158433506100L;

//    static {
//        if (datacenterId == 0L) {
//            logger.error("请先配置机器id");
//            throw new RuntimeException("业务id无法生成,缺少配置项目!");
//        }
//    }


//    public SnowFlakeUtil(long workerId, long datacenterId, long sequence) {
//        // sanity check for workerId
//        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));
//        }
//        System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
//                timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);
//
//        this.workerId = workerId;
//        this.datacenterId = datacenterId;
//        this.sequence = sequence;
//    }

    //初始时间戳
    private static long twepoch = 1288834974657L;

    //长度为5位
    private static long workerIdBits = 5L;
    private static long datacenterIdBits = 5L;
    //最大值
    private static long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    //序列号id长度
    private static long sequenceBits = 12L;
    //序列号最大值
    private static long sequenceMask = -1L ^ (-1L << sequenceBits);

    //工作id需要左移的位数,12位
    private static long workerIdShift = sequenceBits;
    //数据id需要左移位数 12+5=17位
    private static long datacenterIdShift = sequenceBits + workerIdBits;
    //时间戳需要左移位数 12+5+5=22位
    private static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    //上次时间戳,初始值为负数
    private static long lastTimestamp = -1L;

    //下一个ID生成算法
    public static synchronized long nextId(long wId, long datacenterId) {
        long timestamp = timeGen();

        //获取当前时间戳如果小于上次时间戳,则表示时间戳获取出现异常
        if (timestamp < lastTimestamp) {
            System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                    lastTimestamp - timestamp));
        }

        //获取当前时间戳如果等于上次时间戳(同一毫秒内),则在序列号加一;否则序列号赋值为0,从0开始。
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }

        //将上次时间戳值刷新
        lastTimestamp = timestamp;

        /**
         * 返回结果:
         * (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳,再左移相应位数
         * (datacenterId << datacenterIdShift) 表示将数据id左移相应位数
         * (workerId << workerIdShift) 表示将工作id左移相应位数
         * | 是按位或运算符,例如:x | y,只有当x,y都为0的时候结果才为0,其它情况结果都为1。
         * 因为个部分只有相应位上的值有意义,其它位上都是0,所以将各部分的值进行 | 运算就能得到最终拼接好的id
         */
        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (wId << workerIdShift) |
                sequence;
    }

    //获取时间戳,并与上次时间戳比较
    private static long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    //获取系统时间戳
    private static long timeGen() {
        return System.currentTimeMillis();
    }

    public long getTimestamp() {
        return System.currentTimeMillis();
    }

    //---------------测试---------------
//    public static void main(String[] args) {
//        SnowFlakeUtil worker = new SnowFlakeUtil(1, 1, 1234567890);
//        for (int i = 0; i < 30; i++) {
//            System.out.println(worker.nextId());
//        }
//    }
}

其中return处的位运算做的事可以这样理解

// 将时间戳的42bit左移22位,给5bit的数据中心ID、5bit的机器ID和12bit的序列号ID腾出位置
((timestamp - twepoch) << timestampLeftShift) |  // 与上数据中心ID+机器ID+序列号ID
// 将数据中心ID的5bit左移17位,给5bit的机器ID和12bit的序列号ID腾出位置
(datacenterId << datacenterIdShift) |  // 与上机器ID+序列号ID
// 将机器ID的5bit左移12位,给12bit的序列号ID腾出位置
(wId << workerIdShift) |  // 与上序列号ID
sequence;

最后结果就是:
时间戳.append数据中心ID.append机器ID.append序列号

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值