美团真题:分布式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圣经

全部评论
欢迎讨论
点赞 回复 分享
发布于 09-06 11:26 江西

相关推荐

评论
2
3
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务