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:
yasinshaw
2026-03-01 00:10:53 +08:00
parent fe2e6dc2f2
commit 0e46a367c4
47 changed files with 0 additions and 0 deletions

View 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
- 实例 11, 2, 3, 4, 5
- 实例 21, 2, 3, 4, 5 ← 冲突!
```
---
#### **分布式 ID 的核心要求**
| 要求 | 说明 | 示例 |
|------|------|------|
| **全局唯一性** | 不能重复 | 订单号不能重复 |
| **有序性** | 趋势递增(可选) | 按时间排序的订单号 |
| **高性能** | 生成速度快 | 支持 10 万+/秒 |
| **高可用** | 服务不中断 | 宕机后仍可生成 |
| **信息安全** | 不暴露业务信息 | 不暴露订单总量 |
---
### 2. 常见分布式 ID 方案对比
| 方案 | 唯一性 | 有序性 | 性能 | 复杂度 | 适用场景 |
|------|--------|--------|------|--------|----------|
| **UUID** | ✅ | ❌ | ⭐⭐⭐⭐⭐ | 低 | 非主键、内部 ID |
| **数据库自增** | ✅ | ✅ | ⭐⭐ | 低 | 小规模、并发低 |
| **Redis INCR** | ✅ | ✅ | ⭐⭐⭐ | 低 | 中小规模 |
| **Snowflake** | ✅ | ✅ | ⭐⭐⭐⭐⭐ | 中 | 大规模、高并发 |
| **号段模式** | ✅ | ✅ | ⭐⭐⭐⭐ | 中 | 大规模、高性能 |
| **美团 Leaf** | ✅ | ✅ | ⭐⭐⭐⭐⭐ | 高 | 金融级、高可用 |
---
### 3. UUID
#### **原理**
UUIDUniversally 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+ 树叶子节点顺序填充
无序 IDUUID
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
- 美团 LeafSnowflake 模式)
- 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 1workerId=1
Leaf Server 2workerId=2
Leaf Server 3workerId=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 生成器的经验