文章目录
-
- [1. 数据库自增ID](#1. 数据库自增ID)
- [2. UUID](#2. UUID)
- [4. 数据库号段模式](#4. 数据库号段模式)
- [5. Redis分布式ID](#5. Redis分布式ID)
- 6.百度的uid-generator
- [7. 基于Zookeeper的顺序节点](#7. 基于Zookeeper的顺序节点)
- [8. 数据库集群模式](#8. 数据库集群模式)
- [9. 美团(Leaf)](#9. 美团(Leaf))
- [10. 滴滴(Tinyid)](#10. 滴滴(Tinyid))
在分布式系统中,生成全局唯一的ID是一个常见需求,通常可以采用以下几种方案来实现分布式ID生成:
- UUID(Universally Unique Identifier):
UUID是一种标准的128位唯一标识符,通常以32个十六进制数字表示。UUID可以在不同节点上生成,保证全局唯一性,但由于长度较长且无序,不适合作为数据库主键使用。 - Snowflake算法:
Snowflake是Twitter开源的分布式ID生成算法,通过对64位的ID进行分段组合生成唯一ID。其中包括时间戳、机器标识和序列号等部分,保证了生成的ID在分布式环境下的唯一性和有序性。 - Flake ID生成算法:
Flake是另一种分布式ID生成算法,类似于Snowflake算法,通过时间戳、机器标识符和序列号来生成唯一ID。Flake算法相对于Snowflake算法有一些改进,例如更灵活的位数分配等。 - 数据库自增ID:
在分布式环境中也可以使用数据库的自增ID作为全局唯一ID,但需要考虑数据库的性能和单点故障问题。 - 基于Redis或ZooKeeper的分布式锁生成ID:
可以通过获取分布式锁的方式来保证ID的唯一性,例如利用Redis或ZooKeeper实现分布式锁,然后生成唯一ID。 - 利用分布式缓存生成ID:
可以利用分布式缓存如Redis等来保存自增的ID,每次需要生成ID时从缓存中获取并递增,确保唯一性。
以上是一些常见的分布式ID生成方案,选择合适的方案需要根据具体业务场景、性能需求和可用性要求来进行评估和选择。
数据库自增长序列或字段
UUID
Redis 生成 ID
基于雪花算法(Snowflake)实现
利用 zookeeper 生成唯一 ID
MongoDB 的 ObjectId
百度 (Uidgenerator)
美团(Leaf)
- uuid,它保证对在同一时空中所有机器都唯一,但这种方式生成id 比较长,并且无序,插入浪费空间。
- Snowflake 雪花算法,这种方案不错,但如果某台机器系统时钟回拨,有可能造成 ID 冲突重复,或者 ID 乱序(考虑跨机房部署问题)
- Mysql 自增主键,在高并发下,db 写压力会很大
- 用 Redis 做自增 id 生成器,性能高,但要考虑持久性问题;或者改造雪花算法,通过改造 workId 解决时钟回拨问题)
我们日常开发中,经常需要使用到分布式ID。我们系统一般都是分布式部署的,一些分布式锁、幂等、数据库的唯一键,都需要分布式ID。
1. 数据库自增ID
原理:利用数据库自增字段(如MySQL的AUTO_INCREMENT)生成唯一ID
优点:简单易用、ID有序、索引效率高缺点:单点故障、扩展性差(分库分表困难)
适用场景:单机或简单主从架构系统
代码示例:
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
order_data VARCHAR(255)
);
2. UUID
● 原理:基于MAC地址、时间戳、随机数生成128位字符串
● 优点:全局唯一、无需中心化服务
● 缺点:无序导致索引效率低、存储空间大(36字符)
● 适用场景:日志跟踪、非核心业务(如临时会话)
我们的项目中,有些伙伴为了简单方便,有时候会直接用它,如果业务性比较强的,就在它后缀拼接写个性化标记(业务标记)进来~
代码示例:
import java.util.UUID;
String uuid = UUID.randomUUID().toString();
- 雪花算法(Snowflake)
● 原理:64位结构 = 时间戳(41位) + 机器ID(10位) + 序列号(12位)
● 优点:高性能(单机每秒4万+)、趋势递增29
● 缺点:依赖时钟同步(时钟回拨会导致重复)
● 适用场景:分布式高并发系统(如电商订单)
其实,我们现在的系统,很多场景就是用雪花算法生成的,如流水号等等~
代码示例:
public class Snowflake {
private long machineId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨!");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & 4095; // 12位序列号
if (sequence == 0) timestamp = tilNextMillis(lastTimestamp);
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return (timestamp << 22) | (machineId << 12) | sequence;
}
}
4. 数据库号段模式
● 原理:批量获取ID段(如一次取1000个),减少数据库访问
● 优点:降低数据库压力、可用性高(缓存号段)、速度快
● 缺点:在服务器重启或故障转移等情况下,可能会导致ID的生成出现不连续的情况。
● 适用场景:中等并发业务(如用户ID生成)
我们的一些客户号,当前是用号段模式生成的,然后拼一些业务标记
表结构:
CREATE TABLE id_segment (
biz_tag VARCHAR(50) PRIMARY KEY,
max_id BIGINT NOT NULL,
step INT NOT NULL,
version INT NOT NULL
);
5. Redis分布式ID
● 原理:利用INCR原子操作生成递增ID
● 优点:性能优于数据库、天然有序、高性能、可扩展性强
● 缺点:依赖Redis可用性
● 适用场景:按日生成的流水号(如订单号=日期+自增)
代码示例:
Jedis jedis = new Jedis("redis-host");
Long orderId = jedis.incr("order:20240526");
6.百度的uid-generator
优点:避免频繁生成、吞吐量提升至600万/秒
适用场景:超大规模分布式系统
基于Twitter的Snowflake算法进行改进,增加了更多的配置和灵活性。
与原始的snowflake算法不同在于,uid-generator支持自定义时间戳、工作机器ID和 序列号 等各部分的位数,而且uid-generator中采用用户自定义workId的生成策略。
代码示例:
import com.baidu.fsg.uid.UidGenerator;
import com.baidu.fsg.uid.impl.CachedUidGenerator;
public class UidGeneratorDemo {
public static void main(String[] args) {
// 创建一个UidGenerator实例
UidGenerator uidGenerator = new CachedUidGenerator();
// 初始化,这里只是一个简单的示例,实际使用时你可能需要根据你的业务场景进行更复杂的配置
// 例如,设置workerId、epoch等
// 注意:在多实例部署时,每个实例的workerId必须唯一
long workerId = 1L; // 示例ID,实际使用时需要保证每个实例的唯一性
long datacenterId = 1L; // 数据中心ID,示例
uidGenerator.init(workerId, datacenterId, null);
// 生成一个UID
long uid = uidGenerator.getUID();
System.out.println("Generated UID: " + uid);
}
}
7. 基于Zookeeper的顺序节点
利用Zookeeper的顺序节点特性来生成全局唯一ID。
优点:
● 利用Zookeeper的集群特性保证高可用。
● ID全局唯一。
缺点:
● 需要依赖Zookeeper集群。
● 可能会受到Zookeeper性能的限制。
● 并发竞争较大不适合用Zookeeper
8. 数据库集群模式
单库的数据库自增ID会存在单点问题,所以可以用数据库集群模式,去解决这个问题。数据库集群模式:通过多个数据库实例设置不同的起始值和步长来生成全局唯一的ID。
数据库集群模式优点:
● 可以有效生成集群中的唯一ID。解决了单点的问题。
● 降低ID生成数据库操作的负载。
数据库集群模式缺点:
● 需要独立部署多个数据库实例,成本高。
● 后期不方便扩展
9. 美团(Leaf)
Leaf是美团点评开源的分布式ID生成系统,包含基于数据库和基于Zookeeper的两种实现方式。
以基于数据库的自增ID生成策略为例(数据库表结构):
CREATE TABLE leaf_alloc (
biz_tag VARCHAR(128) NOT NULL COMMENT '业务key',
max_id BIGINT(20) NOT NULL COMMENT '当前已分配的最大id',
step INT(11) NOT NULL COMMENT '每次id的增长步长',
PRIMARY KEY (biz_tag)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
Java 实现:
import java.sql.*;
public class LeafIdGenerator {
private static final String JDBC_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
private static final String USERNAME = "your_username";
private static final String PASSWORD = "your_password";
private static final String UPDATE_SQL = "UPDATE leaf_alloc SET max_id = max_id + ? WHERE biz_tag = ?";
private static final String SELECT_SQL = "SELECT max_id FROM leaf_alloc WHERE biz_tag = ? FOR UPDATE";
public synchronized long getId(String bizTag) throws SQLException {
Connection conn = null;
PreparedStatement updateStmt = null;
PreparedStatement selectStmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);
selectStmt = conn.prepareStatement(SELECT_SQL);
selectStmt.setString(1, bizTag);
rs = selectStmt.executeQuery();
if (rs.next()) {
long maxId = rs.getLong("max_id");
int step = 1000; // 假设步长为1000,你可以从数据库中读取这个值
// 假设这里只是简单演示,不检查是否超过max_id + step是否溢出
updateStmt = conn.prepareStatement(UPDATE_SQL);
updateStmt.setInt(1, step);
updateStmt.setString(2, bizTag);
updateStmt.executeUpdate();
// 返回ID区间中的一个ID,这里简单返回maxId(实际应用中可能需要更复杂的策略)
return maxId;
} else {
// 如果没有找到对应的bizTag,则需要初始化
// ... 初始化代码省略 ...
throw new RuntimeException("BizTag not found: " + bizTag);
}
} finally {
// 关闭资源,省略了异常处理
if (rs != null) rs.close();
if (selectStmt != null) selectStmt.close();
if (updateStmt != null) updateStmt.close();
if (conn != null) conn.close();
}
}
public static void main(String[] args) {
LeafIdGenerator generator = new LeafIdGenerator();
try {
long id = generator.getId("test-biz-tag");
System.out.println("Generated ID: " + id);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
优点:
● 结合了数据库和Zookeeper的优点,提供了高可用和高性能的ID生成服务。缺点:
● 就是时钟回拨问题、复杂性高。
分布式 ID 生成系统 Leaf
Leaf生成分布式ID的原理
Leaf(叶子)是美团点评开源的一个分布式 ID 生成系统,它的原理基于 Snowflake 算法。下面是 Leaf 生成分布式 ID 的基本原理:
Snowflake 算法:
Leaf 使用了 Snowflake 算法作为 ID 生成的基础。Snowflake 算法是一种利用时间、机器ID和序列号生成唯一ID的算法。
Snowflake 算法的结构一般包括一个 64 位的整数,其中高位是时间戳,中间部分是机器ID,最后一部分是序列号。
组成部分:
时间戳:用来记录生成 ID 的时间,通常精确到毫秒级别。
机器ID:标识不同的机器,确保分布式环境下的唯一性。
序列号:在同一毫秒内,通过序列号确保生成的 ID 不重复。
Leaf 的实现:
Leaf 会分配一个全局唯一的机器ID,通常由配置文件指定。
每次生成 ID 时,Leaf 会获取当前时间戳,与上一次生成 ID 的时间戳进行比较,如果相同则递增序列号;如果不同则重置序列号为 0。
最后将时间戳、机器ID和序列号合并生成一个唯一的分布式 ID。
优点:
Leaf 生成的 ID 具有趋势递增、唯一性和高性能的特点。
通过机器ID的划分,可以支持多台机器生成唯一的ID,适用于分布式系统中的 ID 生成需求。
总的来说,Leaf 基于 Snowflake 算法实现了一个高效、高性能的分布式 ID 生成系统,通过合理地利用时间戳、机器ID和序列号,确保生成的 ID 在分布式环境下唯一且趋势递增。这样的设计方案可以满足大多数分布式系统对于唯一 ID 的需求。
10. 滴滴(Tinyid)
Tinyid是滴滴开源的轻量级分布式ID生成系统,它是基于号段模式原理实现的与Leaf如出一辙,每个服务获取一个号段(1000,2000]、(2000,3000]、(3000,4000]
以下是一个简化的Tinyid,服务端的伪代码:
// 假设我们有一个ID生成器,这里用AtomicLong模拟
import java.util.concurrent.atomic.AtomicLong;
public class TinyidService {
private AtomicLong idGenerator = new AtomicLong(0);
// 模拟的ID生成方法
public synchronized long generateId() {
return idGenerator.incrementAndGet();
}
// 这里应该是RESTful API的实现,但为简化起见,我们省略了HTTP部分
// 客户端应该通过HTTP请求调用此方法
public long getIdOverHttp() {
return generateId();
}
}
客户端(Java示例)
import okhttp3.*;
public class TinyidClient {
private static final String TINYID_SERVICE_URL = "http://localhost:8080/tinyid/generate";
public static void main(String[] args) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(TINYID_SERVICE_URL)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
} else {
// 假设服务端返回的是纯文本格式的ID
String responseBody = response.body().string();
long id = Long.parseLong(responseBody);
System.out.println("Generated ID: " + id);
}
}
});
}
}
● 优缺点:简单、轻量级,但性能可能不如其他方案。