分布式id生成(1)

导读:本篇文章讲解 分布式id生成(1),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

全局唯一,自增,Long类型
1.自定义一个SystemClock

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

public class SystemClock {
    private final long period;
    private final AtomicLong now;

    private SystemClock(long period) {
        this.period = period;
        this.now = new AtomicLong(System.currentTimeMillis());
        scheduleClockUpdating();
    }

    /**
     * 尝试下枚举单例法
     */
    private enum SystemClockEnum {
        SYSTEM_CLOCK;
        private SystemClock systemClock;
        SystemClockEnum() {
            systemClock = new SystemClock(1);
        }
        public SystemClock getInstance() {
            return systemClock;
        }
    }

    /**
     * 获取单例对象
     * @return com.cmallshop.module.core.commons.util.sequence.SystemClock
     */
    private static SystemClock getInstance() {
        return SystemClockEnum.SYSTEM_CLOCK.getInstance();
    }

    /**
     * 获取当前毫秒时间戳
     * @return long
     */
    public static long now() {
        return getInstance().now.get();
    }

    /**
     * 起一个线程定时刷新时间戳
     */
    private void scheduleClockUpdating() {
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
            Thread thread = new Thread(runnable, "System Clock");
            thread.setDaemon(true);
            return thread;
        });
        scheduler.scheduleAtFixedRate(() -> now.set(System.currentTimeMillis()), period, period, TimeUnit.MILLISECONDS);
    }
}

2.创建分布式id生产工具类

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;

import java.net.Inet4Address;
import java.net.UnknownHostException;

/**
 * @author hc
 * @date 2022/11/9 16:12
 * @desc
 */
@Slf4j
public class SequenceUtils {
    /**
     * 初始偏移时间戳 1640966400L
     */
    private static final long OFFSET = 1652343192L;

    /**
     * 机器id (0~31 保留 32~63作为备份机器)
     */
    private static long WORKER_ID = 0;
    /**
     * 机器id所占位数 (5bit, 支持最大机器数 2^5 = 32)
     */
    private static final long WORKER_ID_BITS = 6L;
    /**
     * 数据中心id (0~15)
     */
    private static long DATACENTER_ID = 0;
    /**
     * 数据中心所占位数 (5bit, 支持最大数据中心数 2^5 = 32)
     */
    private static final long DATACENTER_ID_BITS = 4L;
    /**
     * 自增序列所占位数 (11bit, 支持最大每秒生成 2^11 = 2048)
     */
    private static final long SEQUENCE_ID_BITS = 11L;
    /**
     * 机器id偏移位数
     */
    private static final long WORKER_SHIFT_BITS = SEQUENCE_ID_BITS;
    /**
     * 机器id偏移位数
     */
    private static final long DATACENTER_SHIFT_BITS = SEQUENCE_ID_BITS + WORKER_ID_BITS;
    /**
     * 自增序列偏移位数
     */
    private static final long OFFSET_SHIFT_BITS = SEQUENCE_ID_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
    /**
     * 机器标识最大值 (2^5 / 2 - 1 = 15)
     */
    private static final long WORKER_ID_MAX = ((1 << WORKER_ID_BITS) - 1) >> 1;
    /**
     * 机器标识最大值 (2^5 = 32)
     */
    private static final long DATACENTER_ID_MAX = -1L ^ (-1L << DATACENTER_ID_BITS);
    /**
     * 备份机器ID开始位置 (2^5 / 2 = 16)
     */
    private static final long BACK_WORKER_ID_BEGIN = (1 << WORKER_ID_BITS) >> 1;
    /**
     * 自增序列最大值 (2^11 - 1 = 2047)
     */
    private static final long SEQUENCE_MAX = (1 << SEQUENCE_ID_BITS) - 1;
    /**
     * 发生时间回拨时容忍的最大回拨时间 (秒)
     */
    private static final long BACK_TIME_MAX = 1L;

    /**
     * 上次生成ID的时间戳 (秒)
     */
    private static long lastTimestamp = 0L;
    /**
     * 当前秒内序列 (2^16)
     */
    private static long sequence = 0L;
    /**
     * 备份机器上次生成ID的时间戳 (秒)
     */
    private static long lastTimestampBak = 0L;
    /**
     * 备份机器当前秒内序列 (2^16)
     */
    private static long sequenceBak = 0L;


    private static long dataCenterId;


    private static SequenceUtils idWorker;



    static {
        dataCenterId = Math.abs(getDataCenterId().hashCode()) % 16;
        //   dataCenterId = Math.abs(System.getProperty("dataCenterId").hashCode()) % 16;
        idWorker = new SequenceUtils(getWorkId(), dataCenterId);
    }

    /**
     * 私有构造函数禁止外部访问
     */
    private SequenceUtils() {
    }

    public SequenceUtils(long workerId, long datacenterId) {
        if (workerId > WORKER_ID_MAX || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0,workerId: %d.", WORKER_ID_MAX, workerId));
        }
        if (datacenterId > DATACENTER_ID_MAX || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0,datacenterId: %d.", DATACENTER_ID_MAX, datacenterId));
        }
        this.WORKER_ID = workerId;
        this.DATACENTER_ID = datacenterId;
        if (log.isInfoEnabled()) {
            log.info("initialize workerId:{},dataCenterId:{}", WORKER_ID, DATACENTER_ID);
        }
    }



    /**
     * 获取自增序列
     *
     * @return long
     */
    public static long nextId() {
        return idWorker.nextId(SystemClock.now() / 1000);
    }

    /**
     * 主机器自增序列
     *
     * @param timestamp 当前Unix时间戳
     * @return long
     */
    private static synchronized long nextId(long timestamp) {
        // 时钟回拨检查
        if (timestamp < lastTimestamp) {
            // 发生时钟回拨
            log.warn("时钟回拨, 启用备份机器ID: now: [{}] last: [{}]", timestamp, lastTimestamp);
            return nextIdBackup(timestamp);
        }

        // 开始下一秒
        if (timestamp != lastTimestamp) {
            lastTimestamp = timestamp;
            sequence = 0L;
        }
        if (0L == (++sequence & SEQUENCE_MAX)) {
            // 秒内序列用尽
//            log.warn("秒内[{}]序列用尽, 启用备份机器ID序列", timestamp);
            sequence--;
            return nextIdBackup(timestamp);
        }

        return ((timestamp - OFFSET) << OFFSET_SHIFT_BITS) | (DATACENTER_ID << DATACENTER_SHIFT_BITS) | (WORKER_ID << WORKER_SHIFT_BITS) | sequence;
    }

    /**
     * 备份机器自增序列
     *
     * @param timestamp timestamp 当前Unix时间戳
     * @return long
     */
    private static long nextIdBackup(long timestamp) {
        if (timestamp < lastTimestampBak) {
            if (lastTimestampBak - SystemClock.now() / 1000 <= BACK_TIME_MAX) {
                timestamp = lastTimestampBak;
            } else {
                throw new RuntimeException(String.format("时钟回拨: now: [%d] last: [%d]", timestamp, lastTimestampBak));
            }
        }

        if (timestamp != lastTimestampBak) {
            lastTimestampBak = timestamp;
            sequenceBak = 0L;
        }

        if (0L == (++sequenceBak & SEQUENCE_MAX)) {
            // 秒内序列用尽
//            logger.warn("秒内[{}]序列用尽, 备份机器ID借取下一秒序列", timestamp);
            return nextIdBackup(timestamp + 1);
        }

        return ((timestamp - OFFSET) << OFFSET_SHIFT_BITS) | (DATACENTER_ID << DATACENTER_SHIFT_BITS) | ((WORKER_ID ^ BACK_WORKER_ID_BEGIN) << WORKER_SHIFT_BITS) | sequenceBak;
    }

    /**
     * workId使用IP生成
     *
     * @return workId
     */
    private static Long getWorkId() {
        try {
            String hostAddress = Inet4Address.getLocalHost().getHostAddress();
            int[] ints = StringUtils.toCodePoints(hostAddress);
            int sums = 0;
            for (int b : ints) {
                sums += b;
            }
            return (long) (sums % 64);
        } catch (UnknownHostException e) {
            // 如果获取失败,则使用随机数备用
            return RandomUtils.nextLong(0, 63);
        }
    }

    /**
     * dataCenterId使用hostName生成
     *
     * @return dataCenterId
     */
    private static Long getDataCenterId() {
        try {
            String hostName = SystemUtils.getHostName();
            int[] ints = StringUtils.toCodePoints(hostName);
            int sums = 0;
            for (int i : ints) {
                sums = sums + i;
            }
            return (long) (sums % 16);
        } catch (Exception e) {
            // 失败就随机
            return RandomUtils.nextLong(0, 15);
        }
    }

    /**
     * 获取分布式id
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            long id = SequenceUtils.nextId();
            System.out.println(id);
        }
    }

    //    static {
//        // 初始化机器ID
//        // 伪代码: 由你的配置文件获取节点ID
//        long workerId = SnowflakeIdUtil.getWorkId();
//        if (workerId < 0 || workerId > WORKER_ID_MAX) {
//            throw new IllegalArgumentException(String.format("Worker Id范围: 0 ~ %d 目前: %d", WORKER_ID_MAX, workerId));
//        }
//        WORKER_ID = workerId;
//        // 初始化机器ID
//        // 伪代码: 由你的配置文件获取节点ID
//        long datacenterId = SnowflakeIdUtil.getDataCenterId();
//        if (datacenterId < 0 || datacenterId > DATACENTER_ID_MAX) {
//            throw new IllegalArgumentException(String.format("Datacenter Id范围: 0 ~ %d 目前: %d", DATACENTER_ID_MAX, workerId));
//        }
//        DATACENTER_ID = datacenterId;
//        if (log.isInfoEnabled()){
//            log.info("initialize workerId:{},dataCenterId:{}",WORKER_ID,DATACENTER_ID);
//        }
//    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/65731.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!