ID生成-雪花算法

SnowFlake算法Twitter提出的一种算法,如果是MySQL数据库的主键采用BIGINT的话,那么他的取值范围是-2^63 到 2^63 ,即存储一个BIGINT类型需要64位二进制。雪花算法就是针对这64位进行设计。

在这里插入图片描述

  • 第1位二进制值固定位0,没有业务含义。
  • 第2~42位,共41位二进制,为时间戳,用于存入精确到毫秒数的时间。
  • 第43~52位,共10位二进制,为工作机器id位。
  • 第53~64位,共12位二进制,代表1ms内可以产生的序列号,当它表示整数时,取值区间为[0,4095]

这样的话,1ms内产生4096个编号,1秒就最多产生4096000个ID,这样的性能就能满足分布式系统的需求。

public class SnowFlakeWorker {
    //开始时间(这里使用2019年4月1日整点)
    private final static long START_TIME = 1554048000000L;
    //数据中心编号所占数位
    private final static long DATA_CENTER_BITS = 10L;
    //最大数据中心编号
    private final static long MAX_DATA_CENTER_ID = 1023;
    //序列编号占位位数
    private final static long SEQUENCE_BIT = 12L;
    //数据中心编号向左移12位
    private final static long DATA_CENTER_SHIFT = SEQUENCE_BIT;
    //时间戳向左移22位(10+12)
    private final static long TIMESTAMP_SHIFT = DATA_CENTER_BITS+DATA_CENTER_SHIFT;

    //最大生成序列号,这里为4095
    private final static long MAX_SEQUENCE = 4095;
    //数据中心ID(0~1023)
    private long dataCenterId;
    //毫秒内序列(0~4095)
    private long sequence = 0L;
    //上次生成ID的时间戳
    private long lastTimestamp = -1L;

    /*
    现在的微服务和分布式趋于中心化,所以不需要受理机器编号
    10位二进制全部用于数据中心
     */
    public SnowFlakeWorker(long dataCenterId){
        if (dataCenterId>MAX_DATA_CENTER_ID){
            String msg = "数据中心编号["+dataCenterId+"]超过最大允许值["+MAX_DATA_CENTER_ID+"]";
            throw new RuntimeException(msg);
        }

        if (dataCenterId<0){
            String msg = "数据中心编号["+dataCenterId+"]不允许小于0";
            throw new RuntimeException(msg);
        }
        this.dataCenterId = dataCenterId ;
    }


    /*
    获得下一个ID
     */
    public synchronized long nextId(){
        //获取当前时间
        long timestamp = System.currentTimeMillis();
        //如果是同一个毫秒时间戳的处理
        if(timestamp == lastTimestamp){
            sequence+=1;//序号+1
            if(sequence>MAX_SEQUENCE){
                sequence = 0;
                //等待到下一毫秒
                timestamp = tilNextMillis(timestamp);
            }
        }else {
            //修改时间戳
            lastTimestamp = timestamp ;
            //序列号重新开始
            sequence = 0;
        }
        //二进制的位运算,其中“<<”代表左移
        long result = ((timestamp-START_TIME)<<TIMESTAMP_SHIFT)
                |(this.dataCenterId<<DATA_CENTER_SHIFT)
                |sequence;
        return result;
    }

    /*
    阻塞到下一毫秒,直到获得新的时间戳
     */
    protected long tilNextMillis(long lastTimestamp){
        long timestamp;
        do{
            timestamp = System.currentTimeMillis();
        }while (timestamp>lastTimestamp);
        return timestamp;
    }


    public static void main(String[] args) {
        SnowFlakeWorker snowFlakeWorker = new SnowFlakeWorker(1000);
        for (int i = 0;i<10;i++)
            System.out.println(snowFlakeWorker.nextId());
    }
}

运行结果

347840848269836288
347840848269836289
347840848269836290
347840848269836291
347840848269836292
347840848269836293
347840848269836294
347840848269836295
347840848269836296
347840848269836297

代码解读
SnowFlakeWorker构造器就是简单的对数据中心的id进行校验

nextId 获取当前时间的毫秒数和上一次ID的毫秒数
如果是同一个毫秒数,就增加序列号,如果序列号大于规定的大小,就等待到下一毫秒,然后归零序列号。
如果不是同一个毫秒数,就是从零计数序列号。
最后返回结果。

通过左移操作将对应的数移动到正确的位置上
时间戳左移22位到高41位(工作机器编号10位,12位序列号)
机器号移动 12位(跨12位的序列号)
序列号不用动

通过异或将他们进行拼接起来就组成了最后的ID

ps
ID包含数据中心、时间戳和序列3种业务逻辑。
时间戳41位,这样就限制了使用时间为69年
此外实际情况,对于机器位和序列号不需要这么长,会引起二进制位的浪费。

通过上述的情况,我们可以删减其他部分的位数,然后可以多余出其他位数,可以加入发号机制,作为发号节点编号。
但是实际情况,ID会受到数据存储的限制,比如MySQL的BIGINT只能存储64位,如果拓展到DECIMAL,但是这种类型性能不如BIGINT,就如果实际可能需要更多位的话,可以采用其他方式进行限制,比如限流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值