雪花算法id

发布时间:2024年01月19日

1. 基本介绍

Snowflake(雪花算法)由Twitter公司开发,用于解决分布式系统中生成唯一标识的问题

说明:

雪花算法生成ID通常在Java中实现,而不是在数据库中。这是因为雪花算法的核心思想在分布式环境中生成全局唯一的ID,而不依赖于数据库。在数据库中实现可能会引入单点故障,而不符合分布式系统的设计原则

1.1. 雪花算法id组成

 1  41                             5        5           12
+-+---------------------------------+--------+------------+
|0| timestamp(ms)                  | datacenter | machine  | sequence  |
+-+---------------------------------+--------+------------+

时间戳(41位):

  • 占用41位,表示生成ID的时间戳。通常使用毫秒级别的时间戳。
  • 可表示的时间范围为:2^41毫秒,大约69年。

数据中心标识(5位):

  • 用于标识数据中心,通常手动配置。允许的最大值为2^5。

机器标识(5位):

  • 用于标识机器(节点),通常手动配置。允许的最大值为2^5。

序列号(12位):

  • 用于标识同一毫秒内生成的ID的序列号。如果同一毫秒内生成的ID数量超过4096(2^12),则会进行等待。
  • 可表示的序列号范围为:0 - 4095。

1.2 优点

  1. 全局唯一性: 雪花算法生成的ID在整个分布式系统中具有全局唯一性,不同节点生成的ID不会冲突
  2. 趋势递增: 生成的ID按照时间戳的顺序递增,有助于提高索引性能
  3. 简单高效: 雪花算法的实现相对简单生成ID的速度较不依赖于数据库或网络的通信

1.3 缺点

  1. 时间回拨问题: 如果系统时间发生回拨,可能导致在同一时间戳内生成的ID不是严格递增的,因此在处理时间回拨问题时需要额外的逻辑。
  2. 依赖机器时钟: 雪花算法依赖于机器的时钟,如果机器时钟不同步,可能导致生成的ID不准确。
  3. 有限的容量: 由于ID的结构和位数限制,雪花算法在短时间内生成的ID数量有上限,如果系统需要生成极高频率的ID,可能需要考虑其他方案。
  4. 数据中心和机器标识分配: 数据中心和机器标识需要手动分配,这需要一些管理工作。同时,分配的位数决定了数据中心和机器的最大数量。
  5. 不适合短时间内大量生成ID: 如果在短时间内需要大量生成ID,可能会遇到序列号用尽的问题,需要等待下一个时间戳。

2. 示例

2.1 手动实现

public class SnowflakeIdGenerator {
	
	// 起始的时间戳,可以根据实际情况调整
	private static final long START_TIMESTAMP = 1609459200000L; // 2021-01-01 00:00:00
	
	// 每部分占用的位数
	private static final long SEQUENCE_BIT = 12; // 序列号占用的位数
	private static final long MACHINE_BIT = 5;   // 机器标识占用的位数
	private static final long DATA_CENTER_BIT = 5; // 数据中心标识占用的位数
	
	// 每部分的最大值
	private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
	private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT);
	private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_BIT);
	
	// 每部分向左的位移
	private static final long MACHINE_LEFT = SEQUENCE_BIT;
	private static final long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
	private static final long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;
	
	// 数据中心ID(可根据实际情况配置)
	private final long dataCenterId;
	// 机器ID(可根据实际情况配置)
	private final long machineId;
	// 序列号
	private long sequence = 0L;
	// 上次生成ID的时间戳
	private long lastTimestamp = -1L;
	
	/**
	 * 初始化 SnowflakeIdGenerator 对象,并设置数据中心标识和机器标识
	 * @param dataCenterId 数据中心标识
	 * @param machineId 机器标识
	 */
	public SnowflakeIdGenerator(long dataCenterId , long machineId) {
		if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) { // 进行合法性检查,确保传入的数据中心标识和机器标识在合理范围内,否则抛出 IllegalArgumentException。
			throw new IllegalArgumentException("Data center ID can't be greater than " + MAX_DATA_CENTER_ID + " or less than 0");
		}
		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.dataCenterId = dataCenterId;
		this.machineId = machineId;
	}
	
	/**
	 * 生成雪花算法的唯一ID
	 * @return 最终的64位ID
	 */
	//确保在多线程环境下生成的ID是唯一的,防止并发冲突。如果您的应用是单线程或者在多线程环境下并发要求不高,可以适当简化同步逻辑。
	public synchronized long generateId() {
		long currentTimestamp = System.currentTimeMillis(); // 获取当前时间戳 currentTimestamp,以毫秒为单位。
		
		if (currentTimestamp < lastTimestamp) { // 检查当前时间戳是否小于上一次生成ID的时间戳 lastTimestamp,如果是,说明发生了时钟回拨,抛出 RuntimeException。
			throw new RuntimeException("Clock moved backwards. Refusing to generate ID for " + (lastTimestamp - currentTimestamp) + " milliseconds.");
		}
		
		if (currentTimestamp == lastTimestamp) { // 如果当前时间戳与上一次时间戳相等,则递增序列号 sequence,并通过位运算确保序列号不超过最大值。如果序列号归零,表示在同一毫秒内生成的ID数量超过限制,调用 waitNextMillis 方法等待下一毫秒。
			sequence = (sequence + 1) & MAX_SEQUENCE;
			if (sequence == 0) {
				currentTimestamp = waitNextMillis(currentTimestamp);
			}
		} else {
			sequence = 0L; // 如果当前时间戳大于上一次时间戳,将序列号重置为零。
		}
		
		lastTimestamp = currentTimestamp;
		
		// 将当前时间戳、数据中心标识、机器标识和序列号按照雪花算法的规则组合起来,生成最终的64位ID。
		return ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_LEFT) |
			(dataCenterId << DATA_CENTER_LEFT) |
			(machineId << MACHINE_LEFT) |
			sequence;
	}
	
	/**
	 * 时钟回拨策略:发生时钟回拨时等待下一个合适的时间戳。
	 * @param currentTimestamp 方法被调用时的当前时间戳
	 * @return 返回新的时间戳,以确保生成的ID是在递增的时间戳基础上生成的。
	 */
	private long waitNextMillis(long currentTimestamp) {
		long timestamp = System.currentTimeMillis();
		// 判断 timestamp 是否小于等于上一次生成ID的时间戳 lastTimestamp。如果是,说明当前时间戳仍然没有超过上一次生成ID的时间戳,继续在循环内等待。
		while (timestamp <= lastTimestamp) {
			timestamp = System.currentTimeMillis(); // 不断更新 timestamp 为当前时间戳,直到 timestamp 大于 lastTimestamp。
		}
		return timestamp; // 返回新的时间戳,以确保生成的ID是在递增的时间戳基础上生成的。
	}
    
    
    public static void main(String[] args) {
        // 根据情况决定
        long dataCenterId = 1L;  // 数据中心标识
        long machineId = 1L;     // 机器标识

        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(dataCenterId, machineId);

        // 使用 idGenerator 生成唯一ID
        long uniqueId = idGenerator.generateId();

    }
	
}

2.2 使用依赖

pom

<dependency>
    <groupId>com.github.beyondfengyu</groupId>
    <artifactId>snowflake-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

yml配置

snowflake:
  data-center-id: 1 # 数据中心ID,可以使用机器IP地址最后一段数字,范围为0-31
  machine-id: 1 # 机器ID,可以使用服务器编号,范围为0-31

业务类

@Service
public class UserService {
    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;
 
    public Long generateUserId() { // 调用即可
        return snowflakeIdWorker.nextId();
    }
}
文章来源:https://blog.csdn.net/m0_74021233/article/details/135667403
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。