学习雪花算法

发布于:2025-08-19 ⋅ 阅读:(14) ⋅ 点赞:(0)

package com.yupi.cicd.test;

import org.springframework.stereotype.Component;

/**
 * 雪花算法ID生成器
 * 技术亮点:解决分布式环境下的全局唯一ID生成问题
 */
@Component
public class SnowflakeIdGenerator {

    // 起始时间戳 (2024-01-01)
    private final long START_TIMESTAMP = 1704067200000L;

    // 机器ID位数
    private final long MACHINE_ID_BITS = 5L;
    // 数据中心ID位数
    private final long DATACENTER_ID_BITS = 5L;
    // 序列号位数
    private final long SEQUENCE_BITS = 12L;

    // 最大机器ID
    private final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS);
    // 最大数据中心ID
    private final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
    // 最大序列号
    private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);

    // 机器ID左移位数
    private final long MACHINE_ID_SHIFT = SEQUENCE_BITS;
    // 数据中心ID左移位数
    private final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
    // 时间戳左移位数
    private final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS + DATACENTER_ID_BITS;

    private long machineId;
    private long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator() {
        this(1L, 1L);
    }

    public SnowflakeIdGenerator(long machineId, long datacenterId) {
        if (machineId > MAX_MACHINE_ID || machineId < 0) {
            throw new IllegalArgumentException("机器ID超出范围");
        }
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException("数据中心ID超出范围");
        }
        this.machineId = machineId;
        this.datacenterId = datacenterId;
    }

    /**
     * 生成下一个ID
     * 技术难点:确保在高并发环境下ID的唯一性
     */
    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();

        // 时钟回拨检查
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("时钟回拨异常,拒绝生成ID");
        }

        // 同一毫秒内序列号递增
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0) {
                // 序列号溢出,等待下一毫秒
                timestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        // 组装ID:时间戳 + 数据中心ID + 机器ID + 序列号
        return ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
                | (datacenterId << DATACENTER_ID_SHIFT)
                | (machineId << MACHINE_ID_SHIFT)
                | sequence;
    }

    /**
     * 等待下一毫秒
     */
    private long waitNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}
package com.yupi.cicd.test;

public class testSonw {
    public static void main(String[] args) {
        System.out.println("hello world");
        SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator();

        for (int i = 0; i < 19; i++) {
            long l = snowflakeIdGenerator.nextId();
            System.out.println(l);
        }

    }
}

为什么不推荐使用数据库自增主键?也不推荐使用UUID作主键,用雪花算法会有什么问题?

自增
在分库分表的时候会出现问题

每一个表都有一个自增,这个时候ID就不能保证唯一性了

也可以用步长来解决问题(类似于分库分表的时候强制唯一)但是后续如果要扩容就是由三个表变成四个表,数据工作量很大,划分规则可能要变,旧数据怎么办

UUID

1.影响查询性能

UUID比较长占用的空间比较大,每行数据也大,那么同样的数据量需要page页 page页越多

索引树的高度也就越高,遍历次数也多,遍历page页也多,page也又是内存和磁盘交互的最最小单位,IO次数多

2.操作性能

UUID无序,非趋势递增

B+树内部株建索引是要进行id 的排序,添加一个新数据的时候,需要对数据的内容进行重排序

3.雪花算法

符号位为最高位,用long来存储,64位

是趋势递增的,是64bit的二进制,占用的空间小很多

一个典型的64位ID结构如下:

- 1位符号位(固定为0)

- 41位时间戳(毫秒级,可以使用约69年)(2的1次方/1000*60*60*24)

- 10位机器ID(可以配置,通常包括5位数据中心ID和5位机器ID)(2的10次方=1024台机器)

数据中心可以代表机房,比如使用云服务器,华南华北还有不用的机房

机器ID:每一个机房里面有不同的这个机器

- 12位序列号(同一毫秒内生成的序列号,每毫秒最多4096个)

也有三个问题

1.时间回拨的问题

系统时间变了不正确,如果改成过去的时候就乱了,ID就不是趋势递增的了

解决:
1.直接抛出异常,发现生成的id时间戳比之前的要小的时候直接抛出异常

2.采用备用方案:比如随机

2.机器ID的管理问题

集群部署的时候,100台机器,要去维护机器ID不重复是成本比较大

解决

1.机器ID一般通过配置文件去配置,

3.序列号一直是0的问题

分库分表场景数据不均匀

当为好为0的时候获取时间错的最后一位换一个


网站公告

今日签到

点亮在社区的每一天
去签到