除了雪花算法(Snowflake)之外,还有多种分布式ID生成方案,以及一些利用中间件实现的方案。
-
UUID(Universally Unique Identifier)
- UUID是一个128位的数字,可以确保全球唯一性。标准的UUID格式包含时间、时钟序列和节点标识等信息,分为多种版本,其中Version 1和Version 4常用于分布式系统。Version 1基于时间和MAC地址生成,而Version 4则是完全随机生成,牺牲了部分时间有序性来换取极高的生成速度和全局唯一性。
-
Redis incr或Lua脚本
- 利用Redis的原子性操作,如
INCR
命令或Lua脚本来生成自增ID,可以确保在分布式环境中ID的唯一性。这种方法简单有效,但需要考虑Redis的单点问题和数据一致性问题。
- 利用Redis的原子性操作,如
-
Twitter的Snowflake改进版或变种
- 除了原始的Snowflake算法,许多公司和项目会根据自身需求对其进行调整或优化,例如调整时间位数、数据中心位数或工作机器位数,以适应不同的规模和场景。
-
Leaf(美团开源)
- Leaf是美团开源的一套分布式ID生成服务,它提供了两种ID生成方式:基于Snowflake算法的Segment模式和基于MySQL的号段模式。Segment模式通过预先加载一段ID到内存,减少对数据库的访问频次,提高生成效率。
-
滴滴出行的TinyID
- TinyID是一个分布式高效ID生成器,采用预生成ID段的方式,将ID预先按批次生成并存储在数据库中,应用通过API获取ID段,然后在本地缓存并按需分配,以此来减少对数据库的访问。
-
MongoDB的ObjectId
- MongoDB在每个文档中自动添加一个ObjectId作为_id字段,ObjectId包含时间戳、机器标识、进程ID和自增值,可以保证全局唯一性,但主要用于MongoDB内部。
-
使用ZooKeeper
- ZooKeeper可以作为一个分布式协调服务,通过创建顺序节点的方式生成唯一的ID。虽然这种方式的性能可能不如专门的ID生成算法,但它可以作为一种可靠的选择,尤其是在已有ZooKeeper集群的环境中。
-
消息队列(如Kafka)
- 利用消息队列的顺序性和分区特性,可以设计一种ID生成方案。例如,通过在一个专门的Kafka主题中生产消息,并利用每个消息的偏移量作为ID的一部分,结合时间戳等其他信息,可以生成全局唯一ID。
每种方案都有其适用场景和优缺点,选择时需要根据系统的需求、性能要求、扩展性和可靠性等因素综合考量。
示例1:使用Redis生成分布式ID
假设你正在使用Redis作为中间件来生成ID,可以使用Redis的原子性递增命令INCR
。下面是一个简单的Redis命令示例和对应的Java代码示例:
Redis命令行示例:
redis> INCR my_counter
(integer) 1
redis> INCR my_counter
(integer) 2
Java代码示例:
import redis.clients.jedis.Jedis;
public class RedisIdGenerator {
private Jedis jedis;
public RedisIdGenerator(String redisHost) {
jedis = new Jedis(redisHost);
}
public long generateId(String key) {
return jedis.incr(key);
}
}
// 使用方法
public static void main(String[] args) {
RedisIdGenerator idGen = new RedisIdGenerator("localhost");
long id = idGen.generateId("my_counter");
System.out.println("Generated ID: " + id);
}
示例2:使用Snowflake算法生成分布式ID
Snowflake算法生成的是一个64位的整数,其结构包括时间戳、数据中心ID、机器ID和序列号。下面是一个简化版的Snowflake算法Java实现示例:
public class SnowflakeIdWorker {
private final long twepoch = 1288834974657L; // 起始时间戳,可以自定义
private final long workerIdBits = 5L; // 机器ID所占位数
private final long datacenterIdBits = 5L; // 数据中心ID所占位数
private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 支持的最大机器ID
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 支持的最大数据中心ID
private final long sequenceBits = 12L; // 序列号所占位数
private final long workerIdShift = sequenceBits; // 机器ID左移位数
private final long datacenterIdShift = sequenceBits + workerIdBits; // 数据中心ID左移位数
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 时间戳左移位数
private final long sequenceMask = -1L ^ (-1L << sequenceBits); // 生成序列的掩码
private long workerId; // 工作机器ID
private long datacenterId; // 数据中心ID
private long sequence = 0L; // 毫秒内序列
private long lastTimestamp = -1L; // 上次生成ID的时间戳
public SnowflakeIdWorker(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();
// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过,这个时候应当抛出异常
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;
}
// 上次生成ID的时间戳
lastTimestamp = timestamp;
// 移位并通过或运算拼到一起组成64位的ID
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();
}
}
// 使用方法
public static void main(String[] args) {
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
long id = idWorker.nextId();
System.out.println("Generated ID: " + id);
}