分布式唯一id方案之雪花算法

雪花算法(Snowflake)

  Snowflake 中文的意思为雪花,Snowflake算法 常被称为 雪花算法,是 Twitter 开源的分布式 ID 生成算法,是一种分布式主键ID生成的解决方案。

使用场景

  分布式唯一id生成,我们通俗讲的雪花算法是 64 字节的版本,当然如果有需求也可以自行实现其它版本。

优缺点

优点:

优点描述
高吞吐每秒能生成百万个不重复的id
高性能不依赖其它纯java代码就能实现

缺点:

缺点描述
时钟回拨会导致id重复由人为或其它原因导致的时钟回拨会导致id重复

雪花算法深入了解

  标准算法是64bit位表示,拆分如下,如果有需要可以重新划分,如只需要时间到秒,则时间可以少10bit位,可以把此10bit位分配到其它需要的位置。注意: 虽然可以重新划分,不过为满足分布式需求,最好结构不变,即要有符号位、时间、机器id、数据中心id、自增id这几部分
雪花算法

代码及解释

1. 机器id和数据中心id在分布式场景下需要保证唯一,否则生成的id可能会重复
2. 计算说明

-1L << x  # 将-1 左移x 位;
-1L ^ (-1L << x)  # 将-1 左移x 位之后最大值, 等价于  ~ (-1L << x)

3. 雪花算法生成的id长度
  雪花算法生成的长度一般为18或19位,因为64字节正好对应java的long型,保留第一位作为符号位,剩63bit位,生成的最大值就是19位。
4. 代码

// 包名替换
package xxxx;

import java.io.Serializable;
import java.util.Date;

/**
 * 雪花算法
 */
public class Snowflake implements Serializable {

    private static final long serialVersionUID = 1L;

    /** 起始时间戳 */
    private final long twepoch;

    /** 机器id字节数 */
    private final long workerIdBits = 5L;

    /** 数据中心字节数 */
    private final long dataCenterIdBits = 5L;
     最大支持机器节点数0~31,一共32个
    // 最大支持数据中心节点数0~31,一共32个

    /** 最大支持的workerId 0~31 */
    @SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /** 最大支持的数据中心id 0~31 */
    @SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
    private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);

    /** 序列号12bit位 */
    private final long sequenceBits = 12L;

    /** 序号最大值 */
    @SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 机器节点左移12bit位 */
    private final long workerIdShift = sequenceBits;

    /** 数据中心节点左移17bit位 */
    private final long dataCenterIdShift = sequenceBits + workerIdBits;

    /** 时间毫秒数左移22位 */
    private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;

    /** 初始化的workerId */
    private final long workerId;

    /** 初始化的数据中心id */
    private final long dataCenterId;

    /** 起始编号 */
    private long sequence = 0L;

    /** 上次的时间 */
    private long lastTimestamp = -1L;

    /**
     * 构造
     *
     * @param workerId     workerId
     * @param dataCenterId 数据中心id
     */
    public Snowflake(long workerId, long dataCenterId) {
        this(null, workerId, dataCenterId);
    }

    /**
     * @param epochDate    初始化时间起点(null表示默认起始日期),后期修改可能会导致id重复
     * @param workerId     工作机器节点id
     * @param dataCenterId 数据中心id
     */
    public Snowflake(Date epochDate, long workerId, long dataCenterId) {
        if (null != epochDate) {
            this.twepoch = epochDate.getTime();
        } else {
            // 2020-01-01 00:00:00
            this.twepoch = 1577808000000L;
        }
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("workerId %s 超过最大值 %s", workerId, maxWorkerId));
        }
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("dataCenterId %s 超过最大值 %s", dataCenterId, maxDataCenterId));
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }

    /**
     * 生成的id,获取机器id
     *
     * @param id 生成的id
     * @return workerId
     */
    public long getWorkerId(long id) {
        return id >> workerIdShift & ~(-1L << workerIdBits);
    }

    /**
     * 生成的id,获取数据中心id
     *
     * @param id 生成的id
     * @return 所属数据中心
     */
    public long getDataCenterId(long id) {
        return id >> dataCenterIdShift & ~(-1L << dataCenterIdBits);
    }

    /**
     * 生成的id,获取生成时间
     *
     * @param id 生成的id
     * @return 生成的时间
     */
    public long getGenerateDateTime(long id) {
        return (id >> timestampLeftShift & ~(-1L << 41L)) + twepoch;
    }

    /**
     * 生成的id,获取生成的序号
     *
     * @param id 生成的id
     * @return 序号id
     */
    public long getSequenceId(long id) {
        return id & ~(-1L << sequenceBits);
    }

    /**
     * 下一个ID
     *
     * @return ID
     */
    public synchronized long nextId() {
        long timestamp = genTime();
        if (timestamp < lastTimestamp) {
            // 如果服务器时间有问题(时钟后退) 报错。
            throw new IllegalStateException(String.format("时钟回退 %s", lastTimestamp - timestamp));
            //容许时钟回退
//            if (lastTimestamp - timestamp < 2000) {
//                // 容忍2秒内的回拨,避免NTP校时造成的异常
//                timestamp = lastTimestamp;
//            } else {
//                // 如果服务器时间有问题(时钟后退) 报错。
//                throw new IllegalStateException(String.format("时钟回退 %s", lastTimestamp - timestamp));
//            }
        }

        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence;
    }

    /**
     * 下一个ID(字符串形式)
     *
     * @return ID 字符串形式
     */
    public String nextIdStr() {
        return Long.toString(nextId());
    }

    // ------------------------------------------------------------------------------------------------------------------------------------ Private method start

    /**
     * 循环等待下一个时间
     *
     * @param lastTimestamp 上次记录的时间
     * @return 下一个时间
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = genTime();
        // 循环直到操作系统时间戳变化
        while (timestamp == lastTimestamp) {
            timestamp = genTime();
        }
        if (timestamp < lastTimestamp) {
            // 如果发现新的时间戳比上次记录的时间戳数值小,说明操作系统时间发生了倒退,报错
            throw new IllegalStateException(
                    String.format("时钟倒退 %sms", lastTimestamp - timestamp));
        }
        return timestamp;
    }

    /**
     * 生成当前时间戳
     *
     * @return 时间戳
     */
    private long genTime() {
        return System.currentTimeMillis();
    }
}
// 包名替换
package xxx;

import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;![请添加图片描述](https://i-blog.csdnimg.cn/direct/82ede48bde164fb2923735f81a1ea410.png)


/**
 * 雪花算法
 * 1. 代码中的8080需要换成实际服务的端口
 * 2. workerId和centerDataId 也可以改为读取配置文件
 */
public class SnowIdUtil {

    private SnowIdUtil() {
        //private
    }

    private static final Object LOCK = new Object();
    private static volatile Snowflake snowflake = null;

    private static final SecureRandom RANDOM = new SecureRandom();

    public static final int THIRTY_ONE = 31;
    public static final int THIRTY_TWO = 32;


    /**
     * 下个id 一毫秒最大 4096个,如果超过慎用
     *
     * @return id
     */
    public static long nextId() {
        if (snowflake == null) {
            synchronized (LOCK) {
                if (snowflake == null) {
                    snowflake = new Snowflake(getWorkId(), getDataId());
                }
            }
        }
        return snowflake.nextId();
    }

    @SuppressWarnings("java:S3252")
    public static int getWorkId() {
        try {
            //8080为服务器端口,实际需要修改为动态获取
            String s = Inet4Address.getLocalHost().getHostAddress().concat(":").concat(String.valueOf(8080));
            return getHostId(s, THIRTY_ONE);
        } catch (UnknownHostException var1) {
            return (RANDOM.nextInt(THIRTY_TWO));
        }
    }

    @SuppressWarnings("java:S3252")
    public static int getDataId() {
        try {
            return getHostId(Inet4Address.getLocalHost().getHostName(), THIRTY_ONE);
        } catch (UnknownHostException var1) {
            return (RANDOM.nextInt(THIRTY_TWO));
        }
    }

    /**
     * 获取时间
     *
     * @param id snowflake算法生成的id
     * @return 时间
     */
    @SuppressWarnings("java:S3252")
    public static LocalDateTime getDateTime(long id) {
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(snowflake.getGenerateDateTime(id)), ZoneId.systemDefault());
    }

    /**
     * 根据Snowflake的ID,获取序号id
     *
     * @param id snowflake算法生成的id
     * @return 序号id
     */
    @SuppressWarnings("java:S3252")
    public static long getSequenceId(long id) {
        return snowflake.getSequenceId(id);
    }

    private static int getHostId(String s, int max) {
        byte[] bytes = s.getBytes();
        int sums = 0;
        byte[] var4 = bytes;
        int var5 = bytes.length;

        for (int var6 = 0; var6 < var5; ++var6) {
            int b = var4[var6];
            sums += b;
        }
        return sums % (max + 1);
    }

    public static void main(String[] args) {
        long l = nextId();
        LocalDateTime dateTime = getDateTime(l);
        long sequenceId = getSequenceId(l);
        System.out.println(dateTime+" "+ sequenceId);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值