Java 全局流水号生成

在开发过程中,我们经常会遇到需要生成全局唯一的流水号的需求。流水号是用来标识某个实体或者事件的唯一标识符,可以用于日志记录、订单号生成、分布式锁等场景。在 Java 中,我们可以使用不同的方式来生成全局流水号,本文将介绍几种常见的实现方法。

1. 使用 UUID

UUID(Universally Unique Identifier)是一种全球唯一的标识符。它是由标准的算法通过网卡 MAC 地址、时间戳、命名空间、随机数等数据计算而来,保证了生成的 ID 是全局唯一的。

在 Java 中,可以通过 java.util.UUID 类来生成 UUID。以下是一个示例代码:

import java.util.UUID;

public class UUIDGenerator {
    public static String generate() {
        return UUID.randomUUID().toString();
    }
}

使用该方法生成的 UUID 是一个包含 32 个字符的字符串,例如:6f845bc0-1f6b-11ec-9621-0242ac130002

优点:生成的 UUID 是全球唯一的,不依赖于任何外部环境。

缺点:UUID 是一个比较长的字符串,不太适合作为流水号使用。

2. 使用数据库自增 ID

在很多应用中,我们通常会使用数据库来存储数据。数据库中的自增 ID 是一种常见的生成全局唯一流水号的方式。

以下是一个使用 MySQL 数据库自增 ID 的示例代码:

import java.sql.*;

public class DatabaseIdGenerator {
    public static String generate() {
        String url = "jdbc:mysql://localhost:3306/mydatabase";
        String username = "root";
        String password = "password";
        String sql = "INSERT INTO id_generator (id) VALUES (null)";

        try (Connection connection = DriverManager.getConnection(url, username, password);
             Statement statement = connection.createStatement()) {
            statement.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
            ResultSet resultSet = statement.getGeneratedKeys();
            if (resultSet.next()) {
                return String.valueOf(resultSet.getLong(1));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return null;
    }
}

每次调用 generate() 方法,都会向数据库中插入一条记录,并返回生成的自增 ID。

优点:数据库自增 ID 是数据库原生支持的方式,保证了生成的 ID 的唯一性。

缺点:依赖于数据库,不适用于无数据库环境。

3. 使用分布式 ID 生成算法

在分布式系统中,生成全局唯一的流水号是一项非常具有挑战性的任务。传统的自增 ID 和 UUID 在分布式环境下都不能保证生成的 ID 的唯一性。为了解决这个问题,一些开源的分布式 ID 生成算法应运而生,例如 Twitter 的 Snowflake 算法、美团的 Leaf 算法等。

以下是一个使用 Snowflake 算法生成全局流水号的示例代码:

public class SnowflakeIdGenerator {
    private static final long EPOCH = 1632172800000L;
    private static final long WORKER_ID_BITS = 5L;
    private static final long SEQUENCE_BITS = 12L;
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);

    private final long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException("Worker ID must be between 0 and " + MAX_WORKER_ID);
        }
        this.workerId = workerId;
    }

    public synchronized long generate() {
        long timestamp = System.currentTimeMillis();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate ID.");
        }

        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - EPOCH) << (WORKER_ID_BITS + SEQUENCE_BITS))
                | (workerId