📖 开场:身份证号码
想象全国人民的身份证号码 🆔:
单机生成(自增ID):
第1个人:ID = 1
第2个人:ID = 2
第3个人:ID = 3
...
简单,但只能在一个地方办理 ❌
分布式生成(多地办理):
北京办事处:生成 110开头的ID
上海办事处:生成 310开头的ID
广州办事处:生成 440开头的ID
问题:如何保证全国唯一?如何保证有序?🤔
这就是分布式唯一ID生成器要解决的问题!
需求:
- 全局唯一:不能重复
- 趋势递增:有序(方便MySQL索引)
- 高性能:QPS > 10万
- 高可用:不能单点故障
🤔 分布式ID的要求
核心要求
| 要求 | 说明 | 重要性 |
|---|---|---|
| 全局唯一 | 不能有重复ID | ⭐⭐⭐ 必须 |
| 趋势递增 | 新ID比旧ID大 | ⭐⭐⭐ 重要(MySQL B+树索引) |
| 高性能 | QPS > 10万 | ⭐⭐⭐ 重要 |
| 高可用 | 不能单点故障 | ⭐⭐⭐ 重要 |
| 信息安全 | 不暴露业务信息 | ⭐⭐ 可选 |
为什么需要趋势递增?
MySQL的B+树索引:
自增ID(有序):
插入ID=1 → 插入ID=2 → 插入ID=3
↓
顺序插入B+树叶子节点
↓
性能好 ✅(顺序IO)
随机ID(无序):
插入ID=999 → 插入ID=2 → 插入ID=500
↓
随机插入B+树,可能导致页分裂
↓
性能差 ❌(随机IO,页分裂)
结论:趋势递增的ID对数据库友好 ⭐⭐⭐
🎯 五种分布式ID生成方案
方案1:数据库自增ID 🗄️
原理
单机版:
CREATE TABLE id_generator (
id BIGINT NOT NULL AUTO_INCREMENT,
stub CHAR(1) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY (stub)
) ENGINE=InnoDB;
-- 获取ID
INSERT INTO id_generator (stub) VALUES ('a');
SELECT 0;
分布式版(多实例,不同步长):
数据库1(步长2,初始值1):
1, 3, 5, 7, 9, ...
数据库2(步长2,初始值2):
2, 4, 6, 8, 10, ...
结果:所有ID唯一 ✅
配置
-- 数据库1配置
SET @@auto_increment_increment = 2; -- 步长
SET @@auto_increment_offset = 1; -- 初始值
-- 数据库2配置
SET @@auto_increment_increment = 2; -- 步长
SET @@auto_increment_offset = 2; -- 初始值
代码实现
@Service
@Slf4j
public class DatabaseIdGenerator {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 生成ID
*/
public Long generateId() {
// 插入记录
jdbcTemplate.update("INSERT INTO id_generator (stub) VALUES ('a')");
// 获取自增ID
Long id = jdbcTemplate.queryForObject("SELECT 0", Long.class);
log.info("生成ID: {}", id);
return id;
}
}
优缺点
优点 ✅:
- 实现简单
- 趋势递增(满足MySQL索引要求)
- 数据库天然保证唯一性
缺点 ❌:
- 性能瓶颈(数据库IO)
- 单点故障(主库挂了)
- 扩展性差(增加数据库需要重新配置步长)
优化:号段模式(见下文)
方案2:号段模式(Leaf-segment)🍃
原理
核心思想:一次性从数据库获取一批ID,缓存在内存中
数据库:
┌─────────────────────────────────────┐
│ biz_tag | max_id | step | version │
│ order | 1000 | 1000 | 1 │
│ user | 5000 | 1000 | 1 │
└─────────────────────────────────────┘
应用服务器:
1. 从数据库获取号段:[1001, 2000]
2. 更新数据库:max_id = 2000
3. 内存分配:1001, 1002, 1003, ..., 2000
4. 号段用完,再次获取:[2001, 3000]
数据库设计
CREATE TABLE leaf_alloc (
biz_tag VARCHAR(128) NOT NULL COMMENT '业务标识',
max_id BIGINT NOT NULL COMMENT '当前最大ID',
step INT NOT NULL COMMENT '步长(号段大小)',
description VARCHAR(256) DEFAULT NULL COMMENT '描述',
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (biz_tag)
) ENGINE=InnoDB;
-- 初始化数据
INSERT INTO leaf_alloc (biz_tag, max_id, step, description)
VALUES ('order', 1, 1000, '订单ID');
代码实现
@Component
@Slf4j
public class LeafSegmentIdGenerator {
@Autowired
private JdbcTemplate jdbcTemplate;
// 缓存号段
private Map<String, Segment> segmentMap = new ConcurrentHashMap<>();
/**
* 生成ID
*/
public Long generateId(String bizTag) {
// 获取号段
Segment segment = segmentMap.computeIfAbsent(bizTag, k -> new Segment());
// 从号段中获取ID
Long id = segment.getId();
if (id == null) {
// 号段用完,重新获取
synchronized (segment) {
// 双重检查
if (segment.getId() == null) {
loadSegment(bizTag, segment);
id = segment.getId();
}
}
}
return id;
}
/**
* ⭐ 从数据库加载号段
*/
private void loadSegment(String bizTag, Segment segment) {
// ⭐ 更新数据库,获取号段
String sql =
"UPDATE leaf_alloc " +
"SET max_id = max_id + step " +
"WHERE biz_tag = ?";
jdbcTemplate.update(sql, bizTag);
// 查询号段范围
String querySql =
"SELECT max_id, step FROM leaf_alloc WHERE biz_tag = ?";
Map<String, Object> row = jdbcTemplate.queryForMap(querySql, bizTag);
Long maxId = (Long) row.get("max_id");
Integer step = (Integer) row.get("step");
// 计算号段范围:[current, max]
long current = maxId - step + 1;
long max = maxId;
// 设置号段
segment.setCurrent(current);
segment.setMax(max);
log.info("加载号段成功: bizTag={}, range=[{}, {}]", bizTag, current, max);
}
/**
* 号段类
*/
static class Segment {
private AtomicLong current = new AtomicLong(0);
private long max = 0;
public Long getId() {
long val = current.incrementAndGet();
if (val <= max) {
return val;
}
return null; // 号段用完
}
public void setCurrent(long current) {
this.current.set(current - 1); // -1是因为getId会先+1
}
public void setMax(long max) {
this.max = max;
}
}
}
双Buffer优化
问题:号段用完,需要等待数据库IO
优化:双Buffer,提前加载下一个号段
static class Segment {
private SegmentBuffer buffer = new SegmentBuffer();
static class SegmentBuffer {
private Segment current; // 当前号段
private Segment next; // 下一个号段
private volatile boolean nextReady = false; // 下一个号段是否就绪
}
public Long getId() {
// 从当前号段获取ID
Long id = buffer.current.getId();
// ⭐ 当前号段使用到10%时,异步加载下一个号段
if (buffer.current.getUsagePercent() > 0.9 && !buffer.nextReady) {
loadNextSegmentAsync();
}
// ⭐ 当前号段用完,切换到下一个号段
if (id == null && buffer.nextReady) {
buffer.current = buffer.next;
buffer.nextReady = false;
id = buffer.current.getId();
}
return id;
}
}
优缺点
优点 ✅:
- 性能高(内存分配,减少数据库访问)
- 数据库压力小(批量获取)
- 趋势递增
- 可扩展(支持多业务)
缺点 ❌:
- 号段浪费(服务重启,未使用的号段丢失)
- 不是严格递增(多机器并发分配)
适用场景:
- 大部分业务场景 ⭐⭐⭐
- 美团Leaf-segment方案
方案3:雪花算法(Snowflake)❄️
原理
64位Long类型ID:
┌───────────┬──────┬──────┬────────────┐
│ 41位 │ 10位 │ 12位 │ │
│ 时间戳 │机器ID│ 序列号│ 64位 │
└───────────┴──────┴──────┴────────────┘
↑ ↑ ↑
│ │ └─ 同一毫秒内的序列号(0-4095)
│ └──────── 机器ID(0-1023)
└────────────────── 时间戳(毫秒级)
结构:
- 1位符号位:0(正数)
- 41位时间戳:精确到毫秒,可用69年
- 10位机器ID:支持1024台机器
- 5位数据中心ID(0-31)
- 5位机器ID(0-31)
- 12位序列号:同一毫秒内最多生成4096个ID
特点:
- 全局唯一 ✅
- 趋势递增 ✅(时间戳递增)
- 高性能 ✅(内存生成,无IO)
- 去中心化 ✅(每台机器独立生成)
代码实现
@Component
@Slf4j
public class SnowflakeIdGenerator {
// ⭐ 起始时间戳(2020-01-01)
private static final long START_TIMESTAMP = 1577808000000L;
// ⭐ 各部分的位数
private static final long DATACENTER_ID_BITS = 5L; // 数据中心ID位数
private static final long WORKER_ID_BITS = 5L; // 机器ID位数
private static final long SEQUENCE_BITS = 12L; // 序列号位数
// ⭐ 各部分的最大值
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS); // 31
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); // 31
private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS); // 4095
// ⭐ 各部分的左移位数
private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; // 12
private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; // 17
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS; // 22
// ⭐ 机器标识
private long datacenterId; // 数据中心ID(0-31)
private long workerId; // 机器ID(0-31)
// ⭐ 序列号
private long sequence = 0L;
// ⭐ 上次生成ID的时间戳
private long lastTimestamp = -1L;
public SnowflakeIdGenerator() {
// 默认:数据中心ID=1,机器ID=1
this(1, 1);
}
public SnowflakeIdGenerator(long datacenterId, long workerId) {
if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId超出范围");
}
if (workerId > MAX_WORKER_ID || workerId < 0) {
throw new IllegalArgumentException("workerId超出范围");
}
this.datacenterId = datacenterId;
this.workerId = workerId;
log.info("Snowflake初始化: datacenterId={}, workerId={}", datacenterId, workerId);
}
/**
* ⭐ 生成ID(线程安全)
*/
public synchronized long generateId() {
long timestamp = System.currentTimeMillis();
// ⭐ 时钟回拨检查
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("时钟回拨,拒绝生成ID: lastTimestamp=%d, current=%d",
lastTimestamp, timestamp)
);
}
// ⭐ 同一毫秒内
if (timestamp == lastTimestamp) {
// 序列号+1
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0) {
// 序列号溢出(同一毫秒生成超过4096个ID)
// 等待下一毫秒
timestamp = waitNextMillis(lastTimestamp);
}
} else {
// 不同毫秒,序列号重置为0
sequence = 0L;
}
// 更新上次时间戳
lastTimestamp = timestamp;
// ⭐ 组装ID
long id = ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT) // 时间戳部分
| (datacenterId << DATACENTER_ID_SHIFT) // 数据中心ID部分
| (workerId << WORKER_ID_SHIFT) // 机器ID部分
| sequence; // 序列号部分
return id;
}
/**
* 等待下一毫秒
*/
private long waitNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
/**
* 解析ID(测试用)
*/
public String parseId(long id) {
long timestamp = ((id >> TIMESTAMP_SHIFT) & ~(-1L << 41)) + START_TIMESTAMP;
long datacenterId = (id >> DATACENTER_ID_SHIFT) & MAX_DATACENTER_ID;
long workerId = (id >> WORKER_ID_SHIFT) & MAX_WORKER_ID;
long sequence = id & MAX_SEQUENCE;
return String.format(
"ID=%d, timestamp=%d (%s), datacenterId=%d, workerId=%d, sequence=%d",
id, timestamp, new Date(timestamp), datacenterId, workerId, sequence
);
}
}
使用示例
@SpringBootTest
public class SnowflakeTest {
@Test
public void testGenerate() {
SnowflakeIdGenerator generator = new SnowflakeIdGenerator(1, 1);
// 生成10个ID
for (int i = 0; i < 10; i++) {
long id = generator.generateId();
System.out.println(generator.parseId(id));
}
}
@Test
public void testPerformance() {
SnowflakeIdGenerator generator = new SnowflakeIdGenerator(1, 1);
int count = 1000000; // 100万
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
generator.generateId();
}
long end = System.currentTimeMillis();
System.out.println("生成" + count + "个ID,耗时: " + (end - start) + "ms");
System.out.println("QPS: " + (count * 1000 / (end - start)));
}
}
输出:
ID=123456789012345, timestamp=1609459200000 (2021-01-01 00:00:00), datacenterId=1, workerId=1, sequence=0
ID=123456789012346, timestamp=1609459200000 (2021-01-01 00:00:00), datacenterId=1, workerId=1, sequence=1
...
生成1000000个ID,耗时: 500ms
QPS: 2000000 ← ⭐ 200万QPS
时钟回拨问题
问题:服务器时钟回拨,导致生成重复ID
10:00:00 生成ID(timestamp=10:00:00)
10:00:05 【时钟回拨到09:59:00】💀
10:00:06 生成ID(timestamp=09:59:00)
↓
可能生成重复ID ❌
解决方案1:拒绝生成 ❌
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨,拒绝生成ID");
}
缺点:服务不可用
解决方案2:等待时钟追上 ⚠️
if (timestamp < lastTimestamp) {
long offset = lastTimestamp - timestamp;
if (offset > 5000) { // 回拨超过5秒,拒绝
throw new RuntimeException("时钟回拨过大");
}
// 等待时钟追上
Thread.sleep(offset);
timestamp = System.currentTimeMillis();
}
缺点:等待时间长
解决方案3:使用扩展位 ✅(推荐)
// 预留2位扩展位
// 时钟回拨时,扩展位+1
┌───────────┬──────┬──────┬──┬──────────┐
│ 39位 │ 10位 │ 2位 │12位 │
│ 时间戳 │机器ID│扩展位│序列号 │
└───────────┴──────┴──────┴───────────┘
↑
扩展位(0-3)
优缺点
优点 ✅:
- 全局唯一
- 趋势递增(时间戳递增)
- 高性能(200万QPS)
- 去中心化(无数据库依赖)
- 包含时间信息(可解析)
缺点 ❌:
- 依赖系统时钟(时钟回拨问题)
- 机器ID需要配置(手动或自动分配)
- 不是严格递增(多机器并发)
适用场景:
- 高性能场景 ⭐⭐⭐
- 分布式系统
- 大部分互联网公司的选择(Twitter、美团等)
方案4:Redis生成ID 🔴
原理
利用Redis的INCR命令(原子操作)
INCR key
每次调用返回递增的值:
INCR order_id → 1
INCR order_id → 2
INCR order_id → 3
...
代码实现
@Component
@Slf4j
public class RedisIdGenerator {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 生成ID
*/
public Long generateId(String bizTag) {
String key = "id:" + bizTag;
// ⭐ INCR命令(原子操作)
Long id = redisTemplate.opsForValue().increment(key);
log.info("生成ID: bizTag={}, id={}", bizTag, id);
return id;
}
/**
* 批量生成ID(性能优化)
*/
public List<Long> generateIds(String bizTag, int count) {
String key = "id:" + bizTag;
// ⭐ INCRBY命令(一次获取多个)
Long maxId = redisTemplate.opsForValue().increment(key, count);
// 返回 [maxId - count + 1, maxId]
List<Long> ids = new ArrayList<>();
for (long i = maxId - count + 1; i <= maxId; i++) {
ids.add(i);
}
return ids;
}
}
优化:加入时间戳
/**
* 生成ID(包含时间戳)
*
* 格式:yyyyMMddHHmmss + 6位序号
* 例如:20210101120000000001
*/
public String generateIdWithTimestamp(String bizTag) {
// 时间戳部分
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
// 序号部分(Redis INCR)
String key = "id:" + bizTag + ":" + timestamp;
Long seq = redisTemplate.opsForValue().increment(key);
// 设置过期时间(1天后过期)
redisTemplate.expire(key, 1, TimeUnit.DAYS);
// 拼接ID
String id = timestamp + String.format("%06d", seq);
return id;
}
高可用方案
问题:Redis单点故障
解决方案1:Redis集群
Redis Master-Slave集群
↓
Master挂了 → Slave升级为Master
↓
可能丢失部分ID(主从同步延迟)⚠️
解决方案2:Redis Cluster + 步长
Redis1:生成 1, 4, 7, 10, ...(步长3,初始值1)
Redis2:生成 2, 5, 8, 11, ...(步长3,初始值2)
Redis3:生成 3, 6, 9, 12, ...(步长3,初始值3)
优缺点
优点 ✅:
- 实现简单
- 性能好(Redis内存操作)
- 严格递增(单Redis实例)
缺点 ❌:
- 依赖Redis(单点故障)
- 网络IO(比Snowflake慢)
- 需要持久化(AOF/RDB)
适用场景:
- 已有Redis集群
- 对ID严格递增有要求
- 并发量不是特别高
方案5:UUID 🆔
原理
UUID = Universally Unique Identifier(通用唯一识别码)
格式:8-4-4-4-12,共36个字符
550e8400-e29b-41d4-a716-446655440000
↑ ↑ ↑ ↑ ↑
时间戳 版本 变体 随机数
长度:32个十六进制字符 + 4个连字符 = 36个字符
代码实现
import java.util.UUID;
@Component
public class UUIDGenerator {
/**
* 生成UUID
*/
public String generateId() {
return UUID.randomUUID().toString();
}
/**
* 生成UUID(去掉连字符)
*/
public String generateIdWithoutDash() {
return UUID.randomUUID().toString().replace("-", "");
}
}
优缺点
优点 ✅:
- 实现简单(JDK内置)
- 全局唯一
- 去中心化(无依赖)
缺点 ❌:
- 不是趋势递增(随机,对MySQL索引不友好)❌❌❌
- 太长(36个字符,占用存储空间大)
- 无序(不适合做主键)
适用场景:
- 不需要趋势递增的场景
- 文件名、临时ID等
- 不适合做数据库主键 ❌
📊 五种方案对比
| 方案 | 性能 | 趋势递增 | 依赖 | 适用场景 |
|---|---|---|---|---|
| 数据库自增 | ⭐ 低 | ✅ 是 | 数据库 | 低并发 |
| 号段模式 | ⭐⭐⭐ 高 | ✅ 是 | 数据库 | 通用 ⭐⭐⭐ |
| 雪花算法 | ⭐⭐⭐ 极高 | ✅ 是 | 无 | 高并发 ⭐⭐⭐ |
| Redis | ⭐⭐ 中 | ✅ 是 | Redis | 中等并发 |
| UUID | ⭐⭐⭐ 高 | ❌ 否 | 无 | 不适合主键 ❌ |
🎓 面试题速答
Q1: 设计一个分布式ID生成器,有哪些方案?
A: 五种方案:
- 数据库自增ID:简单,但性能差
- 号段模式:批量获取,性能好(美团Leaf)⭐
- 雪花算法:高性能,去中心化(Twitter)⭐⭐⭐
- Redis INCR:简单,依赖Redis
- UUID:全局唯一,但不是趋势递增 ❌
推荐:雪花算法(高并发)或号段模式(通用)
Q2: 雪花算法的原理是什么?
A: 64位Long类型ID:
┌───────────┬──────┬──────┬────────────┐
│ 41位 │ 10位 │ 12位 │ 64位 │
│ 时间戳 │机器ID│序列号│ │
└───────────┴──────┴──────┴────────────┘
组成:
- 41位时间戳:精确到毫秒,可用69年
- 10位机器ID:支持1024台机器
- 12位序列号:同一毫秒最多4096个ID
特点:
- 全局唯一 ✅
- 趋势递增 ✅
- 高性能(200万QPS)✅
Q3: 雪花算法的时钟回拨问题如何解决?
A: 三种方案:
-
拒绝生成:
if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨"); }缺点:服务不可用
-
等待时钟追上:
Thread.sleep(lastTimestamp - timestamp);缺点:等待时间长
-
使用扩展位(推荐):
- 预留2位扩展位
- 时钟回拨时,扩展位+1
- 最多支持4次回拨
Q4: 为什么UUID不适合做数据库主键?
A: 三个原因:
-
不是趋势递增:
- UUID是随机的
- 插入MySQL时,随机位置插入
- 导致页分裂,性能差 ❌
-
太长:
- 36个字符(包含连字符)
- 占用存储空间大
- 索引占用空间大
-
无序:
- 不方便排序
- 不方便分页
结论:UUID适合文件名等场景,不适合数据库主键 ❌
Q5: 号段模式的原理是什么?
A: 核心思想:批量获取ID,缓存在内存中
1. 从数据库获取号段:[1001, 2000]
2. 更新数据库:max_id = 2000
3. 内存分配:1001, 1002, ..., 2000
4. 号段用完,再次获取:[2001, 3000]
优点:
- 减少数据库访问(批量获取)
- 性能高(内存分配)
- 趋势递增
优化:双Buffer
- 当前号段使用到10%时,异步加载下一个号段
- 避免号段用完时的等待
Q6: 如何选择分布式ID方案?
A: 根据场景选择:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 高并发(秒杀) | 雪花算法 | 性能极高(200万QPS)⭐⭐⭐ |
| 通用业务 | 号段模式 | 性能好,实现简单 ⭐⭐⭐ |
| 低并发 | 数据库自增 | 简单 ⭐ |
| 已有Redis | Redis INCR | 利用现有资源 ⭐⭐ |
| 文件名等 | UUID | 不需要趋势递增 ⭐ |
推荐:
- 高并发:雪花算法 ⭐⭐⭐
- 通用:号段模式 ⭐⭐⭐
🎬 总结
分布式ID生成方案对比
┌────────────────────────────────────┐
│ 数据库自增:简单,性能差 ⭐ │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 号段模式:批量获取,性能好 ⭐⭐⭐ │
│ (美团Leaf) │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 雪花算法:高性能,去中心化 ⭐⭐⭐ │
│ (Twitter Snowflake) │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ Redis:简单,依赖Redis ⭐⭐ │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ UUID:全局唯一,但不趋势递增 ❌ │
└────────────────────────────────────┘
高并发用雪花,通用用号段!✅
🎉 恭喜你!
你已经完全掌握了分布式ID生成器的设计!🎊
核心要点:
- 雪花算法:高性能,去中心化 ⭐⭐⭐
- 号段模式:批量获取,性能好 ⭐⭐⭐
- UUID不适合做主键:不是趋势递增 ❌
下次面试,这样回答:
"分布式ID生成器有5种方案,最常用的是雪花算法和号段模式。
雪花算法生成64位Long类型ID,包含41位时间戳、10位机器ID和12位序列号。优点是全局唯一、趋势递增、高性能(200万QPS)、去中心化。缺点是依赖系统时钟,有时钟回拨问题,可以通过预留扩展位解决。
号段模式是一次性从数据库获取一批ID(如1000个),缓存在内存中分配,减少数据库访问。可以用双Buffer优化,当前号段使用到10%时异步加载下一个号段,避免等待。
我们项目的订单系统使用雪花算法生成订单号,QPS达到50万,机器ID通过Zookeeper自动分配,避免手动配置。"
面试官:👍 "很好!你对分布式ID生成器理解很深刻!"
本文完 🎬
上一篇: 198-分布式事务的解决方案.md
下一篇: 200-设计一个亿级用户的社交关系链存储.md
作者注:写完这篇,我都想去公安局办身份证了!🆔
如果这篇文章对你有帮助,请给我一个Star⭐!