refactor: rename files to Chinese and organize by category
Organized 50 interview questions into 12 categories: - 01-分布式系统 (9 files): 分布式事务, 分布式锁, 一致性哈希, CAP理论, etc. - 02-数据库 (2 files): MySQL索引优化, MyBatis核心原理 - 03-缓存 (5 files): Redis数据结构, 缓存问题, LRU算法, etc. - 04-消息队列 (1 file): RocketMQ/Kafka - 05-并发编程 (4 files): 线程池, 设计模式, 限流策略, etc. - 06-JVM (1 file): JVM和垃圾回收 - 07-系统设计 (8 files): 秒杀系统, 短链接, IM, Feed流, etc. - 08-算法与数据结构 (4 files): B+树, 红黑树, 跳表, 时间轮 - 09-网络与安全 (3 files): TCP/IP, 加密安全, 性能优化 - 10-中间件 (4 files): Spring Boot, Nacos, Dubbo, Nginx - 11-运维 (4 files): Kubernetes, CI/CD, Docker, 可观测性 - 12-面试技巧 (1 file): 面试技巧和职业规划 All files renamed to Chinese for better accessibility and organized into categorized folders for easier navigation. Generated with [Claude Code](https://claude.com/claude-code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
731
questions/01-分布式系统/分布式ID生成.md
Normal file
731
questions/01-分布式系统/分布式ID生成.md
Normal file
@@ -0,0 +1,731 @@
|
||||
# 分布式 ID 生成方案
|
||||
|
||||
## 问题
|
||||
|
||||
1. 为什么需要分布式 ID?分布式 ID 有哪些要求?
|
||||
2. 常见的分布式 ID 生成方案有哪些?各自的优缺点是什么?
|
||||
3. Snowflake 算法的原理是什么?有什么坑?
|
||||
4. 数据库自增 ID 如何实现分布式?如何优化性能?
|
||||
5. Redis 如何生成分布式 ID?有哪些优缺点?
|
||||
6. 在实际项目中,你是如何设计分布式 ID 的?
|
||||
|
||||
---
|
||||
|
||||
## 标准答案
|
||||
|
||||
### 1. 分布式 ID 的要求和特点
|
||||
|
||||
#### **为什么需要分布式 ID?**
|
||||
|
||||
在分布式系统中,需要保证 ID 的**全局唯一性**:
|
||||
- 订单号、用户 ID、支付流水号
|
||||
- 分库分表后的主键冲突问题
|
||||
- 微服务间的数据关联
|
||||
|
||||
**单机 ID 的问题**:
|
||||
```
|
||||
单机数据库自增 ID:
|
||||
- 实例 1:1, 2, 3, 4, 5
|
||||
- 实例 2:1, 2, 3, 4, 5 ← 冲突!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **分布式 ID 的核心要求**
|
||||
|
||||
| 要求 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **全局唯一性** | 不能重复 | 订单号不能重复 |
|
||||
| **有序性** | 趋势递增(可选) | 按时间排序的订单号 |
|
||||
| **高性能** | 生成速度快 | 支持 10 万+/秒 |
|
||||
| **高可用** | 服务不中断 | 宕机后仍可生成 |
|
||||
| **信息安全** | 不暴露业务信息 | 不暴露订单总量 |
|
||||
|
||||
---
|
||||
|
||||
### 2. 常见分布式 ID 方案对比
|
||||
|
||||
| 方案 | 唯一性 | 有序性 | 性能 | 复杂度 | 适用场景 |
|
||||
|------|--------|--------|------|--------|----------|
|
||||
| **UUID** | ✅ | ❌ | ⭐⭐⭐⭐⭐ | 低 | 非主键、内部 ID |
|
||||
| **数据库自增** | ✅ | ✅ | ⭐⭐ | 低 | 小规模、并发低 |
|
||||
| **Redis INCR** | ✅ | ✅ | ⭐⭐⭐ | 低 | 中小规模 |
|
||||
| **Snowflake** | ✅ | ✅ | ⭐⭐⭐⭐⭐ | 中 | 大规模、高并发 |
|
||||
| **号段模式** | ✅ | ✅ | ⭐⭐⭐⭐ | 中 | 大规模、高性能 |
|
||||
| **美团 Leaf** | ✅ | ✅ | ⭐⭐⭐⭐⭐ | 高 | 金融级、高可用 |
|
||||
|
||||
---
|
||||
|
||||
### 3. UUID
|
||||
|
||||
#### **原理**
|
||||
|
||||
UUID(Universally Unique Identifier)是 128 位的唯一标识符。
|
||||
|
||||
**格式**:
|
||||
```
|
||||
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
550e8400-e29b-41d4-a716-446655440000
|
||||
```
|
||||
|
||||
**Java 示例**:
|
||||
```java
|
||||
import java.util.UUID;
|
||||
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
// 输出:550e8400-e29b-41d4-a716-446655440000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **优缺点**
|
||||
|
||||
**优点**:
|
||||
- 性能高:本地生成,无网络开销
|
||||
- 简单:JDK 自带,无需额外组件
|
||||
|
||||
**缺点**:
|
||||
- **无序**:无法按时间排序
|
||||
- **过长**:36 字符,存储空间大
|
||||
- **不安全**:暴露 MAC 地址(UUID v1)
|
||||
- **索引性能差**:无序 ID 导致 B+ 树频繁分裂
|
||||
|
||||
**B+ 树分裂问题**:
|
||||
```
|
||||
有序 ID:
|
||||
1 → 100 → 1000 → 10000
|
||||
└─ 顺序插入,B+ 树叶子节点顺序填充
|
||||
|
||||
无序 ID(UUID):
|
||||
abc → xyz → 123 → 999
|
||||
└─ 随机插入,B+ 树频繁分裂,性能差
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **适用场景**
|
||||
|
||||
- ✅ 非数据库主键(如请求 ID)
|
||||
- ✅ 临时标识、会话 ID
|
||||
- ✅ 内部系统、不需要排序
|
||||
- ❌ 订单号、用户 ID(需要有序)
|
||||
|
||||
---
|
||||
|
||||
### 4. 数据库自增 ID
|
||||
|
||||
#### **方案 1:单机自增**
|
||||
|
||||
```sql
|
||||
CREATE TABLE orders (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_no VARCHAR(32),
|
||||
created_at DATETIME
|
||||
);
|
||||
```
|
||||
|
||||
**问题**:单机性能瓶颈,无法扩展。
|
||||
|
||||
---
|
||||
|
||||
#### **方案 2:多机步长模式(Flickr 方案)**
|
||||
|
||||
**原理**:不同数据库实例设置不同的起始值和步长。
|
||||
|
||||
```
|
||||
实例 1:起始 1,步长 2 → 1, 3, 5, 7, 9
|
||||
实例 2:起始 2,步长 2 → 2, 4, 6, 8, 10
|
||||
```
|
||||
|
||||
**配置**:
|
||||
```sql
|
||||
-- 实例 1
|
||||
SET auto_increment_increment = 2;
|
||||
SET auto_increment_offset = 1;
|
||||
|
||||
-- 实例 2
|
||||
SET auto_increment_increment = 2;
|
||||
SET auto_increment_offset = 2;
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- 实现简单
|
||||
- ID 有序
|
||||
|
||||
**缺点**:
|
||||
- 扩容困难:需要重新计算步长
|
||||
- 性能瓶颈:数据库写入限制
|
||||
|
||||
---
|
||||
|
||||
#### **方案 3:号段模式(批量获取)**
|
||||
|
||||
**原理**:一次从数据库获取一批 ID(号段),缓存在本地。
|
||||
|
||||
**表结构**:
|
||||
```sql
|
||||
CREATE TABLE id_segment (
|
||||
biz_type VARCHAR(32) PRIMARY KEY, -- 业务类型
|
||||
max_id BIGINT, -- 当前最大 ID
|
||||
step INT, -- 步长(批量大小)
|
||||
version INT, -- 版本号(乐观锁)
|
||||
updated_at DATETIME
|
||||
);
|
||||
|
||||
-- 初始化数据
|
||||
INSERT INTO id_segment (biz_type, max_id, step, version)
|
||||
VALUES ('order', 0, 1000, 1);
|
||||
```
|
||||
|
||||
**获取 ID 逻辑**:
|
||||
```java
|
||||
@Service
|
||||
public class IdSegmentService {
|
||||
|
||||
@Autowired
|
||||
private IdSegmentMapper segmentMapper;
|
||||
|
||||
private final Map<String, IdSegment> localCache = new ConcurrentHashMap<>();
|
||||
|
||||
@Transactional
|
||||
public synchronized Long nextId(String bizType) {
|
||||
IdSegment segment = localCache.get(bizType);
|
||||
|
||||
// 本地号段用完,从数据库获取
|
||||
if (segment == null || segment.getCurrentId() >= segment.getMaxId()) {
|
||||
segment = fetchSegmentFromDb(bizType);
|
||||
}
|
||||
|
||||
// 返回下一个 ID
|
||||
return segment.getNextId();
|
||||
}
|
||||
|
||||
private IdSegment fetchSegmentFromDb(String bizType) {
|
||||
// 使用 CAS 更新数据库
|
||||
IdSegment dbSegment = segmentMapper.selectByType(bizType);
|
||||
|
||||
// 更新 max_id = max_id + step
|
||||
int updated = segmentMapper.updateMaxId(
|
||||
bizType,
|
||||
dbSegment.getMaxId() + dbSegment.getStep(),
|
||||
dbSegment.getVersion()
|
||||
);
|
||||
|
||||
if (updated == 0) {
|
||||
throw new RuntimeException("并发冲突,请重试");
|
||||
}
|
||||
|
||||
// 缓存到本地
|
||||
IdSegment newSegment = new IdSegment();
|
||||
newSegment.setMaxId(dbSegment.getMaxId() + dbSegment.getStep());
|
||||
newSegment.setCurrentId(dbSegment.getMaxId());
|
||||
|
||||
localCache.put(bizType, newSegment);
|
||||
return newSegment;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Mapper**:
|
||||
```java
|
||||
@Update("UPDATE id_segment SET max_id = #{maxId}, version = version + 1 " +
|
||||
"WHERE biz_type = #{bizType} AND version = #{version}")
|
||||
int updateMaxId(@Param("bizType") String bizType,
|
||||
@Param("maxId") Long maxId,
|
||||
@Param("version") Integer version);
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- 性能高:本地缓存,减少数据库访问
|
||||
- 有序:ID 趋势递增
|
||||
|
||||
**缺点**:
|
||||
- 宕机丢 ID:本地缓存的 ID 未使用完就丢失
|
||||
- 实现复杂
|
||||
|
||||
**优化**:使用双缓冲(Double Buffer)机制预加载号段。
|
||||
|
||||
---
|
||||
|
||||
### 5. Redis INCR
|
||||
|
||||
#### **原理**
|
||||
|
||||
使用 Redis 的 `INCR` 和 `INCRBY` 命令生成全局唯一 ID。
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
# 初始化
|
||||
SET order:id 1
|
||||
|
||||
# 获取下一个 ID
|
||||
INCR order:id
|
||||
# 返回:2
|
||||
|
||||
# 批量获取(步长 1000)
|
||||
INCRBY order:id 1000
|
||||
# 返回:1001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **Java 实现**
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class RedisIdGenerator {
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
public Long nextId(String key) {
|
||||
// 使用 redisTemplate 的 opsForValue
|
||||
Long id = redisTemplate.opsForValue().increment(key);
|
||||
|
||||
if (id == null) {
|
||||
throw new RuntimeException("生成 ID 失败");
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
// 批量获取(优化性能)
|
||||
public Long[] batchNextId(String key, int count) {
|
||||
Long startId = redisTemplate.opsForValue().increment(key, count);
|
||||
|
||||
Long[] ids = new Long[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
ids[i] = startId - count + i + 1;
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **优缺点**
|
||||
|
||||
**优点**:
|
||||
- 性能高:Redis 内存操作
|
||||
- 有序:ID 趋势递增
|
||||
- 实现简单
|
||||
|
||||
**缺点**:
|
||||
- 依赖 Redis:需要维护 Redis 集群
|
||||
- 宕机丢数据:未持久化会丢失
|
||||
|
||||
**持久化配置**:
|
||||
```properties
|
||||
# 开启 AOF 持久化
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **适用场景**
|
||||
|
||||
- 中小规模、性能要求高
|
||||
- 已有 Redis 集群
|
||||
- 可容忍短期 ID 丢失
|
||||
|
||||
---
|
||||
|
||||
### 6. Snowflake 算法
|
||||
|
||||
#### **原理**
|
||||
|
||||
Snowflake 是 Twitter 开源的分布式 ID 算法,生成 64 位的 Long 型 ID。
|
||||
|
||||
**结构**(64 位):
|
||||
```
|
||||
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
|
||||
↑ ↑ ↑ ↑ ↑
|
||||
│ │ │ │ │
|
||||
│ └─ 41 位时间戳(毫秒) │ │ │
|
||||
│ │ │ │
|
||||
│ └─ 5 位数据中心 ID │
|
||||
│ └─ 12 位序列号
|
||||
1 位符号位(始终为 0)
|
||||
```
|
||||
|
||||
**组成部分**:
|
||||
- **1 位符号位**:始终为 0(正数)
|
||||
- **41 位时间戳**:毫秒级,可用 69 年(`2^41 / 1000 / 60 / 60 / 24 / 365 ≈ 69`)
|
||||
- **5 位数据中心 ID**:支持 32 个数据中心
|
||||
- **5 位机器 ID**:每个数据中心 32 台机器
|
||||
- **12 位序列号**:每毫秒可生成 4096 个 ID
|
||||
|
||||
**理论 QPS**:
|
||||
```
|
||||
单机:4096 / 毫秒 = 409.6 万/秒
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **Java 实现**
|
||||
|
||||
```java
|
||||
public class SnowflakeIdGenerator {
|
||||
|
||||
// 起始时间戳(2024-01-01 00:00:00)
|
||||
private final long twepoch = 1704067200000L;
|
||||
|
||||
// 各部分位数
|
||||
private final long workerIdBits = 5L;
|
||||
private final long datacenterIdBits = 5L;
|
||||
private final long sequenceBits = 12L;
|
||||
|
||||
// 最大值
|
||||
private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 31
|
||||
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 31
|
||||
private final long maxSequence = -1L ^ (-1L << sequenceBits); // 4095
|
||||
|
||||
// 位移
|
||||
private final long workerIdShift = sequenceBits; // 12
|
||||
private final long datacenterIdShift = sequenceBits + workerIdBits; // 17
|
||||
private final long timestampShift = sequenceBits + workerIdBits + datacenterIdBits; // 22
|
||||
|
||||
private final long workerId;
|
||||
private final long datacenterId;
|
||||
private long sequence = 0L;
|
||||
private long lastTimestamp = -1L;
|
||||
|
||||
public SnowflakeIdGenerator(long workerId, long datacenterId) {
|
||||
if (workerId > maxWorkerId || workerId < 0) {
|
||||
throw new IllegalArgumentException("workerId 无效");
|
||||
}
|
||||
if (datacenterId > maxDatacenterId || datacenterId < 0) {
|
||||
throw new IllegalArgumentException("datacenterId 无效");
|
||||
}
|
||||
this.workerId = workerId;
|
||||
this.datacenterId = datacenterId;
|
||||
}
|
||||
|
||||
public synchronized long nextId() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
// 时钟回拨检查
|
||||
if (timestamp < lastTimestamp) {
|
||||
throw new RuntimeException("时钟回拨,拒绝生成 ID");
|
||||
}
|
||||
|
||||
// 同一毫秒内,序列号自增
|
||||
if (timestamp == lastTimestamp) {
|
||||
sequence = (sequence + 1) & maxSequence;
|
||||
// 序列号溢出,等待下一毫秒
|
||||
if (sequence == 0) {
|
||||
timestamp = tilNextMillis(lastTimestamp);
|
||||
}
|
||||
} else {
|
||||
// 新毫秒,序列号重置
|
||||
sequence = 0L;
|
||||
}
|
||||
|
||||
lastTimestamp = timestamp;
|
||||
|
||||
// 组装 ID
|
||||
return ((timestamp - twepoch) << timestampShift)
|
||||
| (datacenterId << datacenterIdShift)
|
||||
| (workerId << workerIdShift)
|
||||
| sequence;
|
||||
}
|
||||
|
||||
// 等待下一毫秒
|
||||
private long tilNextMillis(long lastTimestamp) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
while (timestamp <= lastTimestamp) {
|
||||
timestamp = System.currentTimeMillis();
|
||||
}
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
// 解析 ID(用于调试)
|
||||
public static void parseId(long id) {
|
||||
long timestamp = (id >> 22) + 1704067200000L;
|
||||
long datacenterId = (id >> 17) & 0x1F;
|
||||
long workerId = (id >> 12) & 0x1F;
|
||||
long sequence = id & 0xFFF;
|
||||
|
||||
System.out.println("ID: " + id);
|
||||
System.out.println("时间戳: " + new Date(timestamp));
|
||||
System.out.println("数据中心 ID: " + datacenterId);
|
||||
System.out.println("机器 ID: " + workerId);
|
||||
System.out.println("序列号: " + sequence);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**使用示例**:
|
||||
```java
|
||||
// 初始化(workerId=1, datacenterId=1)
|
||||
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
|
||||
|
||||
// 生成 ID
|
||||
long id = idGenerator.nextId();
|
||||
System.out.println("生成的 ID: " + id);
|
||||
|
||||
// 解析 ID
|
||||
SnowflakeIdGenerator.parseId(id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **Snowflake 的坑**
|
||||
|
||||
##### **问题 1:时钟回拨**
|
||||
|
||||
**原因**:
|
||||
- 系统时钟不准确(NTP 同步)
|
||||
- 人工修改系统时间
|
||||
|
||||
**后果**:
|
||||
```
|
||||
时间:10:00:00.100,生成 ID(时间戳=T1)
|
||||
时钟回拨:时间 → 09:59:59.900
|
||||
时间:09:59:59.950,生成 ID(时间戳=T2 < T1)← 冲突!
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
|
||||
1. **拒绝服务**(简单):
|
||||
```java
|
||||
if (timestamp < lastTimestamp) {
|
||||
throw new RuntimeException("时钟回拨,拒绝生成 ID");
|
||||
}
|
||||
```
|
||||
|
||||
2. **等待时钟追上**(推荐):
|
||||
```java
|
||||
if (timestamp < lastTimestamp) {
|
||||
long offset = lastTimestamp - timestamp;
|
||||
if (offset <= 5) {
|
||||
// 等待 5 毫秒
|
||||
try {
|
||||
Thread.sleep(offset << 1);
|
||||
timestamp = System.currentTimeMillis();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("时钟回拨,等待失败");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("时钟回拨过多,拒绝生成 ID");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **使用备用 workerId**(美团 Leaf 方案):
|
||||
```java
|
||||
if (timestamp < lastTimestamp) {
|
||||
// 切换到备用 workerId
|
||||
workerId = backupWorkerId;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
##### **问题 2:机器 ID 分配**
|
||||
|
||||
**问题**:如何保证 workerId 全局唯一?
|
||||
|
||||
**解决方案**:
|
||||
|
||||
1. **配置文件**(简单):
|
||||
```yaml
|
||||
application.yml:
|
||||
snowflake:
|
||||
worker-id: 1
|
||||
datacenter-id: 1
|
||||
```
|
||||
|
||||
2. **数据库配置**:
|
||||
```sql
|
||||
CREATE TABLE worker_config (
|
||||
id INT PRIMARY KEY,
|
||||
worker_id INT,
|
||||
datacenter_id INT,
|
||||
ip VARCHAR(32),
|
||||
used BOOLEAN
|
||||
);
|
||||
|
||||
-- 启动时申请 workerId
|
||||
INSERT INTO worker_config (worker_id, datacenter_id, ip, used)
|
||||
VALUES (1, 1, '192.168.1.10', TRUE);
|
||||
```
|
||||
|
||||
3. **Zookeeper 顺序节点**(动态):
|
||||
```java
|
||||
// 在 Zookeeper 中创建临时顺序节点
|
||||
String path = zk.create("/snowflake/worker-", null, ZooDefs.Ids.EPHEMERAL_SEQUENTIAL);
|
||||
// 获取序号作为 workerId
|
||||
int workerId = Integer.parseInt(path.split("-")[1]);
|
||||
```
|
||||
|
||||
4. **Redis INCR**(动态):
|
||||
```java
|
||||
Long workerId = redisTemplate.opsForValue().increment("snowflake:worker:id");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
##### **问题 3:序列号溢出**
|
||||
|
||||
**场景**:高并发下,1 毫秒内请求超过 4096 个。
|
||||
|
||||
**解决**:
|
||||
```java
|
||||
// 序列号溢出,等待下一毫秒
|
||||
if (sequence == 0) {
|
||||
timestamp = tilNextMillis(lastTimestamp);
|
||||
}
|
||||
```
|
||||
|
||||
**优化**:使用 13 位序列号(每毫秒 8192 个 ID)。
|
||||
|
||||
---
|
||||
|
||||
#### **优缺点**
|
||||
|
||||
**优点**:
|
||||
- 性能极高:409 万 QPS/单机
|
||||
- 有序:趋势递增(按时间排序)
|
||||
- 不依赖数据库、Redis
|
||||
|
||||
**缺点**:
|
||||
- 时钟回拨问题
|
||||
- 机器 ID 分配复杂
|
||||
- ID 较长(18 位数字)
|
||||
|
||||
---
|
||||
|
||||
#### **适用场景**
|
||||
|
||||
- 大规模、高并发场景
|
||||
- 需要有序 ID
|
||||
- 可容忍时钟问题(或已有解决方案)
|
||||
|
||||
**实际应用**:
|
||||
- 百度 UidGenerator
|
||||
- 美团 Leaf(Snowflake 模式)
|
||||
- Etsy)
|
||||
|
||||
---
|
||||
|
||||
### 7. 美团 Leaf
|
||||
|
||||
#### **Leaf-segment(号段模式)**
|
||||
|
||||
**原理**:优化版号段模式,使用双缓冲机制。
|
||||
|
||||
**架构**:
|
||||
```
|
||||
Leaf Server
|
||||
├─ Buffer 1:当前使用号段 [1000, 2000)
|
||||
└─ Buffer 2:预加载号段 [2000, 3000)(后台异步加载)
|
||||
|
||||
当 Buffer 1 用完:
|
||||
└─ 切换到 Buffer 2
|
||||
└─ 异步加载 Buffer 1 的下一个号段
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- 无停顿:双缓冲无缝切换
|
||||
- 高性能:本地缓存
|
||||
|
||||
**缺点**:
|
||||
- 宕机丢 ID:未使用的号段丢失
|
||||
|
||||
---
|
||||
|
||||
#### **Leaf-snowflake(优化版 Snowflake)**
|
||||
|
||||
**优化点**:
|
||||
1. **Zookeeper 生成 workerId**:动态分配,无需配置
|
||||
2. **时钟回拨优化**:
|
||||
- 回拨 5ms 内:等待时钟追上
|
||||
- 回拨 5ms 外:告警并拒绝服务
|
||||
|
||||
**架构**:
|
||||
```
|
||||
Leaf Server 1(workerId=1)
|
||||
Leaf Server 2(workerId=2)
|
||||
Leaf Server 3(workerId=3)
|
||||
↓
|
||||
Zookeeper(协调)
|
||||
```
|
||||
|
||||
**GitHub**:https://github.com/Meituan-Dianping/Leaf
|
||||
|
||||
---
|
||||
|
||||
### 8. 百度 UidGenerator
|
||||
|
||||
**特点**:
|
||||
- 基于 Snowflake 优化
|
||||
- 使用 22 位序列号(每秒 400 万 ID)
|
||||
- 支持跨毫秒分配序列号
|
||||
|
||||
**GitHub**:https://github.com/baidu/uid-generator
|
||||
|
||||
---
|
||||
|
||||
### 9. 实际项目选型建议
|
||||
|
||||
#### **决策树**
|
||||
|
||||
```
|
||||
是否需要有序?
|
||||
├─ 否 → UUID(最简单)
|
||||
└─ 是 → 继续判断
|
||||
│
|
||||
├─ QPS < 1000?
|
||||
│ ├─ 是 → Redis INCR(简单)
|
||||
│ └─ 否 → 继续判断
|
||||
│
|
||||
├─ 已有 Redis?
|
||||
│ ├─ 是 → 号段模式(高性能)
|
||||
│ └─ 否 → 继续判断
|
||||
│
|
||||
├─ 可容忍时钟回拨问题?
|
||||
│ ├─ 是 → Snowflake(性能最高)
|
||||
│ └─ 否 → 美团 Leaf-snowflake
|
||||
│
|
||||
└─ 金融级可靠性?
|
||||
└─ 美团 Leaf-segment + 监控
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **性能对比**
|
||||
|
||||
| 方案 | 单机 QPS | 延迟 | 依赖 |
|
||||
|------|---------|------|------|
|
||||
| UUID | 1000 万+ | 0.001ms | 无 |
|
||||
| 数据库自增 | 1000 | 10ms | 数据库 |
|
||||
| Redis INCR | 10 万 | 1ms | Redis |
|
||||
| 号段模式 | 100 万 | 0.1ms | 数据库 |
|
||||
| Snowflake | 400 万 | 0.01ms | 无 |
|
||||
|
||||
---
|
||||
|
||||
### 10. 阿里 P7 加分项
|
||||
|
||||
**深度理解**:
|
||||
- 理解 Snowflake 的时间戳回拨问题的根本原因
|
||||
- 理解号段模式的双缓冲机制和 CAS 原理
|
||||
|
||||
**实战经验**:
|
||||
- 有处理 Snowflake 时钟回拨的线上故障经验
|
||||
- 有号段模式宕机丢 ID 的解决方案
|
||||
- 有分布式 ID 迁移经验(如从数据库自增迁移到 Snowflake)
|
||||
|
||||
**架构能力**:
|
||||
- 能设计支持多业务类型的分布式 ID 系统
|
||||
- 能设计分布式 ID 的监控和告警体系
|
||||
- 有分布式 ID 容灾方案(多机房容灾)
|
||||
|
||||
**技术选型**:
|
||||
- 能根据业务特点选择合适的方案
|
||||
- 了解美团 Leaf、百度 UidGenerator 等开源方案
|
||||
- 有自研分布式 ID 生成器的经验
|
||||
Reference in New Issue
Block a user