侧边栏壁纸
博主头像
拾荒的小海螺博主等级

只有想不到的,没有做不到的

  • 累计撰写 194 篇文章
  • 累计创建 19 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

架构师:生成唯一标识的技术指南

拾荒的小海螺
2024-09-29 / 0 评论 / 0 点赞 / 13 阅读 / 10538 字

1、简述

在 Java 中生成唯一标识符(UUID)是开发过程中常见的需求,特别是在分布式系统或需要确保数据唯一性的场景中。Java 提供了多种方法生成唯一标识符,常用的方式包括使用 UUID 类、Snowflake 算法以及自定义生成逻辑。下面我将介绍这些方法,并为每种方法提供代码示例。

image-ebvh.png

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 是推荐的选择。
  • 如果有定制化需求,可以通过组合时间戳、随机数等生成自定义唯一标识符。

这篇博客可以结合项目场景讨论各个方法的优缺点,并附带相应的代码示例和输出。

0

评论区