分布式ID
文章目录
- 1. 数据库自增ID
- 2. UUID
- 4. 数据库号段模式
- 5. Redis分布式ID
- 6.百度的uid-generator
- 7. 基于Zookeeper的顺序节点
- 8. 数据库集群模式
- 9. 美团(Leaf)
- 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();
3. 雪花算法(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); } } }); }
}
● 优缺点:简单、轻量级,但性能可能不如其他方案。