雪花算法snowflake自定义带日期格式流水号及其配置类

查了一些雪花算法的资料,带日期格式且长度又满足我们现有长度的规范的不多,所以自己研究了下,直接上代码吧~~!~~

  • MySnowflake 雪花算法带日期格式实现,支持配置 数据中心数量, 应用数量, 每毫秒产生流水号个数, 注意下配置数值越大, 流水号长度越大, 返回流水号是字符串格式(因带日期就已经占用8位了)
package com.qzframework.common.uid;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Objects;

/**
 * 雪花算法 自定义版本 带年月日
 *
 * @author 庄金明
 */
public class MySnowflake {
    /**
     * 默认回拨时间,2S
     */
    public static long DEFAULT_TIME_OFFSET = 2000L;

    /**
     * 数据中心占用位数
     */
    private final long dataCenterIdBits;

    /**
     * 工作机器占用位数
     */
    private final long workerIdBits;

    /**
     * 每毫秒能够生产的流水号个数 占用位数
     */
    private final long sequenceBits;

    /**
     * 根据工作机器占用位数计算出能够支持的最大机器数,2 工作机器占用位数 次方
     */
    private final long maxWorkerId;

    /**
     * 根据数据中心占用位数计算出能够支持的最大数据中心数,2 数据中心占用位数 次方
     */
    private final long maxDataCenterId;
    private final long workerIdShift;
    private final long dataCenterIdShift;
    private final long timestampLeftShift;
    /**
     * 根据每毫秒能够生产的流水号个数 占用位数,计算出每毫秒能够生产几个流水号,2 占用位数 次方
     */
    private final long sequenceMask;

    /**
     * 当日当前机器生成的最大流水号的长度,每日循环使用的,所以最大长度是固定的
     */
    private final long datetimeMask;

    private final String idFormat;

    private final long workerId;
    private final long dataCenterId;
    /**
     * 允许的时钟回拨毫秒数
     */
    private final long timeOffset;
    /**
     * 当在低频模式下时,序号始终为0,导致生成ID始终为偶数<br>
     * 此属性用于限定一个随机上限,在不同毫秒下生成序号时,给定一个随机数,避免偶数问题。<br>
     * 注意次数必须小于{@link #sequenceMask},{@code 0}表示不使用随机数。<br>
     * 这个上限不包括值本身。
     */
    private final long randomSequenceLimit;

    /**
     * 自增序号,当高频模式下时,同一毫秒内生成N个ID,则这个序号在同一毫秒下,自增以避免ID重复。
     */
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public MySnowflake(long workerId, long dataCenterId, long dataCenterIdBits, long workerIdBits, long sequenceBits) {
        this(workerId, dataCenterId, DEFAULT_TIME_OFFSET, dataCenterIdBits, workerIdBits, sequenceBits);
    }

    public MySnowflake(long workerId, long dataCenterId, long timeOffset,
                       long dataCenterIdBits, long workerIdBits, long sequenceBits) {
        this(workerId, dataCenterId, timeOffset, 0, dataCenterIdBits, workerIdBits, sequenceBits);
    }

    public MySnowflake(long workerId, long dataCenterId, long timeOffset, long randomSequenceLimit,
                       long dataCenterIdBits, long workerIdBits, long sequenceBits) {
        this.dataCenterIdBits = dataCenterIdBits;
        this.workerIdBits = workerIdBits;
        this.sequenceBits = sequenceBits;
        this.maxWorkerId = -1L ^ (-1L << workerIdBits);
        this.maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
        this.workerIdShift = sequenceBits;
        this.dataCenterIdShift = sequenceBits + workerIdBits;
        this.timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
        this.sequenceMask = ~(-1L << sequenceBits);

        this.workerId = Assert.checkBetween(workerId, 0, maxWorkerId);
        this.dataCenterId = Assert.checkBetween(dataCenterId, 0, maxDataCenterId);
        this.timeOffset = timeOffset;
        this.randomSequenceLimit = Assert.checkBetween(randomSequenceLimit, 0, sequenceMask);
        this.datetimeMask = String.valueOf(
                (86400000L << timestampLeftShift)
                        | (this.dataCenterId << dataCenterIdShift)
                        | (this.workerId << workerIdShift)
                        | this.sequenceMask).length();

        this.idFormat = "%d%02d%02d%0" + this.datetimeMask + "d";
    }

    /**
     * 根据Snowflake的ID,获取机器id
     *
     * @param id snowflake算法生成的id
     * @return 所属机器的id
     */
    public long getWorkerId(String id) {
        long currId = Long.valueOf(id.substring(8));
        return currId >> workerIdShift & ~(-1L << workerIdBits);
    }

    /**
     * 根据Snowflake的ID,获取数据中心id
     *
     * @param id snowflake算法生成的id
     * @return 所属数据中心
     */
    public long getDataCenterId(String id) {
        long currId = Long.valueOf(id.substring(8));
        return currId >> dataCenterIdShift & ~(-1L << dataCenterIdBits);
    }

    /**
     * 根据Snowflake的ID,获取生成时间
     *
     * @param id snowflake算法生成的id
     * @return 生成的时间
     */
    public long getGenerateDateTime(String id) {
        int year = Integer.valueOf(id.substring(0, 4));
        int month = Integer.valueOf(id.substring(4, 6));
        int day = Integer.valueOf(id.substring(6, 8));
        long currId = Long.valueOf(id.substring(8));
        LocalDateTime localDateTime = LocalDateTime.of(year, month, day, 0, 0);
        return (currId >> timestampLeftShift) + toDayEpochMilli(localDateTime, ZoneOffset.ofHours(8));
    }

    /**
     * 下一个ID
     *
     * @return ID
     */
    public synchronized String nextId() {
        Instant instant = Instant.now();
        LocalDateTime now = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        long timestamp = instant.toEpochMilli();
        if (timestamp < this.lastTimestamp) {
            if (this.lastTimestamp - timestamp < timeOffset) {
                // 容忍指定的回拨,避免NTP校时造成的异常
                timestamp = lastTimestamp;
            } else {
                // 如果服务器时间有问题(时钟后退) 报错。
                throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));
            }
        }

        if (timestamp == this.lastTimestamp) {
            final long sequence = (this.sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
            this.sequence = sequence;
        } else {
            // issue#I51EJY
            if (randomSequenceLimit > 1) {
                sequence = RandomUtil.randomLong(randomSequenceLimit);
            } else {
                sequence = 0L;
            }
        }
        lastTimestamp = timestamp;
        long timeDiff = timestamp - toDayEpochMilli(now, ZoneOffset.ofHours(8));
        long nextId = (timeDiff << timestampLeftShift)
                | (dataCenterId << dataCenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
        return String.format(this.idFormat, now.getYear(), now.getMonthValue(), now.getDayOfMonth(), nextId);
    }

    /**
     * 获取当前日期(不包含时分秒)到1970.1.1的毫秒数
     *
     * @param localDateTime
     * @param offset
     * @return
     */
    private long toDayEpochMilli(LocalDateTime localDateTime, ZoneOffset offset) {
        Objects.requireNonNull(offset, "offset");
        long epochDay = localDateTime.toLocalDate().toEpochDay();
        long secs = epochDay * 86400;
        secs -= offset.getTotalSeconds();
        return secs * 1000;
    }

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

    /**
     * 生成时间戳
     *
     * @return 时间戳
     */
    private long genTime() {
        return Instant.now().toEpochMilli();
    }

    public static void main(String[] args) {
        MySnowflake mySnowflake = new MySnowflake(7L, 1L, 1L, 3L, 6L);
        String suid = "";
        long start = Instant.now().toEpochMilli();
        for (int i = 0; i < 100000; i++) {
            suid = mySnowflake.nextId();
            if (i == 0) {
                System.out.println(suid);
            }
        }
        System.out.println("生产 100000 耗时:" + (Instant.now().toEpochMilli() - start));
        System.out.println(mySnowflake.nextId());

        String uid = mySnowflake.nextId();
        System.out.println(mySnowflake.getGenerateDateTime(uid));
        System.out.println(mySnowflake.getWorkerId(uid));
        System.out.println(mySnowflake.getDataCenterId(uid));

    }
}
  • UidProperties 配置属性
package com.qzframework.common.uid;

import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

/**
 * 配置属性
 *
 * @author 庄金明
 */
@Data
@Accessors(chain = true)
@ConfigurationProperties(prefix = "qzframework.uid")
public class UidProperties {

    private long dataCenterIdBits = 1L;
    private long workerIdBits = 3L;
    private long sequenceBits = 6L;

    // ex:
    // * qzframework:
    // *   uid:
    // *     dataCenterIdBits: 1L
    // *     workerIdBits: 3L
    // *     sequenceBits: 6L
    // *     snowflakeIds:
    // *       - address: 10.10.10.1
    // *         workerId: 1
    // *         dataCenterId: 1
    private List<SnowflakeId> snowflakeIds;

    @Data
    @Accessors(chain = true)
    public static class SnowflakeId {
        /**
         * IP地址
         */
        private String address;
        /**
         * 终端ID (0-31) 单机配置0 即可。 集群部署,根据情况每个实例自增即可。
         */
        private Long workerId = 0L;
        /**
         * 数据中心ID (0-31) 单机配置0 即可。 集群部署,根据情况每个实例自增即可。
         */
        private Long dataCenterId = 0L;
    }
}
  • UidConfig 配置类
package com.qzframework.common.uid;

import cn.hutool.core.lang.Assert;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;

/**
 * uid配置
 *
 * @author 庄金明
 **/
@Configuration
public class UidConfig {

    @Bean
    public UidProperties uidProperties() {
        return new UidProperties();
    }

    @Bean
    public MySnowflake mySnowflake(UidProperties uidProperties) throws UnknownHostException {
        String hostName = InetAddress.getLocalHost().getHostName();
        Assert.notEmpty(hostName, "fail to get host name");

        List<UidProperties.SnowflakeId> snowflakeIds = uidProperties.getSnowflakeIds();
        InetAddress[] inetAddresses = InetAddress.getAllByName(hostName);
        UidProperties.SnowflakeId snowflakeId = snowflakeIds.stream().filter(
                item -> Arrays.stream(inetAddresses)
                        .anyMatch(inetAddress -> item.getAddress().equals(inetAddress.getHostAddress())))
                .findFirst().orElse(null);

        Assert.notNull(snowflakeId, "Configuration Error:qzbankframework.uid.snowflakeIds");

        return new MySnowflake(snowflakeId.getWorkerId(), snowflakeId.getDataCenterId(),
                uidProperties.getDataCenterIdBits(), uidProperties.getWorkerIdBits(), uidProperties.getSequenceBits());

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

庄先生~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值