当前位置: 首页 > article >正文

Require:基于雪花算法完成一个局部随机,全局离散没有热点切唯一的数值Id生成器。

1,雪花算法

【雪花算法】雪花算法(Snowflake Algorithm)是Twitter开源的用于生成唯一ID的算法,它可以在分布式系统中生成唯一的64位长整数ID。这种ID生成方式既保证了趋势递增,又保证了在不同数据中心、不同机器上生成的ID的唯一性。

  • 符号位:占用1位。

  • 时间戳:通常占用41位,表示从某个固定时间点(如1970年1月1日)起的毫秒数。这使得生成的ID具有时间排序性。

  • 机器ID:占用10位,表示生成ID的机器节点,以确保不同机器生成的ID不冲突。

  • 序列号:占用12位,允许同一毫秒内生成多个ID,通常用于支持高并发。

public class SnowflakeIdGenerator {
    private static final long EPOCH = 1640995200000L; // 自定义的开始时间戳(2022年1月1日)
    private static final long MACHINE_ID_BITS = 10L; // 机器ID的位数
    private static final long SEQUENCE_BITS = 12L; // 序列号的位数

    private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS); // 机器ID最大值
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); // 序列号最大值

    private long machineId; // 当前机器ID
    private long sequence = 0L; // 当前序列号
    private long lastTimestamp = -1L; // 上次生成ID的时间戳

    public SnowflakeIdGenerator(long machineId) {
        if (machineId > MAX_MACHINE_ID || machineId < 0) {
            throw new IllegalArgumentException("Machine ID can't be greater than " + MAX_MACHINE_ID + " or less than 0");
        }
        this.machineId = machineId;
    }

    public synchronized long generateId() {
        long timestamp = System.currentTimeMillis();

        // 如果当前时间小于上次生成ID的时间戳,说明系统时钟回拨,抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock is moving backwards. Rejecting requests until " + lastTimestamp);
        }

        // 如果在同一毫秒内,增加序列号
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK; // 使用位运算确保序列号循环
            // 如果序列号溢出,等待下一毫秒
            if (sequence == 0) {
                timestamp = waitForNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L; // 如果是新的毫秒,重置序列号
        }

        lastTimestamp = timestamp; // 更新上次生成ID的时间戳

        // 组合ID
        return ((timestamp - EPOCH) << (MACHINE_ID_BITS + SEQUENCE_BITS)) | (machineId << SEQUENCE_BITS) | sequence;
    }

    private long waitForNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis(); // 等待下一毫秒
        }
        return timestamp;
    }
}

2,雪花算法变种

【雪花算法-修正版】

/*
 * Long: 1,000,000,000,000,000,000-9,223,372,036,854,775,807,共19位
 * 雪花算法变种实现: {HHmmssSSS-9位}+{系统号-2位}+{毫秒递增seq-4位}+{随机数-3位}
 * */
public class TiDBRandomPrimaryKeyGenerator {

    //机器号
    private String machineId;
    public static AtomicInteger machineIndex = new AtomicInteger(0);
    //序列号
    private long sequence = 0L;
    //上一个时间戳,用于保证同一毫秒内序列号不重复。
    private long lastTimestamp = -1L;
    //序列号最大值
    private static final long SEQUENCE_MASK = 9999;

    private String pattern = "HHmmssSSS";

    public TiDBRandomPrimaryKeyGenerator() {
        machineId = getInerMachineId();
        this.sequence = (long) (Math.random() * SEQUENCE_MASK);
    }

    public synchronized long nextId() {
        Date currentDate = new Date();
        String dateTime = DateFormatUtils.format(currentDate, pattern);
        String seq = getInerSequence(currentDate.getTime());
        String random = generateRandomString(3);
        StringBuilder id = new StringBuilder(32);
        id.append(dateTime).append(machineId).append(seq).append(random);
        return Long.parseLong(String.valueOf(id));
    }

    private synchronized String getInerSequence(long timestamp) {
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
        }
        // 如果是同一毫秒内的时间戳
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) % SEQUENCE_MASK;
        } else {
            sequence = 0;
        }
        lastTimestamp = timestamp;
        return String.format("%04d", sequence);
    }

    private String getInerMachineId( ) {
        String machineId = String.format("%02d", machineIndex.intValue());
        this.machineIndex.incrementAndGet();
        return machineId;
    }

    public static String generateRandomString(int length) {
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            // 生成0到9之间的随机数,并转换为字符
            char randomChar = (char) (random.nextInt(10) + '0');
            sb.append(randomChar);
        }
        return sb.toString();
    }
}

3,基于DB自增

算法:{机器号}+{数据库id自增}+{伪随机}

@Service
public class UniqueIDGenerator {

    @Autowired
    PrefixRepository prefixRepository;
    // 队列最小容量阈值
    private static final int QUEUE_MIN_SIZE = 10000;
    private static final int QUEUE_CAPACITY = 12000;
    private static final int RANDOM_UPPER_LIMIT = 9999;
    // 递增前缀
    private long prefix;
    // 随机数队列
    private BlockingQueue<String> idQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
    // 线程安全锁
    private final Lock lock = new ReentrantLock();

    public String getUniqueId() throws InterruptedException {
        String uniqueId;
        uniqueId = idQueue.poll();
        if (uniqueId == null) {
            lock.lock();
            try {
                refillQueue();
            } finally {
                lock.unlock();
            }
            uniqueId = idQueue.take();
        }
        return uniqueId;
    }
    private long fetchDBPrefix() {
        Prefix prefix_from_db = prefixRepository.findById(1).get();
        this.prefix = prefix_from_db.getNext_value();
        prefix_from_db.setNext_value(prefix_from_db.getNext_value() + 1);
        prefixRepository.save(prefix_from_db);
        return prefix;
    }

    private void refillQueue() {
        //前1w个已经用完,重新获取前缀
        prefix = fetchDBPrefix();
        Random random = new Random();
        while (idQueue.size() < QUEUE_MIN_SIZE) {
            int randomPart = random.nextInt(RANDOM_UPPER_LIMIT + 1);
            String id = String.format("%014d", prefix) + String.format("%04d", randomPart);
            idQueue.offer(id);
        }
    }
}
public interface PrefixRepository  extends JpaRepository<Prefix, Integer> {}
@Entity
@Table(name = "unique_id_table")
public class Prefix{
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "next_value")
    private long next_value;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public long getNext_value() {
        return next_value;
    }

    public void setNext_value(long nextValue) {
        this.next_value = nextValue;
    }
}
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=sa
spring.datasource.password=

spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:/sql/schema.sql
spring.sql.init.data-locations=classpath:/sql/data.sql

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
DROP TABLE IF EXISTS `unique_id_table`;
CREATE TABLE `unique_id_table`
(
    `id` int,
    `next_value`   bigint
);

INSERT INTO `unique_id_table` (`id`, `next_value`) VALUES (1,1);

http://www.kler.cn/a/328300.html

相关文章:

  • 下一代以区域为导向的电子/电气架构
  • vscode remote-ssh直连docker容器
  • 【代码随想录|回溯算法排列问题】
  • Vue3 pinia使用
  • 使用 JavaScript 制作 To-Do List
  • 基于Java Springboot幼儿园管理系统
  • FileLink跨网文件交换:高效、安全、灵活的企业文件传输新方案
  • 力扣10.1
  • 5QI(5G QoS Identifier)
  • 《Linux从小白到高手》理论篇(二):Linux的目录结构和磁盘管理
  • 基于贝叶斯优化CNN-GRU网络的数据分类识别算法matlab仿真
  • python画图|自制渐变柱状图
  • 鸿蒙开发(NEXT/API 12)【穿戴设备信息查询】手机侧应用开发
  • 影院管理新篇章:小徐的Spring Boot应用
  • 低代码时代的企业信息化:规范与标准化的重要性
  • Redis: Sentinel哨兵监控架构及环境搭建
  • 通信工程学习:什么是LAN局域网、MAN城域网、WAN广域网
  • HarmonyOS Next应用开发——@build构建函数的使用
  • 每天一个数据分析题(四百九十一)- 主成分分析与因子分析
  • linux下recoketmq安装教程
  • JVM有哪些参数以及如何使用
  • 基于Java+SQL Server2008开发的(CS界面)个人财物管理系统
  • 【深度学习】(6)--图像数据增强
  • 信息安全工程师(16)密码学概况
  • HarmonyOS应用六之应用程序进阶一
  • 服装时尚与动漫游戏的跨界联动:创新运营与策划策略研究