分布式雪花算法工具类


SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。

SnowFlake算法的优点:

  • 高性能高可用:生成时不依赖于数据库,完全在内存中生成。
  • 容量大:每秒中能生成数百万的自增ID
  • ID自增:存入数据库中,索引效率高。

SnowFlake算法的缺点:
依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。

SnowFlakeUtil工具类

package com.gmaya.springbootrabbitmq.utils;

import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 雪花算法 分布式 id生成工具类
 * @author GMaya
 * @dateTime 2020/4/21 9:36
 */
@Slf4j
public class SnowFlakeUtil {
    private final long id;
    /**
     * 时间起始标记点,作为基准,一般取系统的最近时间
     */
    private final long epoch = 1587433423721L;
    /**
     * 机器标识位数
     */
    private final long workerIdBits = 10L;
    /**
     * 机器ID最大值: 1023
     */
    private final long maxWorkerId = -1L ^ -1L << this.workerIdBits;
    /**
     * 0,并发控制
     */
    private long sequence = 0L;
    /**
     * 毫秒内自增位
     */
    private final long sequenceBits = 12L;

    /**
     * 12
     */
    private final long workerIdShift = this.sequenceBits;
    /**
     * 22
     */
    private final long timestampLeftShift = this.sequenceBits + this.workerIdBits;
    /**
     * 4095,111111111111,12位
     */
    private final long sequenceMask = -1L ^ -1L << this.sequenceBits;
    /**
     * 记录产生时间毫秒数,判断是否是同1毫秒
     */
    private long lastTimestamp = -1L;

    /**
     * 传入机器id
     * @param id
     */
    private SnowFlakeUtil(long id) {
        if (id > this.maxWorkerId || id < 0) {
            throw new IllegalArgumentException(String.format("机器id不能大于%d或小于0", this.maxWorkerId));
        }
        this.id = id;
    }

    public synchronized long nextId() {
        // 获取当前时间毫秒数
        long timestamp = timeGen();
        if (this.lastTimestamp == timestamp) {
            //如果上一个timestamp与新产生的相等,则sequence加一(最大4095)
            this.sequence = this.sequence + 1 & this.sequenceMask;
            if (this.sequence == 0) {
                // 超过最大值进行按位与,结果为0,也就是当这一毫秒序号超过最大值,就会循环等待下一毫秒
                timestamp = this.tilNextMillis(this.lastTimestamp);
            }
        } else {
            this.sequence = 0;
        }

        // 如果时间回退,则报错或者返回-1,调用端进行判断
        if (timestamp < this.lastTimestamp) {
            log.error(String.format("时钟回退,拒绝 %d 毫秒内生成雪花id", (this.lastTimestamp - timestamp)));
            return -1;
        }

        this.lastTimestamp = timestamp;
        // 当前时间-初始时间,然后左移timestampLeftShift。
        // 将机器id左移workerIdShift
        // | 是按位或运算符,例如:x | y,只有当x,y都为0的时候结果才为0,其它情况结果都为1。
        return timestamp - this.epoch << this.timestampLeftShift | this.id << this.workerIdShift | this.sequence;
    }

    /**
     * 如果说几十年后id重复了,把机器id加1,再用几十年
     */
    private static SnowFlakeUtil flowIdWorker = new SnowFlakeUtil(1);


    public static long getSnowFlakeId() {
        return flowIdWorker.nextId();
    }

    /**
     * 等待下一个毫秒的到来, 保证返回的毫秒数在参数lastTimestamp之后
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 获得系统当前毫秒数
     */
    private static long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        //判断生成的记录是否有重复记录
        final Map<Long, Integer> map = new ConcurrentHashMap();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int s = 0; s < 2000; s++) {
                    long snowFlakeId = SnowFlakeUtil.getSnowFlakeId();
                    log.info("生成雪花ID={}",snowFlakeId);
                    Integer put = map.put(snowFlakeId, 1);
                    if (put != null) {
                        throw new RuntimeException("主键重复");
                    }
                }
            }).start();

        }
    }
}

文章作者: GMaya
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 GMaya !
评论
 上一篇
泰山版《Java开发手册》 泰山版《Java开发手册》
每次阿里推出最新的开发手册,自己都会去喵一眼,看一下。养成好习惯泰山版:新增5条日期时间规约;新增2条表别名sql规约;新增统一错误码规约。 官网地址:https://developer.aliyun.com/topic/java2020
2020-04-22
下一篇 
SpringBoot+RabbitMQ削峰入门 SpringBoot+RabbitMQ削峰入门
前言当大量的客户访问请求打到后端,去访问数据库等,瞬间会爆炸的。经过前端或者其他的方案进行限流外。还是有大量的请求,这个时候需要削峰了。 简单的削峰例子先设置小一点,然后循环往队列里面放消息,消费的时候延迟2秒 spring: ra
2020-04-20
  目录