美团真题:分布式ID生成方案
面试重要程度:⭐⭐⭐⭐⭐
真题来源:美团2024春招技术面试
考察重点:分布式系统设计、ID生成算法、高可用架构
预计阅读时间:40分钟
真题背景
面试官: "美团每天产生数十亿订单、用户行为等数据,需要为这些数据生成全局唯一ID。要求ID生成服务支持每秒百万级请求,保证ID全局唯一、趋势递增、高可用。请设计一个分布式ID生成方案,包括多种算法对比、架构设计、容灾方案等。"
考察意图:
- 分布式系统核心组件设计能力
- 多种ID生成算法的理解和选择
- 高并发、高可用架构设计
- 系统容灾和故障处理能力
🎯 需求分析与方案对比
业务需求分析
/**
* 分布式ID生成需求定义
*/
public class DistributedIdRequirements {
// 性能需求
public static final long MAX_QPS = 1_000_000; // 百万级QPS
public static final int MAX_LATENCY_MS = 5; // 延迟<5ms
public static final double AVAILABILITY = 99.99; // 99.99%可用性
// 功能需求
public static final boolean GLOBAL_UNIQUE = true; // 全局唯一
public static final boolean TREND_INCREASING = true; // 趋势递增
public static final boolean NO_DUPLICATE = true; // 不重复
public static final int ID_LENGTH = 64; // 64位长度
// 业务特性
public static final boolean SORTABLE = true; // 可排序
public static final boolean MEANINGFUL = false; // 无业务含义
public static final boolean SECURITY = true; // 安全性(不可预测)
}
ID生成方案对比
/**
* 分布式ID生成方案对比
*/
@Component
public class IdGenerationComparison {
/**
* 方案1:数据库自增ID
*/
public static class DatabaseAutoIncrement {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 单机数据库自增
*/
public Long generateId() {
String sql = "INSERT INTO id_generator (stub) VALUES ('a')";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
return ps;
}, keyHolder);
return keyHolder.getKey().longValue();
}
/**
* 多机数据库自增(设置步长)
*/
public Long generateIdWithStep(int serverId, int serverCount) {
// 设置自增起始值和步长
String setAutoIncrement = String.format(
"ALTER TABLE id_generator AUTO_INCREMENT = %d", serverId);
String setStep = String.format(
"SET @@auto_increment_increment = %d", serverCount);
jdbcTemplate.execute(setAutoIncrement);
jdbcTemplate.execute(setStep);
return generateId();
}
// 优点:简单易实现,强一致性
// 缺点:性能瓶颈,单点故障,扩展困难
}
/**
* 方案2:UUID
*/
public static class UUIDGenerator {
/**
* 标准UUID
*/
public String generateUUID() {
return UUID.randomUUID().toString();
}
/**
* 压缩UUID(去掉横线)
*/
public String generateCompactUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* 数值型UUID
*/
public Long generateNumericUUID() {
UUID uuid = UUID.randomUUID();
return uuid.getMostSignificantBits() ^ uuid.getLeastSignificantBits();
}
// 优点:本地生成,性能高,无单点故障
// 缺点:无序,长度长,不适合做主键
}
/**
* 方案3:Snowflake算法
*/
public static class SnowflakeIdGenerator {
// Snowflake算法参数
private static final long START_TIMESTAMP = 1609459200000L; // 2021-01-01
private static final long SEQUENCE_BITS = 12;
private static final long MACHINE_BITS = 10;
private static final long TIMESTAMP_BITS = 41;
private static final long MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;
private static final long MAX_MACHINE_ID = (1L << MACHINE_BITS) - 1;
private final long machineId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long machineId) {
if (machineId > MAX_MACHINE_ID || machineId < 0) {
throw new IllegalArgumentException("Machine ID out of range");
}
this.machineId = machineId;
}
/**
* 生成Snowflake ID
*/
public synchronized long generateId() {
long currentTimestamp = System.currentTimeMillis();
// 时钟回拨检查
if (currentTimestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
// 同一毫秒内
if (currentTimestamp == lastTimestamp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0) {
// 序列号用完,等待下一毫秒
currentTimestamp = waitNextMillis(currentTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = currentTimestamp;
// 组装ID:时间戳(41位) + 机器ID(10位) + 序列号(12位)
return ((currentTimestamp - START_TIMESTAMP) << (MACHINE_BITS + SEQUENCE_BITS))
| (machineId << SEQUENCE_BITS)
| sequence;
}
private long waitNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
// 优点:高性能,趋势递增,包含时间信息
// 缺点:依赖机器时钟,机器ID管理复杂
}
/**
* 方案4:号段模式
*/
public static class SegmentIdGenerator {
@Autowired
private IdSegmentMapper segmentMapper;
private final ConcurrentHashMap<String, SegmentBuffer> segmentBuffers = new ConcurrentHashMap<>();
/**
* 号段缓冲区
*/
public static class SegmentBuffer {
private volatile Segment currentSegment;
private volatile Segment nextSegment;
private volatile boolean isLoadingNext = false;
private final ReentrantLock lock = new ReentrantLock();
public static class Segment {
private final AtomicLong value;
private final long maxId;
private final int step;
public Segment(long currentId, int step) {
this.value = new AtomicLong(currentId);
this.maxId = currentId + step;
this.step = step;
}
public long getAndIncrement() {
return value.getAndIncrement();
}
public boolean isAvailable() {
return value.get() < maxId;
}
public boolean shouldLoadNext() {
return value.get() >= maxId * 0.9; // 使用90%时开始加载下一段
}
}
}
/**
* 生成ID
*/
public Long generateId(String bizType) {
SegmentBuffer buffer = segmentBuffers.computeIfAbsent(bizType,
k -> initSegmentBuffer(bizType));
return getIdFromBuffer(buffer, bizType);
}
private Long getIdFromBuffer(SegmentBuffer buffer, String bizType) {
while (true) {
buffer.lock.lock();
try {
Segment segment = buffer.currentSegment;
// 检查是否需要加载下一段
if (!buffer.isLoadingNext && segment.shouldLoadNext() && buffer.nextSegment == null) {
buffer.isLoadingNext = true;
// 异步加载下一段
CompletableFuture.runAsync(() -> loadNextSegment(buffer, bizType));
}
// 当前段还有可用ID
if (segment.isAvailable()) {
return segment.getAndIncrement();
}
// 当前段用完,切换到下一段
if (buffer.nextSegment != null) {
buffer.currentSegment = buffer.nextSegment;
buffer.nextSegment = null;
buffer.isLoadingNext = false;
continue;
}
// 没有下一段,同步加载
loadNextSegment(buffer, bizType);
} finally {
buffer.lock.unlock();
}
}
}
private void loadNextSegment(SegmentBuffer buffer, String bizType) {
try {
// 从数据库获取新号段
IdSegment segment = segmentMapper.getNextSegment(bizType);
buffer.nextSegment = new SegmentBuffer.Segment(segment.getCurrentId(), segment.getStep());
buffer.isLoadingNext = false;
} catch (Exception e) {
log.error("Failed to load next segment for bizType: {}", bizType, e);
buffer.isLoadingNext = false;
}
}
// 优点:高性能,数据库压力小,支持多业务类型
// 缺点:ID不连续,重启会有空洞
}
}
🏗️ 架构设计方案
基于Snowflake的分布式ID服务
/**
* 分布式ID生成服务架构
*/
@SpringBootApplication
public class DistributedIdService {
/**
* ID生成服务核心组件
*/
@RestController
@RequestMapping("/api/id")
public class IdGeneratorController {
@Autowired
private IdGeneratorService idGeneratorServ
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经 文章被收录于专栏
Java面试圣经,带你练透java圣经
查看14道真题和解析