在数据库架构设计中,分库分表是一种常见的优化策略,用于解决单表数据量过大导致的查询性能下降问题。然而,分库分表后如何处理主键ID成为了一个关键问题。因为每个表如果都从1开始累加,会导致主键冲突,因此需要生成全局唯一的ID来支持。以下是几种常见的ID生成方案。
UUID(Universally Unique Identifier)
UUID是一种全局唯一标识符,基于算法生成,不依赖于数据库。UUID的长度为128位,通常以36位长度的十六进制数字字符串表示(包括四个连字号)。UUID的优点在于全局唯一性,无需担心主键冲突的问题,适用于分布式系统或需要跨多个数据库实例进行数据同步的场景。然而,UUID作为主键存在以下缺点:
- 存储空间大:UUID长度为36位,占用存储空间较大。
- 无序性:UUID是无序的,新增数据时如果采用btree索引,每次新增一条数据都需要重新排序,影响性能。
- 可读性差:UUID的随机性导致其不易读懂。
使用示例:
import java.util.UUID;
public class ExampleEntity {
private String id;
private String name;
public ExampleEntity(String name) {
this.id = UUID.randomUUID().toString();
this.name = name;
}
// 省略 getter 和 setter 方法
}
数据库自增ID
数据库自增ID是一种简单且常用的主键生成方式。MySQL使用auto_increment
实现,Oracle则使用序列。然而,分库分表后,如果每个表都从1开始累加,会导致主键冲突。为了避免这种情况,可以采用以下两种方式:
- 单独创建主键表维护唯一标识:通过执行SQL获取唯一标识,然后添加到某个分表中。
- 设置不同步长:在分布式环境中,设置不同数据库的自增ID步长,确保全局唯一性。例如,设置实例1步长为1,实例2步长为2。
使用示例:
-- 创建主键表
CREATE TABLE id_generator (
id BIGINT AUTO_INCREMENT PRIMARY KEY
);
-- 获取唯一ID
INSERT INTO id_generator () VALUES ();
SELECT LAST_INSERT_ID();
Redis生成ID
Redis是一个高性能的键值存储系统,支持原子性操作如INCR
和INCRBY
,因此可以用来生成全局唯一的ID。Redis生成ID的优点在于不依赖于数据库,性能优越,且支持高并发场景。然而,引入新的组件会增加系统的复杂度,可用性也会受到影响。
使用示例:
import redis.clients.jedis.Jedis;
public class RedisIdGenerator {
private Jedis jedis;
private String key;
public RedisIdGenerator(String host, int port, String key) {
this.jedis = new Jedis(host, port);
this.key = key;
}
public long generateId() {
return jedis.incr(key);
}
// 省略其他方法
}
Snowflake算法
Snowflake算法是由Twitter开源的一种分布式ID生成算法,它将64位的long型ID分为四个部分:
- 符号位(1位):始终为0,表示正数。
- 时间戳(41位):精确到毫秒级,支持69年的唯一性。
- 工作机器ID(10位):分为数据中心ID和工作机器ID各5位,支持最多1024个节点。
- 序列号(12位):同一毫秒内生成的不同ID,支持每个节点每毫秒产生4096个唯一ID。
Snowflake算法生成的ID有序且唯一,适用于分布式系统,但依赖于机器时钟,如果时钟回拨会导致ID重复。
使用示例:
import java.util.concurrent.atomic.AtomicLong;
public class SnowflakeIdGenerator {
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 workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return System.currentTimeMillis();
}
// 省略其他方法
}
美团Leaf分布式ID生成系统
Leaf是美团点评开源的分布式ID生成系统,支持多种ID生成策略,包括UUID、Snowflake、Segment等。Leaf的设计理念是简单、高效、安全和可扩展,旨在解决分布式系统中的ID生成问题。Leaf生成的ID具有全局唯一性、趋势递增、单调递增、信息安全等特点,但需要依赖关系数据库、Zookeeper等中间件。
使用示例:
import com.meituan.leaf.core.LeafServer;
import com.meituan.leaf.core.LeafServerApplication;
public class LeafServerExample {
public static void main(String[] args) {
LeafServerApplication leafServerApplication = new LeafServerApplication();
leafServerApplication.setZookeeperAddress("127.0.0.1:2181");
leafServerApplication.setDatabaseUrl("jdbc:mysql://localhost:3306/leaf");
leafServerApplication.setDatabaseUser("root");
leafServerApplication.setDatabasePassword("password");
LeafServer leafServer = new LeafServer(leafServerApplication);
leafServer.start();
}
}
总结
方案 | 描述 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
UUID | 全局唯一标识符 | 全局唯一,无需额外中间件 | 性能较低,占用存储空间较大 | 需要全局唯一标识符但不关心性能和存储空间的场景 |
数据库自增ID | 数据库自带的自增ID | 简单易用,ID有序递增 | 分库分表后需要特殊处理,存在单点故障风险 | 单库单表环境 |
Redis生成ID | 利用Redis的原子操作生成ID | 性能优越,ID有序递增 | 增加了系统复杂度,需要依赖Redis中间件 | 对性能要求较高,且可以接受增加系统复杂度的场景 |
Snowflake算法 | 基于Twitter的分布式ID生成算法 | 性能优越,ID唯一且有序,可自定义ID长度和前缀 | 需要依赖额外的中间件,ID长度受限 | 大规模分布式系统,需要全局唯一且有序的ID |
美团Leaf | 美团开源的分布式ID生成系统,支持多种ID生成方式,如号段模式、Snowflake模式等 | 性能优越,支持多种ID生成方式,灵活性高,ID唯一且有序 | 需要依赖额外的中间件,配置和运维相对复杂 | 大规模分布式系统,需要全局唯一且有序的ID,且对灵活性有要求的场景 |