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

高性能分布式全局ID生成器-雪花算法实现

概述

1.定义  

        一种分布式系统下用来生成全局唯一 ID 的工具

2.特点   

  1. 唯一性,满足优惠券需要唯一的 ID 标识用于核销
  2. 高可用,随时能够生成正确的 ID
  3. 高性能,生成 ID 的速度很快,每台机器 400w/s
  4. 递增性,生成的 ID 是逐渐变大的,有利于数据库形成索引
  5. 安全性,生成的 ID 无明显规律,可以避免间接泄露信息
  6. 生成量大,可满足优惠券订单数据量大的需求 

3.ID组成部分


代码实现

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author LiJianhong
 * @Title 雪花算法实现
 * @date 2025/2/7
 */
public class SnowflakeIdGenerator {

    /**
     * 基准纪元值:2025-01-01 08:00:00
     */
    private final long epoch = 1735689600000L;


    /**
     * 锁
     * */
    private Lock lock = new ReentrantLock();


    // 机器ID
    private final long workerId;
    // 机器ID的最大bit位长度
    private final long workerIdBits = 5L;
    // 最大的机器ID值,十进制数为为31
    private final long maxWorkerId = ~ (-1L << workerIdBits);


    // 数据中心ID
    private final long datacenterId;
    // 数据中心ID的最大bit位长度
    private final long datacenterIdBits = 5L;
    // 最大的数据中心ID值,十进制数为为31
    private final long maxDatacenterId = ~ (-1L << datacenterIdBits);


    // 自增序列号
    private long sequence = 0;
    // 序列号的最大bit位长度
    private final long sequenceBits = 12L;
    // 机器ID需要左移的位数12
    private final long workerIdShift = sequenceBits;
    // 数据中心ID需要左移的位数 = 12 + 5
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    // 时间戳需要左移的位数 = 12 + 5 + 5
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits ;
    // 序列号的掩码,十进制数为4095
    private final long sequenceMask = ~(-1L << sequenceBits);
    //最新时间戳快照
    private long lastTimestamp = -1L;
    // 时钟回拨最大容忍度,超过则抛异常
    private final long maxBackwardTime = 1000L;
    //
    private final long minBackwardTime = 100L;


    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        this.workerId = workerId;
        this.datacenterId = datacenterId;
        checkArgs();
    }

    public long nextId() {
        lock.lock();
        try {
            long timestamp = timeGen();
            // 时钟回拨处理
            if (timestamp < lastTimestamp) {
                handleClockBackward(timestamp);
            }

            if (lastTimestamp == timestamp) {
                //如果自增值达到最大值,则等待至下一毫秒
                sequence = (sequence + 1) & sequenceMask;
                if(sequence == 0){
                    timestamp = untilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0;
            }

            // 保存当前时间戳,作为方法下次被调用的上一个时间戳的快照
            lastTimestamp = timestamp;

            return ((timestamp - epoch) << timestampLeftShift)
                    | (datacenterId << datacenterIdShift)
                    | (workerId << workerIdShift)
                    | sequence;
        } finally {
            lock.unlock();
        }
    }

    private void checkArgs() {
        if (!(0 <= workerId && workerId <= maxWorkerId)) {
            throw new IllegalArgumentException("worker id must be in [0,31]");
        }
        if (!(0 <= datacenterId && datacenterId <= maxDatacenterId)) {
            throw new IllegalArgumentException("data center id must be in [0,31]");
        }
    }

    private void handleClockBackward(long timestamp) {
        long backstep = lastTimestamp - timestamp;
        if (backstep > maxBackwardTime) {
            throw new ClockBackwardException("Clock moved backwards. Refusing to generate id for " + backstep + " milliseconds");
        }
        // 使用回拨容忍度,增加回拨窗口
        if (backstep > minBackwardTime) {
            timestamp = untilNextMillis(lastTimestamp);
        }
    }


    private long untilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    // 时钟回拨异常
    static class ClockBackwardException extends RuntimeException {
        public ClockBackwardException(String message) {
            super(message);
        }
    } 

}

  

测试: 

public static void main(String[] args) throws InterruptedException {
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(23, 17);
        int size = 10000;
        Map<Long,Long> idMap = new ConcurrentHashMap<>();
        CountDownLatch latch = new CountDownLatch(size);           // 定义一个工具类,统计线程执行300次task的进度
        long start0 = System.currentTimeMillis();
        for(int i = 0; i < size; i ++) {
            new Thread(()->{
                long id = idGenerator.nextId();
                idMap.putIfAbsent(id, id);
                latch.countDown();
            }).start();
        }
        latch.await();
        System.out.println("并发测试:并发线程数:"+size+",生成id数量:"+idMap.size()+ ",耗时:"+(System.currentTimeMillis()-start0)+" ms");

        //压测该算法每秒生成id个数
        long start = System.currentTimeMillis();
        int count = 0;
        while(true){
            long id = idGenerator.nextId();
            long end = System.currentTimeMillis();
            if((end-start)>1000){
                break;
            }
            count ++;
        }
        System.out.println("性能测试:每秒生成id个数:"+count );
    }

输出: 

并发测试:并发线程数:10000,生成id数量:10000,耗时:729 ms
性能测试:每秒生成id个数:4094779

Process finished with exit code 0

结论: 

线程安全&分布式ID不重复:通过并发测试,10000个线程同时并发访问,生成了10000个不同的id,说明id生成是线程安全的,且id无重复。

高效性:理论上,本算法在单台服务器上最多可以生成1000 * 4096 = 4096000 / s,通过本次性能测试可以看出,分布式ID生成速率约为400w/s,值得注意的是,这仅仅是单台服务器的每秒生成率,而分布式集群的每个服务器都能达到该速率,根据算法入参workerId、datacenterId组合,可以算出最多能集群32×32=1024台机器,可通过调整集群机器数来缓解高并发生成ID的问题。


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

相关文章:

  • JavaScript:还在用if判断属性是否存在?哒咩(?.)用起来
  • 基于 Python(Flask)、JavaScript、HTML 和 CSS 实现前后端交互的详细开发过程
  • PWM波形输出
  • Qt元对象系统
  • (done) openMP学习 (Day13: 线程私有数据和如何支持库(Pi again),蒙特卡洛计算 Pi,线性同余法)
  • Faveo Helpdesk存在目录遍历漏洞(CVE-2024-37700)
  • 【设计模式】【行为型模式】模板方法模式(Template Method)
  • DeepSeek-R1 智能知识库系统使用指南
  • 上拉触底案例
  • 使用docker搭建FastDFS文件服务
  • 探头特征点创建
  • 数据库5(MySQL版)
  • Spring Boot单元测试实战指南
  • 蓝桥与力扣刷题(94 二叉树的中序遍历)
  • 【CubeMX-HAL库】STM32F407—无刷电机开环驱动
  • 从算法到落地:DeepSeek如何突破AI工具的同质化竞争困局
  • 【Rust中级教程】1.1. 指针概览(上):什么是指针、指针和引用的区别
  • [高等数学]不定积分的概念与性质
  • python笔记2--组合数据类型
  • 操作系统—进程与线程
  • DeepSeek多软件协同效应,产生的王炸组合
  • 智慧交通:如何通过数据可视化提升城市交通效率
  • Nexus 实战详解:企业级制品仓库管理
  • 从Open R1来看如何训练DeepSeek R1模型
  • FFmpeg获取RTSP视频流时,视频帧的格式
  • Stability AI 联合 UIUC 提出单视图 3D 重建方法SPAR3D,可0.7秒完成重建并支持交互式用户编辑。