• Snowflake 雪花算法 原理说明和注意事项


    先看图中雪花算法的结构

    第一段1位,固定0, 69年以后可能会用1,也就是说默认在一个系统中只能用最多69年,如果征用第一位可以使用139年。

    第二段41位,用时间毫秒数数表示41位大概是69年多,默认表示1971年1月1日到当前时间的毫秒数,有的雪花算法优化支持设定这个起算时间,我们可以把它指定位我们系统立项的时间,这样的好处在于可以使用完整的69年,第一位改成1,还可以用在用70年,一句话,可以用到死。

    第三段10位,2的10次方,同样1024 个标识符,可用最多支持1024 台节点的分布式器群。1024 多余中小公司来说太多了,有些多雪花算法的包装把它分成2段,workerId和datacenterId一段5位,也就是32个值。分别表示不同的服务和同一个服务的不同集群节点。

    第四段12位,2的12次方4096个,这个是内部加锁单调递增的。也就是每毫秒最多产出4096个,如果你的业务需要单机平均每毫秒生产的数据量大于4096,那么大概不适合学号算法,或者你改改默认的算法,把第三段用不完的借几位给第4段。如果你的系统只是阶段性的超过没毫秒4096,雪花算法依旧是可以支持的,比如当前毫秒如果不够用了,就自动使用下一个毫秒应该生成的id。我们也可以指定这个向后面的毫秒应该生成的id借最多能接几个毫秒的(注意的是,hutool里面有个bugs)。一般默认不指定,每毫秒4096 以后就重复了,hutool 的默认做法死重复以后就等待到下一毫秒。

    下面是一个hutool雪花算法的实现:默认使用时间是2010-11-4 9:42:54(DEFAULT_TWEPOCH),而不是1971年1月1日,默认是没毫秒生产id超过4096个以后就重复,但是可以通过设置timeOffset来指定最多向后面借多个毫秒的ID//

    package com.lomi.entity;

    /**
    * 描述
    *
    * @Author ZHANGYUKUN
    * @Date 2022/6/26
    */
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //


    import cn.hutool.core.date.SystemClock;
    import cn.hutool.core.util.IdUtil;
    import cn.hutool.core.util.StrUtil;
    import java.io.Serializable;
    import java.util.Date;

    public class Snowflake implements Serializable {
    private static final long serialVersionUID = 1L;
    public static long DEFAULT_TWEPOCH = 1288834974657L;
    public static long DEFAULT_TIME_OFFSET = 2000L;
    private static final long WORKER_ID_BITS = 5L;
    private static final long MAX_WORKER_ID = 31L;
    private static final long DATA_CENTER_ID_BITS = 5L;
    private static final long MAX_DATA_CENTER_ID = 31L;
    private static final long SEQUENCE_BITS = 12L;
    private static final long WORKER_ID_SHIFT = 12L;
    private static final long DATA_CENTER_ID_SHIFT = 17L;
    private static final long TIMESTAMP_LEFT_SHIFT = 22L;
    private static final long SEQUENCE_MASK = 4095L;
    private final long twepoch;
    private final long workerId;
    private final long dataCenterId;
    private final boolean useSystemClock;
    private final long timeOffset;
    private long sequence;
    private long lastTimestamp;

    public Snowflake() {
    this(IdUtil.getWorkerId(IdUtil.getDataCenterId(31L), 31L));
    }

    public Snowflake(long workerId) {
    this(workerId, IdUtil.getDataCenterId(31L));
    }

    public Snowflake(long workerId, long dataCenterId) {
    this(workerId, dataCenterId, false);
    }

    public Snowflake(long workerId, long dataCenterId, boolean isUseSystemClock) {
    this((Date)null, workerId, dataCenterId, isUseSystemClock);
    }

    public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) {
    this(epochDate, workerId, dataCenterId, isUseSystemClock, DEFAULT_TIME_OFFSET);
    }

    public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) {
    this.sequence = 0L;
    this.lastTimestamp = -1L;
    if (null != epochDate) {
    this.twepoch = epochDate.getTime();
    } else {
    this.twepoch = DEFAULT_TWEPOCH;
    }

    if (workerId <= 31L && workerId >= 0L) {
    if (dataCenterId <= 31L && dataCenterId >= 0L) {
    this.workerId = workerId;
    this.dataCenterId = dataCenterId;
    this.useSystemClock = isUseSystemClock;
    this.timeOffset = timeOffset;
    } else {
    throw new IllegalArgumentException(StrUtil.format("datacenter Id can't be greater than {} or less than 0", new Object[]{31L}));
    }
    } else {
    throw new IllegalArgumentException(StrUtil.format("worker Id can't be greater than {} or less than 0", new Object[]{31L}));
    }
    }

    public long getWorkerId(long id) {
    return id >> 12 & 31L;
    }

    public long getDataCenterId(long id) {
    return id >> 17 & 31L;
    }

    public long getGenerateDateTime(long id) {
    return (id >> 22 & 2199023255551L) + this.twepoch;
    }

    public synchronized long nextId() {
    long timestamp = this.genTime();
    if (timestamp < this.lastTimestamp) {
    if (this.lastTimestamp - timestamp >= this.timeOffset) {
    throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", new Object[]{this.lastTimestamp - timestamp}));
    }

    timestamp = this.lastTimestamp;
    }

    if (timestamp == this.lastTimestamp) {
    long sequence = this.sequence + 1L & 4095L;

    //默认是这么写的,这里是一个应该是一个bug,如果是向下一秒借的情况,不需要等到下一毫秒,直接返回就行了
    if ( sequence == 0L) {
    timestamp = this.tilNextMillis(this.lastTimestamp);
    }
    //修正的写法
    /* if ( sequence == 0L) {
    if( timeOffset == 0 ){
    timestamp = this.tilNextMillis(this.lastTimestamp);
    }else{
    timestamp = timestamp+1;
    }
    }*/

    this.sequence = sequence;
    } else {
    this.sequence = 0L;
    }

    this.lastTimestamp = timestamp;
    return timestamp - this.twepoch << 22 | this.dataCenterId << 17 | this.workerId << 12 | this.sequence;
    }

    public String nextIdStr() {
    return Long.toString(this.nextId());
    }

    private long tilNextMillis(long lastTimestamp) {
    long timestamp;
    for(timestamp = this.genTime(); timestamp == lastTimestamp; timestamp = this.genTime()) {
    }

    if (timestamp < lastTimestamp) {
    throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", new Object[]{lastTimestamp - timestamp}));
    } else {
    return timestamp;
    }
    }

    private long genTime() {
    return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis();
    }
    }

      

     //测试例子(测试使用hutool 生产409600个ID ,100毫秒默认生成的最大值,后面 new Snowflake(new Date(), 0, 0, false, 409600000) 最后指的的 偏移毫秒数,我随便指定的,只要大于  100 就行)

    	public static void main(String[] args) {
            
            Snowflake snowflake = new Snowflake(new Date(), 0, 0, false, 409600000);
            Set<Long> ids = new HashSet<>();
    
            Long a = System.currentTimeMillis();
            for(int i = 0;i<409600;i++   ){
                snowflake.nextId();
               // ids.add( snowflake.nextId() );
            }
            System.out.println( System.currentTimeMillis()-a );
            System.out.println( ids.size() );
    
    
    
    	}
    

    hutool 的写法 用时:

     

    修正后的耗时:

    正确性验证代码:生成 409600 个 id并且去重复,然后得到生成的个数

    	public static void main(String[] args) {
    
            Snowflake snowflake = new Snowflake(new Date(), 0, 0, false, 409600000);
            Set<Long> ids = new HashSet<>();
    
            Long a = System.currentTimeMillis();
            for(int i = 0;i<409600;i++   ){
                //snowflake.nextId();
                ids.add( snowflake.nextId() );
            }
            System.out.println( System.currentTimeMillis()-a );
            System.out.println( ids.size() );
    
    
    
    	}
    

     结果:

    hutool 版本 5.7.22

  • 相关阅读:
    BZOJ1954 Pku3764 The xor-longest Path
    BZOJ3697 采药人的路径
    BZOJ1468 Tree
    BZOJ2326 [HNOI2011]数学作业
    BZOJ2809 [Apio2012]dispatching
    BZOJ1334 [Baltic2008]Elect
    BZOJ2882 工艺
    BZOJ3791 作业
    BZOJ1224 [HNOI2002]彩票
    [noip2013]花匠
  • 原文地址:https://www.cnblogs.com/cxygg/p/16412736.html
Copyright © 2020-2023  润新知