天天看点

分布式系统全局唯一Id(SnowFlake)雪花算法实现简介代码

简介

由于工作中需要提供通用的唯一ID生成方案,调研了目前市面上通用的解决方案,最终经过汇总,解决了(SnowFlake)雪花算法的坑,时钟回拨问题

其中最主要的就是  处理时钟回拨  ,生成的规则是传入服务器的唯一workId,根据workId生成id

代码

package com.example.demo;

/**
 * @Author: rumenxiaobaidog
 * @Date: 2020/12/15
 * @Desc: 雪花算法生成唯一id  处理了时钟回拨的坑
 */
public class SnowFlakeWorkerUtils {
    private volatile static SnowFlakeWorkerUtils snowFlakeWorkerInstance;

    // 1位标识部分    -      41位时间戳部分        -         10位节点部分     12位序列号部分
    /** 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 */
    /**
     * 起始的时间戳
     */
    private final static long START_STMP = 1288834974657L;

    /**
     * 每一部分占用的位数
     */
    // 序列号占用的位数
    private final static long SEQUENCE_BIT = 12;
    // 机器标识占用的位数
    private final static long WORK_BIT = 10;

    /**
     * WORK_NUM最大值 1023
     */
    private final static long MAX_WORK_NUM = -1L ^ (-1L << WORK_BIT);
    /**
     * SEQUENCE最大值 4095
     */
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long WORK_LEFT = SEQUENCE_BIT;
    private final static long TIMESTMP_LEFT = WORK_LEFT + WORK_BIT;

    private long workId;
    private long sequence = 0L;  //序列号
    private long lastStmp = -1L; //上一次时间戳

    /** 步长, 1024 */
    private static long stepSize = 2 << 9;
    /** 基础序列号, 每发生一次时钟回拨即改变, basicSequence += stepSize */
    private long basicSequence = 0L;

    private SnowFlakeWorkerUtils(long workId) {
        if (workId > MAX_WORK_NUM || workId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        this.workId = workId;
    }


    private synchronized static SnowFlakeWorkerUtils initSnowFlakeWorker(long workId) {
        snowFlakeWorkerInstance = new SnowFlakeWorkerUtils(workId);
        return snowFlakeWorkerInstance;
    }

    public static SnowFlakeWorkerUtils getInstance(long workId) {
        if (null == snowFlakeWorkerInstance) {
            synchronized (SnowFlakeWorkerUtils.class) {
                if (null == snowFlakeWorkerInstance) {
                    snowFlakeWorkerInstance = new SnowFlakeWorkerUtils(workId);
                }
            }
        }
        return snowFlakeWorkerInstance;
    }


    /**
     * 产生下一个ID
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            return handleClockBackwards(currStmp);
        }

        if (currStmp == lastStmp) {
            // 相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            // 不同毫秒内,序列号置为 basicSequence
            sequence = basicSequence;
        }

        lastStmp = currStmp;

        return (currStmp - START_STMP) << TIMESTMP_LEFT  // 时间戳部分
                | workId << WORK_LEFT                    // 节点部分
                | sequence;                              // 序列号部分
    }

    /**
     * 处理时钟回拨
     */
    private long handleClockBackwards(long currStmp) {
        basicSequence += stepSize;
        if (basicSequence == MAX_SEQUENCE + 1) {
            basicSequence = 0;
            currStmp = getNextMill();
        }
        sequence = basicSequence;

        lastStmp = currStmp;

        return (currStmp - START_STMP) << TIMESTMP_LEFT  // 时间戳部分
                | workId << WORK_LEFT                    // 节点部分
                | sequence;                              // 序列号部分
    }

    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        //workId 可根据不同的机器标识唯一的id
        Long workId = 1L;
        for (int i = 0; i < 100; i++) {
            long nextId = SnowFlakeWorkerUtils.getInstance(workId).nextId();
            System.out.println(nextId);
        }
    }

}
           

基于github开源的SnowFlake加以改进,处理了时钟回拨

特别感谢:   https://github.com/beyondfengyu/SnowFlake/blob/master/SnowFlake.java

希望给大家提供帮助,谢谢