雪花算法详解

雪花算法是推特开源的分布式ID生成算法,用于在不同的机器上生成唯一的ID的算法。
该算法生成一个64bit的数字作为分布式ID,保证这个ID自增并且全局唯一。
生成的64位ID结构如下:
雪花算法详解_第1张图片

实现原理

第一位为0不使用
2进制中,数字的第一位表示正负,0为正1为负。因此第一位为0表示我们生成的分布式ID是一个正数
时间戳41位
这41位表示当前时间的毫秒值,可以表示从0到 2 41 2^{41} 241-1,换算成 年份是69年。也就是说,使用雪花算法我们可以保证从今年(2021年)到2090年都不会生成重复ID
工作机器ID10位
工作机器ID10位分为机房号机器号,共可以表示1024台不同的机器。一般前五位表示机房,后五位表示机器。我们实际使用时可以根据自己的情况指定机房和机器的位数。反正一共占10位。
机房ID5位
用5位来表示机房ID, 2 5 2^5 25=32,共可以表示32个机房,编号从0到 2 5 2^5 25-1
机器ID5位
用5位来表示机器ID, 2 5 2^5 25=32,共可以表示32台机器,编号从0到 2 5 2^5 25-1
序列号12位
如果是同一台机器在同一毫秒生成了多个ID,他们的前52位完全相同(时间戳和机房ID机器ID完全相同),那么我们使用序列号来对这些ID进行区分,在这一毫秒中,第一个生成的ID序列号为0,第二个为1以此类推。序列号共占12位,最多表示 2 12 2^{12} 212=4096个数。因此一台机器在一豪秒最多升成4096个ID,如果到达这个值,程序通过自旋等待时间到达下一毫秒然后生成新的分布式ID。

实现代码

public class SnowFlakeUtils {
    /*
        起始时间时间戳:这个时间为第一次运行时的时间,这里设置为2021/11/23/19/17
        可以在未来的69年内稳定运行
     */
    private final static long START_STMP=1637666189914L;


    private final static long SEQUENCE_BIT=12;//序列号占用12bit
    private final static long MACHINE_BIT=5;//机器号占用5bit
    private final static long MACHINE_HOUSE_BIT=5;//机房号占用5bit
    /*
        -1的源码   10000001
        -1的反码   11111110
        -1的补码   11111111
        -1左移12位= 1111 1111 0000 0000 0000
        -1       = 1111 1111 1111 1111 1111
        异或运算  = 0000 0000 1111 1111 1111=4095
        因此MAX_SEQUENCE的值为4095
     */
    private final static long MAX_SEQUENCE=-1L^(-1L<<SEQUENCE_BIT);
    //同理 MAX_MACHINE为31
    private final static long MAX_MACHINE=-1L^(-1L<<MACHINE_BIT);
    //同理 MAX_MACHINE_HOUSE值为31
    private final static long MAX_MACHINE_HOUSE=-1L^(-1L<<MACHINE_HOUSE_BIT);
    //机器ID
    private long machineID;
    //机房ID
    private long machineHouseID;
    private long lastTime=0;//上一次生成ID的时间戳
    private long sequence=0;//序列号,默认为0

    public SnowFlakeUtils(long machineID, long machineHouseID) {
        this.machineID = machineID;
        this.machineHouseID = machineHouseID;
    }

    public long getMachineID() {
        return machineID;
    }

    public void setMachineID(long machineID) {
        this.machineID = machineID;
    }

    public long getMachineHouseID() {
        return machineHouseID;
    }

    public void setMachineHouseID(long machineHouseID) {
        this.machineHouseID = machineHouseID;
    }


    /***
     *产生下一个ID
     * 用long型来表示我们生成的64位ID,
     * @return
     */

    public  synchronized long nextId(){
        if(machineHouseID>MAX_MACHINE_HOUSE ||machineID>MAX_MACHINE){
            throw new RuntimeException("机房ID或机器ID超出最大值");
        }
        //获取当前时间戳
        long currentTime=System.currentTimeMillis();
        //如果当前时间小于上一次生成ID的时间,抛出异常
        if(currentTime<lastTime){
            throw new RuntimeException("当前时间为异常值,请勿回拨时间!");
        }
        //如果当前时间等于上一次生成ID时间,说明是在同一毫秒中生成,那么序列号加一
        else if(currentTime==lastTime){
            /*
                MAX_SEQUENCE: 0000 1111 1111 1111
                            &
                        4096: 0001 0000 0000 0000
                           = 0
                 当sequence小于4095时, (sequence+1)&MAX_SEQUENCE=sequence+1
                 当sequence等于4095时,(sequence+1)&MAX_SEQUENCE=0
             */
            sequence= (sequence+1)&MAX_SEQUENCE;
            if(sequence==0L){
                //获取下一个毫秒值
                currentTime=getNextMill();
            }

        }else{
            //毫秒值不同,sequence初始为0
            sequence=0L;
        }
        //更新最近一次生成时间的毫秒值
        lastTime=currentTime;
        return (currentTime-START_STMP)<<22//左移22位 空出机房ID5位+机器ID5位+序列号12位
                |machineID<<12//左移12位 空出序列号12位
                |machineHouseID<<17//左移17位 空出机器ID5位+序列号12位
                |sequence;//序列号部分
    }

    /**
     * 获取下一个毫秒值
     * @return
     */
    private  long getNextMill() {
        long mill=System.currentTimeMillis();
        //如果当前时间等于上一次的时间则一直自旋
        while(mill==lastTime){
            mill=System.currentTimeMillis();
        }
        return mill;

    }

    /**
     * Main方法测试
     * @param args
     */

    public static void main(String[] args) {
        //初始化一个雪花算法工具类,设置机房ID和机器ID都为0
        SnowFlakeUtils snowFlakeUtils=new SnowFlakeUtils(0,0);
        for (int i = 0; i <100; i++) {
            //生成100个ID
            System.out.println(snowFlakeUtils.nextId());
        }

    }
}

雪花算法的优缺点!

(PS:2022年1月14日面试被问到了雪花算法的优缺点,总结一下):
优点:

  • 生成单调自增的唯一ID,在innodb的b+数表中顺序插入不会造成页的分裂,性能高。(uuid的话每个id是随机的,大量的随机IO效率不但低,还会使innodb页造成分裂和合并,使得插入效率低)
  • 生成64位id,只占用8个字节节省存储空间。
    缺点:
  • 每台数据库的本地时间都要设置相同,否则会导致全局不递增
  • 如果时钟回拨,会产生重复id。
    时钟回拨的解决方案
    存储每一毫秒产生的最后一个id的序列值,回拨到当前毫秒从序列值+1开始继续产生id即可!

你可能感兴趣的:(mysql)