1. 雪花算法概念

雪花算法(Snowflake)是一种生成唯一ID的算法,主要应用于分布式系统中。它可以在不依赖于数据库等其他存储设施的情况下,生成全局唯一的ID。

雪花算法生成的ID为64位整数,具体的格式如下:

0 | 0000000000 0000000000 0000000000 000000000 | 00000 | 00000 | 000000000000
其中,第1位为符号位,固定为0;接下来的41位为时间戳(毫秒级),记录了生成ID的时间;然后是10位的机器ID,5位数据中心ID和5位工作机器ID,用于标识不同的机器;最后是12位的序列号,用于表示在同一毫秒内生成的多个ID的顺序。

实际使用时,每台机器需要配置一个唯一的机器ID,以保证生成的ID不与其他机器生成的ID重复。同时,需要注意时钟回拨的问题,即当本地时钟发生回拨时,可能会导致生成的ID出现重复或者乱序的情况。

雪花算法是一种简单高效的分布式ID生成算法,可以满足大多数分布式系统生成唯一ID的需求。

1雪花算法的缺点

由于Snowflake算法生成的ID包含时间戳等信息,因此在使用时需要保证系统时间的准确性。如果系统时间发生回拨或者误差较大,可能会导致生成的ID出现重复或者乱序的问题。

2. springboot中使用雪花算法

2.1. 添加依赖

<dependency> <groupId>com.fasterxml.uuid</groupId> <artifactId>java-uuid-generator</artifactId> <version>4.0.0</version></dependency>

2.2.创建Snowflake ID生成器类

import com.fasterxml.uuid.Generators;import org.springframework.stereotype.Component;@Componentpublic class SnowflakeIdGenerator {private static final long DATA_CENTER_ID = 1L; // 数据中心ID,可根据实际情况修改private static final long WORKER_ID = 1L; // 工作机器ID,可根据实际情况修改/*数据中心ID数据中心ID用于标识不同的数据中心。它的取值范围为0~31,最多支持32个数据中心。在实际使用中,需要根据实际情况来设置数据中心ID,以确保在整个分布式系统中唯一。工作机器ID工作机器ID用于标识不同的工作节点或者进程。它的取值范围为0~31,最多支持32个工作节点或者进程。在实际使用中,需要根据实际情况来设置工作机器ID,以确保在整个分布式系统中唯一。为了避免工作机器ID和数据中心ID重复,我们在设置这些参数时需要特别小心并进行规划和分配。通常情况下,可以通过手动指定、配置文件或者自动化工具等方式来为每台机器分配唯一的工作机器ID和数据中心ID,以保证生成的ID足够唯一且不会重复。*/private final com.fasterxml.uuid.impl.TimeBasedGenerator generator;public SnowflakeIdGenerator() {this.generator = Generators.timeBasedGenerator(com.fasterxml.uuid.impl.RandomUtil.getSecureRandom());}public synchronized String nextId() {return generator.generate().toString();}}

在这个类中,我们使用timeBasedGenerator()方法创建了一个基于时间戳的UUID生成器,并将其包装成一个线程安全的单例。同时,我们还定义了数据中心ID和工作机器ID,并在构造函数中进行了初始化。

2.3.使用Snowflake ID生成器

在需要生成ID的地方,可以通过依赖注入的方式来使用SnowflakeIdGenerator类。例如:

@Servicepublic class UserServiceImpl implements UserService {@Autowiredprivate SnowflakeIdGenerator idGenerator;@Overridepublic void createUser(User user) {String userId = idGenerator.nextId();// TODO: 保存用户信息}}

在这个示例中,我们通过依赖注入的方式获取了SnowflakeIdGenerator实例,并调用其nextId()方法来生成一个新的ID。该方法返回的是一个字符串类型的UUID。

需要注意的是,由于Snowflake算法生成的ID包含时间戳等信息,因此在使用时需要保证系统时间的准确性。如果系统时间发生回拨或者误差较大,可能会导致生成的ID出现重复或者乱序的问题。

3. 数据中心ID与工作机器ID

3.1 设置数据中心id与工作机器id

获取工作机器ID和数据中心ID需要根据实际情况来进行调整。一般来说,可以通过以下几种方式来获取这些参数:

3.1.1 手动指定

在开发过程中,你可以手动为每台机器分配一个唯一的工作机器ID和数据中心ID,例如:

private static final long DATA_CENTER_ID = 1L; // 数据中心IDprivate static final long WORKER_ID = 1L; // 工作机器ID

这里我们将数据中心ID设置为1,工作机器ID也设置为1。当然,在实际应用中,需要根据机器数量和实际情况来调整这些参数,以保证生成的ID足够唯一且不会重复。

可以写1作为工作机器ID或数据中心ID,但是这样可能会导致生成的ID不够唯一,特别是在分布式系统中。为了保证生成的ID足够唯一且不会重复,建议将工作机器ID和数据中心ID设置为较大的值,例如16、32、64等。

Snowflake算法中规定,数据中心ID和工作机器ID的取值范围都是0~31,因此最多支持32个数据中心,每个数据中心最多支持32个工作机器。如果你的分布式系统中机器数量超过了这个限制,就需要使用更大的取值范围来确保生成的ID足够唯一。

3.1.2使用配置文件

在部署应用程序时,你可以使用配置文件来指定工作机器ID和数据中心ID。例如,在Spring Boot应用程序中,可以通过配置文件来指定这些参数:

snowflake.data-center-id=1snowflake.worker-id=1

然后在代码中读取这些配置,创建SnowflakeIdGenerator实例时将其传入即可。例如:

@Componentpublic class SnowflakeIdGenerator {private final com.fasterxml.uuid.impl.TimeBasedGenerator generator;public SnowflakeIdGenerator(@Value("${snowflake.data-center-id}") long dataCenterId,@Value("${snowflake.worker-id}") long workerId) {this.generator = Generators.timeBasedGenerator(com.fasterxml.uuid.impl.RandomUtil.getSecureRandom(), dataCenterId, workerId);}public synchronized String nextId() {return generator.generate().toString();}}

在这个示例中,我们通过构造函数来接收工作机器ID和数据中心ID,并使用它们来创建SnowflakeIdGenerator实例。同时,我们也可以在代码中对这些参数进行校验,以确保它们的合法性。

3.1.3使用自动化工具

除了手动分配和配置文件之外,你还可以使用一些自动化工具来为每台机器分配唯一的工作机器ID和数据中心ID。例如,可以使用ZooKeeper等分布式协调服务来为每台机器分配一个唯一的ID。

在实际应用中,可以使用ZooKeeper等分布式协调服务来为每台机器分配唯一的工作机器ID和数据中心ID。

具体实现方式如下:

在ZooKeeper集群中创建一个持久化的节点,例如/snowflake。

每个机器启动时,向该节点下创建一个临时有序节点,例如/snowflake/worker-。

每个机器获取自己创建的节点名,并解析出其中的序号。例如,如果节点名为/snowflake/worker-0000000010,则其序号为10。

将序号作为工作机器ID,同时将ZooKeeper的客户端ID(即会话ID)的低32位作为数据中心ID。

这样就可以保证每个机器的工作机器ID和数据中心ID都是唯一的,不会重复。同时,由于节点创建时是临时有序节点,因此当机器宕机或者断开连接时,其节点会被自动删除,不会对其他机器造成影响。

需要注意的是,使用ZooKeeper等分布式协调服务需要对ZooKeeper的配置和使用有一定的了解,同时也需要考虑网络延迟等因素对性能的影响。如果对ZooKeeper不熟悉或者想要更简单的解决方案,也可以使用第三方的分布式ID生成器库,例如Twitter的Snowflake-java或者百度的UidGenerator等。

3.2 使用三方库生成

3.2.1需要引入依赖才能使用第三方的分布式ID生成器库:

3.2.1.1 Snowflake-java库(已不再维护)

对于Snowflake-java库,需要在pom.xml文件中添加以下依赖:

<dependency><groupId>com.github.twitter</groupId> <!--com.twitter--><artifactId>snowflake</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
long datacenterId = new SnowflakeIdWorker(0, 0).getDatacenterId();long workerId = new SnowflakeIdWorker(0, 0).getWorkerId();/*创建了两个SnowflakeIdWorker对象,并从中获取数据中心ID和工作机器ID。在实际使用时,可以根据需要调整构造函数中的参数,以确保生成的ID足够唯一且不会重复*/

需要注意的是,该库目前已经不再维护,因此建议使用其他类似的库,例如美团的Leaf算法或者百度的UidGenerator等。

3.2.1.2 百度UidGenerator库

对于UidGenerator库,需要在pom.xml文件中添加以下依赖:

<dependency><groupId>org.n3r</groupId><artifactId>uid-generator</artifactId><version>2.3.4</version></dependency>
long datacenterId = UidGenerator.DEFAULT_DATA_CENTER_ID;long workerId = UidGenerator.getWorkerId();/*这里我们直接使用了默认的数据中心ID,并通过getWorkerId()方法获取了工作机器ID。在实际使用时,也可以根据需要调整这些参数,以确保生成的ID足够唯一且不会重复*/

需要注意的是,在使用第三方的分布式ID生成器库时,也需要根据实际情况来进行调整,并确保生成的ID足够唯一且不会重复。同时,这些库的实现机制和Snowflake算法可能有所不同,需要仔细研究其文档和源代码,以确保生成的ID符合我们的要求。

3.2.1.3 美团Leaf

在pom.xml文件中添加以下依赖:

<dependency><groupId>io.github.leaf-oss</groupId><artifactId>leaf-core</artifactId><version>${leaf.version}</version></dependency>

在配置文件中添加以下属性:

Leaf服务端地址:
leaf.server.host=127.0.0.1
leaf.server.port=8080

业务名:
leaf.name=myapp
在代码中读取这些配置,并通过SnowflakeSegmentIDGen类来生成唯一的ID。例如:

@Componentpublic class IdGenerator {private final SnowflakeSegmentIDGen idGen;public IdGenerator(@Value("${leaf.server.host}") String host, @Value("${leaf.server.port}") int port, @Value("${leaf.name}") String name) {SnowflakeConfig config = new SnowflakeConfig();config.setPort(port);config.setWorkerId(Integer.parseInt(NetUtils.getHostName().split("-")[1]));LeafSegmentServiceImpl leafSegmentService = new LeafSegmentServiceImpl(config);leafSegmentService.init();this.idGen = new SnowflakeSegmentIDGen(leafSegmentService, name);}public long nextId() {return idGen.nextId();}}

在这个示例中,我们通过读取配置文件中的参数来初始化SnowflakeSegmentIDGen类,并将其封装到IdGenerator类中。在需要生成ID时,只需要调用nextId()方法即可。

需要注意的是,Leaf库的实现机制和Snowflake算法可能有所不同,需要仔细研究其文档和源代码,以确保生成的ID符合我们的要求。同时,也需要根据实际情况来进行调整,并确保生成的ID足够唯一且不会重复。