1、简述
在 Java 中生成唯一标识符(UUID)是开发过程中常见的需求,特别是在分布式系统或需要确保数据唯一性的场景中。Java 提供了多种方法生成唯一标识符,常用的方式包括使用 UUID 类、Snowflake 算法以及自定义生成逻辑。下面我将介绍这些方法,并为每种方法提供代码示例。
2、使用 UUID 类生成唯一标识符
Java 标准库中的 UUID 类可以轻松生成唯一标识符。UUID 是基于随机数的通用唯一识别码(Universally Unique Identifier),它生成的是 128 位长的数字,通常以 36 个字符(包括 4 个连字符)表示。
import java.util.UUID;
public class UUIDExample {
public static void main(String[] args) {
// 生成一个随机 UUID
UUID uuid = UUID.randomUUID();
System.out.println("生成的 UUID: " + uuid.toString());
}
}
特点:
- 优点:简单易用,生成速度快。
- 缺点:不能保证 UUID 的有序性,在高并发场景下可能导致性能问题。
3、基于 Snowflake 算法生成唯一标识符
Snowflake 是 Twitter 开源的分布式唯一 ID 生成算法,生成的 ID 是 64 位的数字。它的特点是高效且有序,特别适用于分布式系统中需要生成全局唯一且时间有序的 ID。
public class SnowflakeIdGenerator {
private final long workerId;
private final long datacenterId;
private final long sequence = 0L;
private final long twepoch = 1288834974657L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private long lastTimestamp = -1L;
private long sequenceNum = 0L;
public SnowflakeIdGenerator(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("Worker ID out of range");
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException("Datacenter ID out of range");
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
if (lastTimestamp == timestamp) {
sequenceNum = (sequenceNum + 1) & sequenceMask;
if (sequenceNum == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequenceNum = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequenceNum;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
for (int i = 0; i < 10; i++) {
System.out.println(idGenerator.nextId());
}
}
}
特点:
- 优点:生成的 ID 有序性好,适合分布式系统。
- 缺点:实现稍复杂,需要考虑时钟回拨等问题。
4、自定义时间戳加随机数生成唯一标识符
如果项目中对唯一标识的规则有特别的需求,可以根据业务逻辑来自定义生成策略。例如,使用当前时间戳和随机数的组合来生成唯一标识符。
import java.util.Random;
public class CustomIdGenerator {
public static String generateId() {
long timestamp = System.currentTimeMillis();
int randomNum = new Random().nextInt(10000);
return timestamp + String.format("%04d", randomNum);
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println("生成的自定义 ID: " + generateId());
}
}
}
特点:
- 优点:可以根据具体业务需求进行定制。
- 缺点:需要自己设计生成逻辑和保证唯一性。
5、基于数据库的分布式唯一 ID 生成
通过数据库的自增字段来生成唯一标识符是最简单的一种方式,适用于小规模应用场景。可以利用 MySQL、PostgreSQL 等数据库的自增 ID 功能,生成唯一标识符。
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_name VARCHAR(255) NOT NULL
);
在插入数据时,数据库会自动生成唯一的自增 ID。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class DatabaseIdGenerator {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
PreparedStatement stmt = conn.prepareStatement("INSERT INTO orders (order_name) VALUES (?)", PreparedStatement.RETURN_GENERATED_KEYS);
stmt.setString(1, "order_1");
stmt.executeUpdate();
ResultSet rs = stmt.getGeneratedKeys();
if (rs.next()) {
long generatedId = rs.getLong(1);
System.out.println("生成的数据库自增 ID: " + generatedId);
}
conn.close();
}
}
特点:
- 优点:实现简单,适合小规模应用。
- 缺点:扩展性差,数据库压力较大,不适合高并发环境。
6、基于 Redis 的分布式唯一 ID 生成
通过 Redis 自增功能生成唯一标识符也是一种常见的方法,特别适用于高并发场景。Redis 的自增操作是原子性的,因此非常适合分布式环境中生成全局唯一 ID。
import redis.clients.jedis.Jedis;
public class RedisIdGenerator {
private static Jedis jedis = new Jedis("localhost");
public static String generateId(String key) {
long id = jedis.incr(key); // 使用 Redis 自增生成 ID
return String.valueOf(id);
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println("生成的 Redis ID: " + generateId("order_id"));
}
}
}
特点:
- 优点:Redis 的自增操作效率高,适合高并发场景。
- 缺点:需要维护 Redis 集群,并且 Redis 宕机时需要考虑备份机制。
7、美团的 Leaf 分布式 ID 生成器
美团的 Leaf 是一个高可用、低延迟的分布式 ID 生成系统,支持两种模式:号段模式(Segment Model) 和 雪花算法模式(Snowflake Model)。Leaf 通过号段模式为每个业务预分配一段 ID,提高生成效率。
号段模式工作原理:
- Leaf 将一段连续的 ID 预分配给应用,避免每次生成 ID 都与数据库交互,减小数据库的压力。
- 每次只需要更新号段的最大值,可以实现高并发的 ID 生成。
引入 Leaf 的示例:
首先,需要在项目中集成 Leaf,通常通过引入 Leaf 的服务。Leaf 由两部分组成:Leaf Server 和 Leaf Core,Server 是提供 ID 生成服务的接口。假设使用 Maven 构建项目,首先需要引入 Leaf 的依赖:
<dependency>
<groupId>com.sankuai.inf.leaf</groupId>
<artifactId>leaf-core</artifactId>
<version>1.0.2</version>
</dependency>
号段模式生成 ID:
import com.sankuai.inf.leaf.common.Result;
import com.sankuai.inf.leaf.service.SegmentService;
import com.sankuai.inf.leaf.service.impl.SegmentServiceImpl;
public class LeafSegmentExample {
public static void main(String[] args) {
// 初始化 SegmentService 实例,通常通过 Spring 容器管理
SegmentService segmentService = new SegmentServiceImpl();
// 生成唯一标识符
Result result = segmentService.getId("order_id");
if (result.getStatus() == Result.Status.SUCCESS) {
System.out.println("生成的 ID: " + result.getId());
} else {
System.err.println("ID 生成失败");
}
}
}
特点:
- 优点:支持高并发,减少数据库操作,号段提前分配。
- 缺点:需要部署 Leaf 服务,维护相对复杂。
8、 百度的 UIDGenerator 分布式 ID 生成器
百度的 UIDGenerator 是基于雪花算法(Snowflake)的分布式 ID 生成方案,主要优化了时钟回拨的问题,并且支持配置不同的时间位和节点位,满足不同的业务场景。
UIDGenerator 工作原理:
- UID 由时间戳、工作机器号、序列号等部分组成。
- 它通过多种手段减少时钟回拨带来的问题,且生成的 ID 是递增的。
引入 UIDGenerator 的示例:
首先,引入 UIDGenerator 的依赖:
<dependency>
<groupId>com.baidu.fsg</groupId>
<artifactId>uid-generator</artifactId>
<version>1.0.0</version>
</dependency>
UIDGenerator 示例代码:
import com.baidu.fsg.uid.impl.CachedUidGenerator;
import org.springframework.beans.factory.annotation.Autowired;
public class UIDGeneratorExample {
@Autowired
private CachedUidGenerator uidGenerator;
public void generateUID() {
long uid = uidGenerator.getUID();
System.out.println("生成的 UID: " + uid);
}
}
特点:
- 优点:高性能,优化了时钟回拨问题,ID 生成速度快。
- 缺点:依赖性强,需要定制化配置,适合大规模分布式场景。
9、总结
在 Java 中生成唯一标识符有多种方式可选,开发者可以根据项目的需求选择合适的生成方法:
- 如果只是简单地需要唯一标识,UUID 是最快捷的选择。
- 在分布式系统中需要有序性且高性能的 ID 生成时,Snowflake 是推荐的选择。
- 如果有定制化需求,可以通过组合时间戳、随机数等生成自定义唯一标识符。
这篇博客可以结合项目场景讨论各个方法的优缺点,并附带相应的代码示例和输出。
评论区