雪花算法生成全局唯一ID的原理及其Java实现

账号生成算法

相信大家都有过如何生成账号的困惑,今天我来说一下我初学的时候常用的账号生成算法。

  • 随机数
  • mysql自增主键
  • 雪花算法

随机数

当时刚刚学习JavaSE,想的是直接随机数生成id,不知道mysql主键可以进行自增。

简单来说,随机生成id会有以下缺点:

  1. 每次生成一个id,需要去mysql中查询是否有重复的,这样数据量巨大的时候便会效率及其低下。
  2. 分布式环境下若代码比较烂可能会产生线程安全问题。

所以随机数是最笨的生成全局唯一id的方式。

mysql自增

我在之后的项目中使用mysql自增进行id的生成,但是久而久之我也发现了一些问题,问题如下:

  1. 主键直接暴露,可能会有安全问题。
  2. 主键不应该与任何业务逻辑有关。

mysql在分布式环境下也不合适。

推特开源的雪花算法

雪花算法其实我觉得没有绝对标准的版本,因为他可以根据自己的需要进行修改。

现在来讲述网络上传播最广的雪花算法的原理:

雪花算法生成的是一个64位的id,其中分为5个部分:

  • 1bit的固定正号(保持id为正)
  • 41bit的时间戳(用时间保持id自增)
  • 10bit的工作ID(机器id与机房id)
  • 12bit的序列号(本地计数器)
public class Snowflake {
    //机器ID
    private Long workId;
    //机房ID
    private Long datacenterId;
    //一毫秒生成的多个id的最新序号
    private Long sequence;
    //时间初始值
    private Long iniTime = 1585644268888L;
    //5位机器ID
    private Long workerIdBits = 5L;
    //5位机房ID
    private Long datacenterIdBits = 5L;
    //每毫秒产生id数的2的12次方
    private Long sequenceBits = 12L;
    //最大机器ID
    private Long maxWorkerId = -1L ^ (-1L << workerIdBits);
    //最大机房ID
    private Long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    private Long workerIdShift = sequenceBits;
    private Long datacenterIdShift = sequenceBits + workerIdBits;
    private Long timeStampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private Long sequenceMask = -1L ^ (-1L <<sequenceBits);
    //记录时间毫秒判断是否为同一毫秒
    private Long lastTimeStamp = -1L;

    public Snowflake(Long workerId, Long datacenterId, Long sequence) {
        if (workerId > maxWorkerId || maxWorkerId < 0) {
            throw new IllegalArgumentException(
                    String.format("机器ID不能超过%d和小于0", maxWorkerId)
            );
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(
                    String.format("机房ID不能超过%d和小于0", maxDatacenterId)
            );
        }
        this.workId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
    }

    //生成唯一ID
    public synchronized Long nextId() {
        Long timeStamp = System.currentTimeMillis();
        if (timeStamp < lastTimeStamp) {
            System.err.printf("错误!,时间已被回调");
            throw new RuntimeException(
                    String.format("时间已被回调%d秒", lastTimeStamp - timeStamp)
            );
        }
        //如果在同一毫秒
        if (lastTimeStamp.equals(timeStamp)) {
            sequence = (sequence+1)&sequenceMask;
            if(sequence==0){
                timeStamp = tilNextMillis(lastTimeStamp);
            }
        }else{
            sequence = 0L;
        }
        lastTimeStamp = timeStamp;
        return ((timeStamp-iniTime)<<timeStampLeftShift)|(datacenterId<<datacenterIdShift)|(workId<<workerIdShift)|sequence;
    }
    
    private Long tilNextMillis(Long lastTimeStamp){
        Long now = System.currentTimeMillis();
        while(now<=lastTimeStamp){
            now=System.currentTimeMillis();
        }
        return now;
    }
}

雪花算法的优缺点

优点:

  • 思路简单
  • 高性能
  • 低延迟

缺点:

  • 需要人部署

总结

其实分布式唯一ID生成算法有很多,包括雪花算法可以自己修改,主要是理解将时间戳与本地计时器保持唯一ID的思想。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值