大厂常用的分布式ID生成方案:兼具性能与可靠性
2024-02-17 03:05:19
分库分表时代的分布式ID生成之谜
缘起:数据激增带来的挑战
随着企业数字化转型深入,数据量持续攀升,单一数据库早已无法承载海量数据的存储和处理需求。分库分表技术应运而生,将数据分布到多个数据库或表中,缓解单机压力。
然而,分库分表后,一个至关重要的难题随之而来:如何为分布在不同数据库中的数据项生成全局唯一的ID?传统ID生成方法在分库分表环境下将导致ID重复,破坏数据完整性和一致性。
分布式ID生成方案:大厂的智慧结晶
分布式ID生成旨在为分布式系统中的实体分配全局唯一的ID,以保证数据的唯一性。业界为此提出了多种方案,各有千秋,适用于不同的业务场景。本文将重点探讨大厂广泛采用的四种方案:
1. 雪花算法:高性能的雪花纷飞
雪花算法由 Twitter 提出,因其高性能而闻名。它将 ID 划分为三个部分:
- 时间戳(41 位):表示 ID 生成的时间戳,单位为毫秒。
- 机器 ID(10 位):表示生成 ID 的机器编号。
- 序列号(12 位):同一毫秒内生成 ID 的序号,从 0 开始递增。
雪花算法可快速生成大量唯一 ID,且支持按时间或机器 ID 排序。
代码示例:
import java.util.concurrent.atomic.AtomicInteger;
public class SnowflakeIdGenerator {
private static final long EPOCH = 1420041600000L; // 2015-01-01 00:00:00
private final long machineId; // 机器 ID,0~1023
private final long sequenceMask = 0x3FF; // 序列号掩码,0~4095
private final long timestampShift = 41; // 时间戳左移位数,41 位
private long sequence = 0L; // 序列号
private long lastTimestamp = -1L; // 上一次生成 ID 的时间戳
public SnowflakeIdGenerator(long machineId) {
if (machineId < 0 || machineId > 1023) {
throw new IllegalArgumentException("Machine ID must be between 0 and 1023.");
}
this.machineId = machineId;
}
public long generateId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards. Refuse to generate ID.");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - EPOCH) << timestampShift) | (machineId << 10) | sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
2. UUID:无序的独特标识
UUID(Universally Unique Identifier)基于 128 位随机数生成,具有绝对唯一性。它不包含时间信息或排序功能,但生成速度较快。
代码示例:
import java.util.UUID;
public class UUIDIdGenerator {
public String generateId() {
return UUID.randomUUID().toString();
}
}
3. Redis:原子性递增的便捷
Redis 可通过原子性递增操作生成分布式 ID。它在 Redis 中维护一个名为 ID_GENERATOR 的键,每次需要生成 ID 时,对该键进行原子性递增。
代码示例:
import redis.clients.jedis.Jedis;
public class RedisIdGenerator {
private final Jedis jedis;
public RedisIdGenerator(String host, int port) {
jedis = new Jedis(host, port);
}
public long generateId() {
return jedis.incr("ID_GENERATOR");
}
}
4. ZooKeeper:可靠有序的序列号
ZooKeeper 是一个分布式协调服务,可通过节点的顺序号生成分布式 ID。在 ZooKeeper 中创建一个名为 ID_GENERATOR 的序列节点,每次需要生成 ID 时,从该节点获取一个顺序号。
代码示例:
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
public class ZooKeeperIdGenerator {
private final ZooKeeper zooKeeper;
public ZooKeeperIdGenerator(String connectString) throws KeeperException, InterruptedException {
zooKeeper = new ZooKeeper(connectString, 10000, null);
}
public long generateId() throws KeeperException, InterruptedException {
return zooKeeper.create("/ID_GENERATOR/id", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL).getId();
}
}
方案选择:根据需求权衡取舍
选择合适的分布式 ID 生成方案需要权衡以下因素:
- 性能: ID 生成吞吐量和延迟。
- 可靠性: ID 生成服务的可用性和稳定性。
- 唯一性: ID 是否真正全局唯一。
- 有序性: ID 是否可以按时间或机器 ID 排序。
根据这些因素,可以将上述方案归纳为以下场景:
- 极致性能: 雪花算法。
- 绝对唯一性: UUID。
- 高可靠性和可扩展性: ZooKeeper。
- 顺序性要求: Redis。
总结:分库分表的ID生成之道
分布式 ID 生成是分库分表系统中至关重要的技术,保障数据的唯一性和一致性。大厂广泛采用的雪花算法、UUID、Redis 和 ZooKeeper 等方案各具特色,企业可根据自身业务需求权衡选择。通过合理的设计和应用,企业可以构建高效、可靠的分布式 ID 生成系统,为业务发展保驾护航。
常见问题解答
1. 分库分表后,为什么不能直接使用自增主键?
因为自增主键在分布式环境下会导致 ID 冲突,破坏数据完整性。
2. 雪花算法的 ID 是否真的全局唯一?
理论上是全局唯一的,但当多个机器同时生成 ID 时,存在极小概率出现重复。
3. UUID 是否可以按时间或机器 ID 排序?
无法排序,因为 UUID 是随机生成的。
4. Redis 的 ID 生成是否会受到故障影响?
是的,当 Redis 故障时,ID 生成将受影响。
5. ZooKeeper 的 ID 生成效率如何?
较低,因为涉及分布式协调操作。