什么是雪花算法?简述一下雪花算法的原理?
什么是雪花算法?
雪花算法(Snowflake Algorithm)是一种用于生成分布式系统中唯一ID的算法。起初由Twitter设计,用于解决分布式系统中唯一ID的需求。
这一算法的目标是生成全局唯一、有序的64位整数ID,以确保数据不冲突、不重复。
雪花算法生成的ID由以下部分组成:
1) 41位时间戳:精确到毫秒级,记录ID生成的时间。
2) 10位机器ID:用于标识不同机器或节点,可自定义分配。
3) 12位序列号:解决同一毫秒内生成多个ID的冲突问题。
图示:

雪花算法的原理
1.获取当前时间戳的41位,并将其左移22位,以获取时间戳部分。 2.获取机器ID,并将其左移12位,以获取机器ID部分。 3.生成序列号,同一毫秒内生成多个ID时,序列号递增。 4.合并时间戳、机器ID和序列号,形成64位整数ID。
手写java中的实现:
public class MySnowflakeIdGenerator { // ==============================字段============================== private final long workerId; // 机器ID private final long datacenterId; // 数据中心ID private long sequence = 0L; // 序列号 // 配置参数 private static final long MAX_WORKER_ID = 31L; private static final long MAX_DATACENTER_ID = 31L; private static final long WORKER_ID_BITS = 5L; private static final long DATACENTER_ID_BITS = 5L; private static final long SEQUENCE_BITS = 12L; // 位移偏移量 private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS; private static final long WORKER_ID_SHIFT = SEQUENCE_BITS + DATACENTER_ID_BITS; private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS; private long lastTimestamp = -1L; // 上次生成ID的时间戳 // ==============================构造函数============================== /** * 构造函数 * @param workerId 机器ID (0 - 31) * @param datacenterId 数据中心ID (0 - 31) */ public MySnowflakeIdGenerator(long workerId, long datacenterId) { // 检查workerId和datacenterId是否合法 if (workerId > MAX_WORKER_ID || workerId < 0) { throw new IllegalArgumentException("Worker ID must be between 0 and " + MAX_WORKER_ID); } if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) { throw new IllegalArgumentException("Datacenter ID must be between 0 and " + MAX_DATACENTER_ID); } // 设置workerId和datacenterId this.workerId = workerId; this.datacenterId = datacenterId; } // ==============================方法============================== /** * 生成唯一ID * @return 唯一ID */ public synchronized long generateUniqueId() { long timestamp = getCurrentTimestamp(); // 检查时钟回退情况 if (timestamp < lastTimestamp) { throw new RuntimeException("Clock moved backwards. Refusing to generate ID."); } // 同一毫秒内自增序列号 if (timestamp == lastTimestamp) { sequence = (sequence + 1) & ((1 << SEQUENCE_BITS) - 1); // 自增并掩码 if (sequence == 0) { // 序列号用完,等待下一毫秒 timestamp = getNextTimestamp(lastTimestamp); } } else { sequence = 0L; // 不同毫秒重置序列号 } lastTimestamp = timestamp; // 组合生成唯一ID return ((timestamp << TIMESTAMP_SHIFT) | (datacenterId << DATACENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence); } /** * 获取当前时间戳 * @return 当前时间戳(毫秒) */ private long getCurrentTimestamp() { return System.currentTimeMillis(); } /** * 等待下一毫秒,直到获得新的时间戳 * @param lastTimestamp 上次生成ID的时间戳 * @return 新的时间戳 */ private long getNextTimestamp(long lastTimestamp) { long timestamp = getCurrentTimestamp(); while (timestamp <= lastTimestamp) { timestamp = getCurrentTimestamp(); } return timestamp; } }
尝试调用:
public class TestCode { // ==============================测试============================== public static void main(String[] args) { // 创建一个雪花算法生成器,传入机器ID和数据中心ID MySnowflakeIdGenerator idGenerator = new MySnowflakeIdGenerator(0, 0); // 生成10个唯一ID并打印 for (int i = 0; i < 10; i++) { new Thread(()->{ long uniqueId = idGenerator.generateUniqueId(); System.out.println("Generated Unique ID: " + uniqueId); }).start(); } } }
源码中的代码示例
[该源码是第三方包 idworker 的源码,原本打算是看mybatis-puls里面的,但是发现就是用的第三方包,所以就直接看第三方包的源码吧]:
package com.imadcn.framework.idworker.algorithm; import java.util.Random; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Snowflake的结构如下(每部分用-分开): <br> * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br> * <b> · </b>1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0 <br> * <b> · </b>41位时间戳(毫秒级),注意,41位时间戳不是存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - * 开始时间戳)得到的值),这里的的开始时间戳,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序epoch属性)。41位的时间戳,可以使用69年 <br> * <b> · </b>10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId <br> * <b> · </b>12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间戳)产生4096个ID序号 加起来刚好64位,为一个Long型。 * <p> * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。 * <p> * 注意这里进行了小改动: <br> * <b> · </b>Snowflake是5位的datacenter加5位的机器id; 这里变成使用10位的机器id (b) <br> * <b> · </b>对系统时间的依赖性非常强,需关闭ntp的时间同步功能。当检测到ntp时间调整后,将会拒绝分配id * * @author imadcn * @since 1.0.0 */ public class Snowflake { private static final Logger logger = LoggerFactory.getLogger(Snowflake.class); /** * 机器ID */ private final long workerId; /** * 时间起始标记点,作为基准,一般取系统的最近时间,默认2017-01-01 */ private final long epoch = 1483200000000L; /** * 机器id所占的位数(源设计为5位,这里取消dataCenterId,采用10位,既1024台) */ private final long workerIdBits = 10L; /** * 机器ID最大值: 1023 (从0开始) */ private final long maxWorkerId = -1L ^ -1L << workerIdBits; /** * 序列在id中占的位数 */ private final long sequenceBits = 12L; /** * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095),12位 */ private final long sequenceMask = -1L ^ -1L << sequenceBits; /** * 机器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** * 时间戳向左移22位(5+5+12) */ private final long timestampLeftShift = sequenceBits + workerIdBits; /** * 并发控制,毫秒内序列(0~4095) */ private long sequence = 0L; /** * 上次生成ID的时间戳 */ private long lastTimestamp = -1L; /** * 100,000 */ private final int HUNDRED_K = 100_000; /** * sequence随机种子(兼容低并发下,sequence均为0的情况) */ private static final Random RANDOM = new Random(); /** * @param workerId 机器Id */ private Snowflake(long workerId) { if (workerId > maxWorkerId || workerId < 0) { String message = String.format("worker Id can't be greater than %d or less than 0", maxWorkerId); throw new IllegalArgumentException(message); } this.workerId = workerId; } /** * Snowflake Builder * * @param workerId 机器Id * @return Snowflake Instance */ public static Snowflake create(long workerId) { return new Snowflake(workerId); } /** * Snowflake Builder * * @param workerId 机器Id * @param lowConcurrency 是否低并发模式 * @return Snowflake Instance */ @Deprecated public static Snowflake create(long workerId, boolean lowConcurrency) { return create(workerId); } /** * 批量获取ID * * @param size 获取大小,最多10万个 * @return SnowflakeId */ public long[] nextId(int size) { if (size <= 0 || size > HUNDRED_K) { String message = String.format("Size can't be greater than %d or less than 0", HUNDRED_K); throw new IllegalArgumentException(message); } long[] ids = new long[size]; for (int i = 0; i < size; i++) { ids[i] = nextId(); } return ids; } /** * 获得ID * * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); // 如果上一个timestamp与新产生的相等,则sequence加一(0-4095循环); if (lastTimestamp == timestamp) { // 对新的timestamp,sequence从0开始 sequence = sequence + 1 & sequenceMask; // 毫秒内序列溢出 if (sequence == 0) { // 阻塞到下一个毫秒,获得新的时间戳 sequence = RANDOM.nextInt(100); timestamp = tilNextMillis(lastTimestamp); } } else { // 时间戳改变,毫秒内序列重置 sequence = RANDOM.nextInt(100); } // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 if (timestamp < lastTimestamp) { String message = String.format("Clock moved backwards. Refusing to generate id for %d milliseconds.", (lastTimestamp - timestamp)); logger.error(message); throw new RuntimeException(message); } lastTimestamp = timestamp; // 移位并通过或运算拼到一起组成64位的ID return timestamp - epoch << timestampLeftShift | workerId << workerIdShift | sequence; } /** * 等待下一个毫秒的到来, 保证返回的毫秒数在参数lastTimestamp之后 * * @param lastTimestamp 上次生成ID的时间戳 * @return 下一个毫秒 */ private long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 获得系统当前毫秒数 * * @return 获得系统当前毫秒数 */ private long timeGen() { return System.currentTimeMillis(); } }

浙公网安备 33010602011771号