文章目录
- 概述
- Snowflake算法结构
- 雪花算法的优缺点
- 解决唯一ID这个问题解决方法
- Snowflake算法生成ID的过程
- Java 实现示例
概述
Snowflake算法是Twitter开发的一种分布式唯一ID生成算法,用于解决在分布式系统中生成全局唯一ID的问题。该算法的核心思想是通过对64位的ID进行合理的位分配,保证在分布式环境下生成的ID具有全局唯一性和有序性。
Snowflake算法结构
64位ID结构:Snowflake算法生成的ID是一个64位的整数,其中各部分如下所示: 符号位(第1位):始终为0,用于保证生成的ID为正数。时间戳(41位):记录生成ID的时间戳,单位是毫秒,可以支持约69年的时间范围。机器ID(10位):标识机器的唯一ID,每台机器需要分配一个唯一的ID。序列号(12位):表示同一毫秒内生成的序列号,支持每台机器每毫秒生成4096个ID。
最高位是符号位,始终为0,不可用。
41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) 后得到的值,这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序SnowFlake类的START_STMP属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
10位的机器标识,10位的长度最多支持部署1024个节点。
12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
加起来刚好64位,为一个Long型。这个算法很简洁,但依旧是一个很好的ID生成策略。其中,10位器标识符一般是5位IDC+5位machine编号,唯一确定一台机器。
雪花算法一种生成分布式全局唯一 ID 算法,生成 ID 称为SnowflakeIDs。这种算法由 Twitter 创,并用于推文 ID。一个 Snowflake ID 有 64 位元。
第 1 位:Java 中 long 最高位符号位代表正负,正数 0,负数1
一般生成 ID 都为正数,所以默认为 0。下来前 41 位时间戳,表示了自选定时期以来毫秒数。下来 10 位代表计算机 ID,防止冲突。
其余 12 位代表每台机器上生成 ID 序列号,这允许在同一毫秒内创多个Snowflake ID。
雪花算法一般用来实现全局唯一的业务主键,解决分库分表之后主键id的唯一性问题。
而雪花算法就是一个比较符合这类特征的全局唯一算法,在美团的Leaf组件中也有用到。
它是由一个64位的long类型数字组成,分为四个部分。
第一部分,用1个bit表示符号位,一般情况下是0
第二部分,用41个bit来表示时间戳,使用系统时间的毫秒数
第三部分,用10个bit来记录工作机器id,这样就可以保证在多个服务器上生成的id的唯一性。
如果存在跨机房部署,我们还可以把它分成两个5bit,前面5个bit可以表示机房id,后面5个bit可以
表示机器id。
第四个部分,用12个bit表示序列号,表示一个递增序列,用来记录同毫秒内产生的不同id
雪花算法,就是根据这四个部分的规则,生成对应的bit位数据,然后组装在一起形成一个全局唯一id。
雪花算法英文翻译为 Snow Flake 算法,它是由Twitter开源的分布式 ID生成算法。主要应用于分库分表场
景中的全局ID作为业务主键,或者生成全局唯一的订单号。
那为什么要叫雪花算法呢?据相关研究表示,一般的雪花大约由10的19次方个水分子组成。在雪花形成过
程中,会形成不同的结构分支,所以说大自然中不存在两片完全一样的雪花,每一片雪花都拥有自己独特的形
状。雪花算法的意思是表示生成的ID如雪花一般独一无二。
实现原理
雪花算法那是一个由64个Bit(比特)位组成的long类型的数字。如图所示,它分为四个部分。
第一部分,用1个bit(比特)位来表示1个符号位,因为ID一般不会是负数,所以一般情况下就是0。
第二部分,用41个bit(比特)位来表示系统时间戳,这个时间戳就是系统时间记录的毫秒数。41个bit可以表示的
最大数字为2的41次方减1毫秒,换算成时间为69年。
第三部分,用10个bit(比特)位来技术工作机器的ID,用来保证多个服务器上生成ID的唯一性。如果存在跨机
房部署的情况,还可以把这10个比特位拆分为两组,每组5个bit(比特)位。前面的5个bit(比特)表示机房ID,
后面5个bit(比特)表示机器ID。10个比特位最大值是2的10次方,也就是最多1024台机器。
第四部分,用12个比特位来表示递增序列,用来记录同一毫秒内产生不同ID的能力。它的最大值为2的12次方减1,也就是4096。
雪花算法就是根据这四个部分的组成规则,生成对应Bit位的数据,然后组装到一起生成一个全局唯一ID。
雪花算法的优缺点
雪花算法主要有以下优点:
1)分布式系统内不会产生ID碰撞,效率高。
2)不需要依赖数据库等第三方系统,稳定性更高,可以根据自身业务分配bit位,非常灵活。
3)生成ID的性能也非常高,每秒能生成26万个自增可排序的ID。
优点:实现简单、高效,生成ID速度快,ID呈递增趋势,适合在分布式系统中大规模使用。
缺点:依赖系统时钟,如果系统时间回拨可能会导致ID重复;机器ID需要提前分配,限制了扩展性。
当然,雪花算法那也有缺点,因为它依赖机器时钟,如果机器时钟回拨,可能会导致ID重复。如果再分布式环境下,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况。当然,大部分情况下可以忽略这一缺点。
解决唯一ID这个问题解决方法
比如UUID、系统时间戳、Redis原子递增、数据库全局表
自增ID等等。但是在实际应用中,我们需要的ID除了唯一性以外,还需要满足以下特征:
1)、单调递增:保证下一个ID号一定大于上一个。
2)、保证安全:ID号需要无规则性,不能让别人根据ID号猜出我们的信息和业务数据量,增加恶意用户扒取数据的难度。
3)、含时间戳:需要记录系统时间戳
4)、高可用:发布一个获取分布式ID的请求,服务器至少要保证99.999%的情况下给创建一个全局唯一的分布式ID。
5)、低延迟:发布一个获取分布式ID的请求,要快,急速。
6)、高QPS:假如并发一口气10万个创建分布式ID请求同时杀过来,服务器要顶得住并且成功创建10万个分布式ID。
雪花算法就是一个比较符合这类特征的全局唯一算法。在很多大厂的全局ID组件中,都有用到,比如百度的UidGenerator、美团的Leaf算法等等。
Snowflake算法生成ID的过程
生成ID的过程:
当需要生成一个新的ID时,Snowflake算法根据当前时间戳、机器ID和序列号来生成一个唯一的ID。
首先获取当前时间戳,然后将时间戳左移22位,给机器ID留出10位的空间。
将机器ID左移12位,空出序列号的位数。
序列号部分初始化为0,如果同一毫秒内生成多个ID,序列号递增,超过4096则等待下一毫秒再生成。
ID的唯一性:
Snowflake算法通过合理分配时间戳、机器ID和序列号来保证生成的ID在分布式环境中的唯一性。不同机器的ID不同,同一毫秒内生成的ID序列号也不同,这样可以避免ID重复的情况发生。
总的来说,Snowflake算法是一种简单有效的分布式唯一ID生成算法,适用于各种分布式系统场景,但在使用时需要注意时钟同步和机器ID的分配等问题。
Java 实现示例
以下是一个简单的 Java 实现示例,用于生成 Snowflake 算法的唯一 ID:
public class SnowflakeIdGenerator {
private final long workerId;
private final long epoch = 1609459200000L; // 起始时间戳,这里以2021-01-01 00:00:00为起始时间
private long sequence = 0L;
private final long workerIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long timestampLeftShift = 22L;
private final long workerIdShift = 12L;
private final long sequenceMask = -1L ^ (-1L << 12L);
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("Worker ID can't be greater than " + maxWorkerId + " or less than 0");
}
this.workerId = workerId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - epoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
public static void main(String[] args) {
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1); // 传入当前机器的ID
System.out.println(idGenerator.nextId());
}
}
在上面的示例中,我们创建了一个名为 SnowflakeIdGenerator 的类,通过调用 nextId() 方法来生成唯一的 ID。在 main 方法中,我们实例化了 SnowflakeIdGenerator 并调用 nextId() 方法来生成一个 ID。请注意,这里的 workerId 需要根据实际情况设置为当前机器的唯一标识符。
总之,这段代码演示了如何使用 Java 实现基本的 Snowflake 算法来生成唯一的 ID。