高并发系统-分布式唯一ID生成(四)-雪花算法Snowflake

紧接着上文高并发系统-分布式唯一ID生成(三)-RedisID生成及应用

2. 生成方案

2.6 雪花算法Snowflake

Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。而 Java中64bit的整数是Long类型,所以在 Java 中 SnowFlake 算法生成的 ID 就是 long 来存储的。

  • 第1位占用1bit,其值始终是0,可看做是符号位不使用。
  • 第2位 开始的41位是时间戳,41-bit位可表示2^41个数,每个数代表毫秒,那么雪花算法可用的时间年限是(1L<<41)/(1000L360024*365)=69 年的时间。
  • 中间的10-bit位可表示机器数,即2^10 = 1024台机器,但是一般情况下我们不会部署这么台机器。如果我们对IDC(互联网数据中心)有需求,还可以将 10-bit 分 5-bit 给 IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,具体的划分可以根据自身需求定义。
  • 最后12-bit位是自增序列,可表示2^12 = 4096个数。

这样的划分之后相当于在一毫秒一个数据中心的一台机器上可产生4096个有序的不重复的ID。但是我们 IDC 和机器数肯定不止一个,所以毫秒内能生成的有序ID数是翻倍的。

1. 优点:

  1. 唯一性:雪花算法生成的ID具有全局唯一性,因为它使用了不同的组合方式来生成ID,包括时间戳、机器ID和序列号。
  2. 高性能:雪花算法生成ID的速度非常快,生成的ID是递增的,可以按照时间顺序进行排序。
  3. 高可用性:雪花算法是一种分布式算法,它可以在多台机器上同时生成ID,即使其中一台机器发生故障,也不会影响整个系统的ID生成。
  4. 可读性:雪花算法生成的ID是一个64位的整数,可以表示为一个长度较短的字符串,便于存储和传输。

2. 缺点

  1. 依赖机器时钟:雪花算法使用了时间戳作为生成ID的一部分,因此对于分布式系统来说,各个机器的时钟需要保持同步,否则可能会导致生成重复的ID。
  2. 有限的容量:雪花算法中的机器ID和序列号都是固定长度的,因此在极端情况下,可能会达到容量上限,无法继续生成唯一的ID。
  3. 不适合特定场景(安全):雪花算法生成的ID是递增的,如果对于一些需要保密性的场景,可能会暴露一些敏感信息,例如系统的使用频率和规模。

3. 应用场景

直接使用雪花算法的不多,大多借鉴其思想,并在其上进行改进。

比如百度 UidGenerator美团分布式ID生成系统 Leaf 中snowflake模式都是在 snowflake 的基础上演进出来的

4.实践

下面主要按照上述雪花算法思想实现

java 复制代码
public class SnowflakeDistributeId {
    /**
     * 开始时间截 (2015-01-01)
     */
    private final long INIT_TIME = 1420041600000L;

    /**
     * 机器id所占的位数
     */
    private final long WORKER_ID_BITS = 5L;

    /**
     * 数据标识id所占的位数
     */
    private final long DATACENTER_ID_BITS = 5L;

    /**
     * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
     */
    private final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);

    /**
     * 支持的最大数据标识id,结果是31
     */
    private final long MAX_DATACENTER_ID = -1L ^ (-1L << DATACENTER_ID_BITS);

    /**
     * 序列在id中占的位数
     */
    private final long SEQUENCE_BITS = 12L;

    /**
     * 机器ID向左移12位
     */
    private final long WORKER_ID_SHIFT = SEQUENCE_BITS;

    /**
     * 数据标识id向左移17位(12+5)
     */
    private final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;

    /**
     * 时间截向左移22位(5+5+12)
     */
    private final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;

    /**
     * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
     */
    private final long SEQUENCE_MASK = -1L ^ (-1L << SEQUENCE_BITS);

    private Byte lock = new Byte("0");

    /**
     * 工作机器ID(0~31)
     */
    private long workerId;

    /**
     * 数据中心ID(0~31)
     */
    private long datacenterId;

    /**
     * 毫秒内序列(0~4095)
     */
    private AtomicLong sequence = new AtomicLong(0L);

    /**
     * 上次生成ID的时间截
     */
    private AtomicLong lastTimestamp = new AtomicLong(-1L);


    /**
     * 构造函数
     *
     * @param workerId     工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public SnowflakeDistributeId(long workerId, long datacenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID));
        }
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATACENTER_ID));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }


    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public long nextId() {
        long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp.get()) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp.get() - timestamp));
        }

        //如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp.get() == timestamp) {
            synchronized (lock) {
                if ((sequence.incrementAndGet() & SEQUENCE_MASK) == 0) {
                    timestamp = tilNextMillis(lastTimestamp.get());
                }
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            sequence.set(0L);
        }

        //上次生成ID的时间截
        lastTimestamp.set(timestamp);

        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - INIT_TIME) << TIMESTAMP_LEFT_SHIFT)
                | (datacenterId << DATACENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence.get();
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
            LockSupport.parkNanos(100L);
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }
}

测试用例如下:

java 复制代码
@Slf4j
public class SnowflakeDistributeIdTest {

    @Test
    public void nextId() {
        SnowflakeDistributeId idWorker = new SnowflakeDistributeId(0, 0);

        for (int i = 0; i < 1000; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }

        ExecutorService executorService = Executors.newFixedThreadPool(5, new ThreadFactory() {
            private volatile int index = 0;

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "t" + (index++));
            }
        });
        CountDownLatch countDownLatch = new CountDownLatch(5);
        executorService.submit(new CreateNextIdJob(idWorker, 10, countDownLatch));
        executorService.submit(new CreateNextIdJob(idWorker, 10, countDownLatch));
        executorService.submit(new CreateNextIdJob(idWorker, 10, countDownLatch));
        executorService.submit(new CreateNextIdJob(idWorker, 10, countDownLatch));
        executorService.submit(new CreateNextIdJob(idWorker, 10, countDownLatch));
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            log.error("countDownLatch.await() error", e);
        }
        executorService.shutdown();
    }

    private static class CreateNextIdJob implements Runnable {
        private SnowflakeDistributeId idWorker;
        private int times;
        private CountDownLatch countDownLatch;

        public CreateNextIdJob(SnowflakeDistributeId idWorker, int times, CountDownLatch countDownLatch) {
            this.idWorker = idWorker;
            this.times = times;
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            while (times > 0) {
                long code = idWorker.nextId();
                log.info("threadName:{},code={}", Thread.currentThread().getName(), code);
                times--;
            }
            countDownLatch.countDown();
        }
    }
}

执行结果在多线程下能保证唯一性

xml 复制代码
2023-12-27 21:43:58.727|INFO |test|18|127.0.0.1|a9a042685bef41a3a1ab05c341a13fc5|threadName:t0,code=1189685130893983744|com.toby.dynamic.data.source.id.Snowflake.SnowflakeDistributeIdTest
2023-12-27 21:43:58.727|INFO |test|20|127.0.0.1|090e86c65e2e4c8eb8c13440eddc25b5|threadName:t2,code=1189685130893983745|com.toby.dynamic.data.source.id.Snowflake.SnowflakeDistributeIdTest
2023-12-27 21:43:58.727|INFO |test|21|127.0.0.1|527dc7fb1f32432ca1e255cc3c362ad0|threadName:t3,code=1189685130893983747|com.toby.dynamic.data.source.id.Snowflake.SnowflakeDistributeIdTest
2023-12-27 21:43:58.727|INFO |test|22|127.0.0.1|cd669d6f3f314e19b046efcffd7e1015|threadName:t4,code=1189685130893983748|com.toby.dynamic.data.source.id.Snowflake.SnowflakeDistributeIdTest
2023-12-27 21:43:58.727|INFO |test|19|127.0.0.1|7b3600403400442996ead7afdbc1d5f7|threadName:t1,code=1189685130893983746|com.toby.dynamic.data.source.id.Snowflake.SnowflakeDistributeIdTest
20

参考
分布式系统 - 全局唯一ID实现方案

相关推荐
ok!ko1 小时前
设计模式之原型模式(通俗易懂--代码辅助理解【Java版】)
java·设计模式·原型模式
2401_857622661 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
2402_857589361 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰2 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
哎呦没3 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
小飞猪Jay3 小时前
C++面试速通宝典——13
jvm·c++·面试
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
编程、小哥哥3 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring
IT学长编程4 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器