732 lines
18 KiB
Markdown
732 lines
18 KiB
Markdown
# 分布式 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 生成器的经验
|