分布式ID实现方式全面指南

分布式ID实现方式全面指南

目录

  1. 概述
  2. 分布式ID的核心需求
  3. UUID实现方式
  4. 数据库自增ID
  5. 数据库号段模式
  6. Redis实现方式
  7. 雪花算法(Snowflake)
  8. Leaf算法(美团)
  9. UidGenerator算法(百度)
  10. TinyID算法(滴滴)
  11. 各种实现方式对比
  12. 选择指南与最佳实践
  13. 实际应用案例

概述

在分布式系统中,生成全局唯一ID是一个核心问题。传统的单机数据库自增ID在分布式环境下会面临冲突、性能瓶颈等问题。分布式ID需要满足全局唯一性、高性能、高可用性、趋势递增等特性。

分布式ID的核心需求

基本特性

  • 全局唯一性:不能出现重复的ID
  • 高性能:高并发场景下生成速度快
  • 高可用性:服务不能成为单点故障
  • 趋势递增:有利于数据库索引性能

可选特性

  • 单调递增:严格递增,有利于排序
  • 信息安全:ID中不包含业务信息
  • 长度可控:ID长度适中,便于存储和传输
  • 可反解:能从ID中解析出有用信息

UUID实现方式

原理

UUID(Universally Unique Identifier)标准形式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符。

版本分类

  • UUID1:基于时间戳和MAC地址
  • UUID3:基于命名空间和MD5散列
  • UUID4:基于随机数
  • UUID5:基于命名空间和SHA-1散列

实现示例

import java.util.UUID;

public class UUIDGenerator {
    public static String generateUUID() {
        return UUID.randomUUID().toString();
    }
    
    public static String generateUUIDWithoutDash() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

优缺点分析

优点:

  • 实现简单,无需额外依赖
  • 本地生成,性能高
  • 全球唯一性保证

缺点:

  • 长度较长(36位或32位)
  • 无序,不利于数据库索引
  • 无法反解出有用信息
  • 存储空间大

适用场景

  • 对ID长度不敏感的场景
  • 不需要排序的业务
  • 临时数据标识

数据库自增ID

原理

利用数据库的自增主键特性,通过单点数据库生成唯一ID。

实现方式

-- 创建ID生成表
CREATE TABLE distributed_id (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    stub CHAR(1) NOT NULL,
    UNIQUE KEY stub (stub)
) ENGINE=MyISAM;

-- 获取ID的存储过程
DELIMITER $$
CREATE PROCEDURE GetDistributedId(OUT result BIGINT)
BEGIN
    INSERT INTO distributed_id (stub) VALUES ('a');
    SET result = LAST_INSERT_ID();
    DELETE FROM distributed_id WHERE stub = 'a';
END$$
DELIMITER ;

Java实现

@Component
public class DatabaseIdGenerator {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public long generateId() {
        String insertSql = "INSERT INTO distributed_id (stub) VALUES ('a')";
        String deleteSql = "DELETE FROM distributed_id WHERE stub = 'a'";
        
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS);
            return ps;
        }, keyHolder);
        
        long id = keyHolder.getKey().longValue();
        jdbcTemplate.update(deleteSql);
        
        return id;
    }
}

优缺点分析

优点:

  • 实现简单,易于理解
  • ID单调递增,有利于数据库性能
  • 数字类型,存储空间小

缺点:

  • 存在单点故障风险
  • 性能瓶颈,并发能力有限
  • 需要数据库连接,网络开销

改进方案

  • 主从架构:主库故障时切换到从库
  • 双主架构:两个数据库实例同时提供服务
  • 步长设置:不同实例使用不同步长

数据库号段模式

原理

一次性从数据库获取一个号段(如1000个ID),在内存中分配,用完后再获取新的号段。

数据库设计

CREATE TABLE id_generator (
    id INT AUTO_INCREMENT PRIMARY KEY,
    business_tag VARCHAR(50) NOT NULL COMMENT '业务标识',
    max_id BIGINT NOT NULL COMMENT '当前最大ID',
    step INT NOT NULL COMMENT '号段长度',
    version INT NOT NULL DEFAULT 0 COMMENT '版本号',
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uniq_business_tag (business_tag)
) ENGINE=InnoDB;

实现代码

@Component
public class SegmentIdGenerator {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    private final Map<String, Segment> segments = new ConcurrentHashMap<>();
    
    public synchronized long generateId(String businessTag) {
        Segment segment = segments.get(businessTag);
        
        if (segment == null || segment.isExhausted()) {
            segment = fetchNewSegment(businessTag);
            segments.put(businessTag, segment);
        }
        
        return segment.nextId();
    }
    
    private Segment fetchNewSegment(String businessTag) {
        return jdbcTemplate.execute(connection -> {
            connection.setAutoCommit(false);
            
            try {
                // 使用乐观锁获取号段
                String selectSql = "SELECT max_id, step, version FROM id_generator WHERE business_tag = ? FOR UPDATE";
                PreparedStatement selectStmt = connection.prepareStatement(selectSql);
                selectStmt.setString(1, businessTag);
                ResultSet rs = selectStmt.executeQuery();
                
                if (!rs.next()) {
                    // 初始化业务标识
                    String insertSql = "INSERT INTO id_generator (business_tag, max_id, step) VALUES (?, ?, ?)";
                    PreparedStatement insertStmt = connection.prepareStatement(insertSql);
                    insertStmt.setString(1, businessTag);
                    insertStmt.setLong(2, 1000);
                    insertStmt.setInt(3, 1000);
                    insertStmt.executeUpdate();
                    
                    connection.commit();
                    return new Segment(1, 1000);
                }
                
                long maxId = rs.getLong("max_id");
                int step = rs.getInt("step");
                int version = rs.getInt("version");
                
                // 更新号段信息
                String updateSql = "UPDATE id_generator SET max_id = ?, version = ? WHERE business_tag = ? AND version = ?";
                PreparedStatement updateStmt = connection.prepareStatement(updateSql);
                updateStmt.setLong(1, maxId + step);
                updateStmt.setInt(2, version + 1);
                updateStmt.setString(3, businessTag);
                updateStmt.setInt(4, version);
                
                int affectedRows = updateStmt.executeUpdate();
                if (affectedRows == 0) {
                    connection.rollback();
                    throw new RuntimeException("Concurrent update conflict");
                }
                
                connection.commit();
                return new Segment(maxId + 1, maxId + step);
                
            } catch (Exception e) {
                connection.rollback();
                throw new RuntimeException("Failed to fetch segment", e);
            }
        });
    }
    
    private static class Segment {
        private final long startId;
        private final long endId;
        private final AtomicLong currentId;
        
        public Segment(long startId, long endId) {
            this.startId = startId;
            this.endId = endId;
            this.currentId = new AtomicLong(startId);
        }
        
        public long nextId() {
            long id = currentId.getAndIncrement();
            if (id > endId) {
                throw new RuntimeException("Segment exhausted");
            }
            return id;
        }
        
        public boolean isExhausted() {
            return currentId.get() > endId;
        }
    }
}

优缺点分析

优点:

  • 性能高,批量获取减少数据库访问
  • 趋势递增,有利于数据库性能
  • 可扩展性强,支持多业务标识

缺点:

  • 存在数据库单点故障风险
  • ID不连续,可能存在跳跃
  • 需要处理号段用完的边界情况

Redis实现方式

原理

利用Redis的原子操作(INCR、INCRBY)生成唯一ID。

基本实现

@Component
public class RedisIdGenerator {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public long generateId(String key) {
        return redisTemplate.opsForValue().increment(key);
    }
    
    public List<Long> generateIds(String key, int count) {
        Long increment = redisTemplate.opsForValue().increment(key, count);
        List<Long> ids = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            ids.add(increment - count + i + 1);
        }
        return ids;
    }
}

高可用方案

@Configuration
public class RedisIdConfig {
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSentinelServers()
            .setMasterName("mymaster")
            .addSentinelAddress("redis://sentinel1:26379", "redis://sentinel2:26379", "redis://sentinel3:26379")
            .setPassword("password");
        return Redisson.create(config);
    }
}

@Component
public class HighAvailabilityRedisIdGenerator {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public long generateId(String businessKey) {
        RAtomicLong atomicLong = redissonClient.getAtomicLong("id_generator:" + businessKey);
        return atomicLong.incrementAndGet();
    }
}

优缺点分析

优点:

  • 性能高,基于内存操作
  • 原子性保证,不会重复
  • 支持批量获取
  • 可扩展性好

缺点:

  • 需要维护Redis集群
  • 存在网络开销
  • ID只是数字,不包含业务信息
  • Redis故障时需要降级策略

优化策略

  • 持久化配置:开启RDB和AOF持久化
  • 集群部署:使用Redis Cluster或哨兵模式
  • 本地缓存:结合本地号段模式
  • 降级方案:Redis故障时切换到数据库

雪花算法(Snowflake)

原理

Twitter开源的分布式ID生成算法,生成64位的long型ID,结构如下:

0 | 00000000000000000000000000000000000000000 | 00000 | 00000 | 000000000000
--|-------------------------------------------|-------|-------|-------------
符号位 |                         41位时间戳(毫秒) | 10位机器ID | 12位序列号

完整实现

public class SnowflakeIdGenerator {
    
    // 起始时间戳(2020-01-01)
    private static final long START_TIMESTAMP = 1577836800000L;
    
    // 各部分位数
    private static final long SEQUENCE_BITS = 12L;
    private static final long WORKER_ID_BITS = 10L;
    
    // 各部分最大值
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    
    // 各部分位移
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    
    private final long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    
    public SnowflakeIdGenerator(long workerId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException("Worker ID must be between 0 and " + MAX_WORKER_ID);
        }
        this.workerId = workerId;
    }
    
    public synchronized long generateId() {
        long currentTimestamp = getCurrentTimestamp();
        
        // 时钟回拨处理
        if (currentTimestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate ID");
        }
        
        if (currentTimestamp == lastTimestamp) {
            // 同一毫秒内,序列号递增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0) {
                // 序列号溢出,等待下一毫秒
                currentTimestamp = waitNextMillis(currentTimestamp);
            }
        } else {
            // 不同毫秒,序列号重置
            sequence = 0L;
        }
        
        lastTimestamp = currentTimestamp;
        
        return ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }
    
    private long waitNextMillis(long currentTimestamp) {
        while (currentTimestamp <= lastTimestamp) {
            currentTimestamp = getCurrentTimestamp();
        }
        return currentTimestamp;
    }
    
    private long getCurrentTimestamp() {
        return System.currentTimeMillis();
    }
    
    // 解析ID信息
    public static class IdInfo {
        private long timestamp;
        private long workerId;
        private long sequence;
        
        // 构造函数和getter省略
    }
    
    public static IdInfo parseId(long id) {
        IdInfo info = new IdInfo();
        info.sequence = id & MAX_SEQUENCE;
        info.workerId = (id >> WORKER_ID_SHIFT) & MAX_WORKER_ID;
        info.timestamp = (id >> TIMESTAMP_SHIFT) + START_TIMESTAMP;
        return info;
    }
}

时钟回拨处理优化

public class RobustSnowflakeIdGenerator extends SnowflakeIdGenerator {
    
    private static final long MAX_BACKWARD_MS = 10L; // 最大容忍回拨时间
    
    public RobustSnowflakeIdGenerator(long workerId) {
        super(workerId);
    }
    
    @Override
    public synchronized long generateId() {
        long currentTimestamp = getCurrentTimestamp();
        
        // 时钟回拨处理
        if (currentTimestamp < lastTimestamp) {
            long backwardTime = lastTimestamp - currentTimestamp;
            if (backwardTime <= MAX_BACKWARD_MS) {
                try {
                    // 小幅度回拨,等待时钟追上
                    Thread.sleep(backwardTime);
                    currentTimestamp = getCurrentTimestamp();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("Interrupted while waiting for clock", e);
                }
            } else {
                throw new RuntimeException("Clock moved backwards by " + backwardTime + "ms");
            }
        }
        
        return super.generateId();
    }
}

动态WorkerId分配

@Component
public class DynamicWorkerIdAllocator {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    private static final long MAX_WORKER_ID = 1023; // 10位机器ID最大值
    
    public long allocateWorkerId(String applicationName, String ip, int port) {
        // 尝试获取已有的workerId
        String selectSql = "SELECT worker_id FROM worker_id_allocation WHERE application_name = ? AND ip = ? AND port = ?";
        
        List<Long> existingIds = jdbcTemplate.query(selectSql, 
            new Object[]{applicationName, ip, port},
            (rs, rowNum) -> rs.getLong("worker_id"));
            
        if (!existingIds.isEmpty()) {
            return existingIds.get(0);
        }
        
        // 分配新的workerId
        for (long workerId = 0; workerId <= MAX_WORKER_ID; workerId++) {
            String insertSql = "INSERT INTO worker_id_allocation (worker_id, application_name, ip, port, create_time) VALUES (?, ?, ?, ?, NOW())";
            try {
                jdbcTemplate.update(insertSql, workerId, applicationName, ip, port);
                return workerId;
            } catch (DuplicateKeyException e) {
                // workerId已被占用,继续尝试下一个
                continue;
            }
        }
        
        throw new RuntimeException("No available worker ID");
    }
}

优缺点分析

优点:

  • 性能高,本地生成无网络开销
  • 趋势递增,有利于数据库性能
  • 可反解出时间戳和机器信息
  • 不依赖外部系统,可用性高

缺点:

  • 依赖系统时钟,需要处理时钟回拨
  • 需要分配和管理workerId
  • 41位时间戳有使用期限(约69年)
  • 单节点并发能力有限(4096/毫秒)

Leaf算法(美团)

原理

美团开源的分布式ID生成系统,提供两种模式:Leaf-segment(号段模式)和Leaf-snowflake(雪花模式)。

Leaf-segment模式

@Component
public class LeafSegmentGenerator {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    private final Map<String, SegmentBuffer> segmentBuffers = new ConcurrentHashMap<>();
    
    public long generateId(String tag) {
        SegmentBuffer buffer = segmentBuffers.computeIfAbsent(tag, k -> new SegmentBuffer());
        
        if (!buffer.isInitialized()) {
            synchronized (buffer) {
                if (!buffer.isInitialized()) {
                    updateSegment(buffer, tag);
                    buffer.setInitialized(true);
                }
            }
        }
        
        return getIdFromBuffer(buffer, tag);
    }
    
    private long getIdFromBuffer(SegmentBuffer buffer, String tag) {
        while (true) {
            Segment current = buffer.getCurrent();
            
            if (current.getIdle() > 0) {
                return current.nextId();
            }
            
            synchronized (buffer) {
                if (current.getIdle() == 0) {
                    updateSegment(buffer, tag);
                }
            }
        }
    }
    
    private void updateSegment(SegmentBuffer buffer, String tag) {
        Segment current = buffer.getCurrent();
        Segment next = buffer.getNext();
        
        if (next == null) {
            Segment newSegment = fetchSegment(tag);
            buffer.setNext(newSegment);
            buffer.switchPos();
            buffer.setNext(null);
        } else {
            buffer.switchPos();
            buffer.setNext(null);
            
            // 异步预加载下一个号段
            CompletableFuture.runAsync(() -> {
                Segment newSegment = fetchSegment(tag);
                buffer.setNext(newSegment);
            });
        }
    }
    
    private Segment fetchSegment(String tag) {
        return jdbcTemplate.execute(connection -> {
            connection.setAutoCommit(false);
            
            try {
                // 获取当前号段信息
                String selectSql = "SELECT max_id, step FROM leaf_alloc WHERE biz_tag = ? FOR UPDATE";
                PreparedStatement selectStmt = connection.prepareStatement(selectSql);
                selectStmt.setString(1, tag);
                ResultSet rs = selectStmt.executeQuery();
                
                if (!rs.next()) {
                    throw new RuntimeException("Business tag not found: " + tag);
                }
                
                long maxId = rs.getLong("max_id");
                int step = rs.getInt("step");
                
                // 更新号段信息
                String updateSql = "UPDATE leaf_alloc SET max_id = max_id + step WHERE biz_tag = ?";
                PreparedStatement updateStmt = connection.prepareStatement(updateSql);
                updateStmt.setString(1, tag);
                updateStmt.executeUpdate();
                
                connection.commit();
                
                return new Segment(maxId + 1, maxId + step);
                
            } catch (Exception e) {
                connection.rollback();
                throw new RuntimeException("Failed to fetch segment", e);
            }
        });
    }
    
    private static class Segment {
        private final AtomicLong currentId;
        private final long maxId;
        
        public Segment(long startId, long maxId) {
            this.currentId = new AtomicLong(startId);
            this.maxId = maxId;
        }
        
        public long nextId() {
            return currentId.getAndIncrement();
        }
        
        public long getIdle() {
            return maxId - currentId.get() + 1;
        }
    }
    
    private static class SegmentBuffer {
        private final Segment[] segments = new Segment[2];
        private volatile int currentPos = 0;
        private volatile boolean initialized = false;
        private volatile Segment next = null;
        
        public Segment getCurrent() {
            return segments[currentPos];
        }
        
        public Segment getNext() {
            return next;
        }
        
        public void setNext(Segment next) {
            this.next = next;
        }
        
        public void switchPos() {
            currentPos = 1 - currentPos;
            segments[currentPos] = next;
        }
        
        public boolean isInitialized() {
            return initialized;
        }
        
        public void setInitialized(boolean initialized) {
            this.initialized = initialized;
        }
    }
}

Leaf-snowflake模式

@Component
public class LeafSnowflakeGenerator {
    
    private final SnowflakeIdGenerator snowflakeGenerator;
    private final ZooKeeper zooKeeper;
    private final String zkPath;
    
    public LeafSnowflakeGenerator(String zkServers, String zkPath) throws Exception {
        this.zooKeeper = new ZooKeeper(zkServers, 3000, null);
        this.zkPath = zkPath;
        this.snowflakeGenerator = new SnowflakeIdGenerator(allocateWorkerId());
        
        // 启动时钟检查线程
        startClockCheckThread();
    }
    
    private long allocateWorkerId() throws Exception {
        // 创建临时顺序节点
        String nodePath = zooKeeper.create(
            zkPath + "/worker-",
            null,
            ZooDefs.Ids.OPEN_ACL_UNSAFE,
            CreateMode.EPHEMERAL_SEQUENTIAL
        );
        
        // 从节点路径中提取workerId
        String nodeId = nodePath.substring(nodePath.lastIndexOf("-") + 1);
        return Long.parseLong(nodeId) % 1024;
    }
    
    private void startClockCheckThread() {
        Thread clockCheckThread = new Thread(() -> {
            while (true) {
                try {
                    // 定期检查时钟是否回拨
                    checkClock();
                    Thread.sleep(1000);
                } catch (Exception e) {
                    // 处理时钟回拨异常
                    handleClockBackwards();
                }
            }
        });
        clockCheckThread.setDaemon(true);
        clockCheckThread.start();
    }
    
    private void checkClock() throws Exception {
        Stat stat = zooKeeper.exists(zkPath + "/clock", false);
        if (stat != null) {
            byte[] data = zooKeeper.getData(zkPath + "/clock", false, stat);
            long zkTime = Long.parseLong(new String(data));
            long localTime = System.currentTimeMillis();
            
            if (localTime < zkTime) {
                throw new RuntimeException("Clock moved backwards");
            }
        }
        
        // 更新ZooKeeper时间
        zooKeeper.setData(zkPath + "/clock", String.valueOf(System.currentTimeMillis()).getBytes(), -1);
    }
    
    private void handleClockBackwards() {
        // 时钟回拨处理逻辑
        try {
            // 重新分配workerId
            long newWorkerId = allocateWorkerId();
            // 重新创建雪花算法生成器
            snowflakeGenerator = new SnowflakeIdGenerator(newWorkerId);
        } catch (Exception e) {
            throw new RuntimeException("Failed to handle clock backwards", e);
        }
    }
    
    public long generateId() {
        return snowflakeGenerator.generateId();
    }
}

优缺点分析

优点:

  • 双模式支持,灵活选择
  • 高可用性设计
  • 性能优异
  • 监控完善

缺点:

  • 实现复杂
  • 需要额外依赖(ZooKeeper)
  • 运维成本高

UidGenerator算法(百度)

原理

百度开源的分布式ID生成器,基于Snowflake算法改进,支持自定义workerId分配和RingBuffer缓存。

核心实现

@Component
public class UidGenerator {
    
    private final RingBuffer ringBuffer;
    private final WorkerIdAssigner workerIdAssigner;
    
    public UidGenerator(WorkerIdAssigner workerIdAssigner) {
        this.workerIdAssigner = workerIdAssigner;
        this.ringBuffer = new RingBuffer(1024);
        initialize();
    }
    
    private void initialize() {
        long workerId = workerIdAssigner.assignWorkerId();
        UidProvider uidProvider = new DefaultUidProvider(workerId);
        
        // 启动填充线程
        Thread fillThread = new Thread(() -> {
            while (true) {
                try {
                    ringBuffer.put(uidProvider.provide());
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });
        fillThread.setDaemon(true);
        fillThread.start();
    }
    
    public long generateId() throws InterruptedException {
        return ringBuffer.take();
    }
}

class DefaultUidProvider implements UidProvider {
    
    private static final long START_TIMESTAMP = 1262275200000L; // 2010-01-01
    
    private final long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    
    public DefaultUidProvider(long workerId) {
        this.workerId = workerId;
    }
    
    @Override
    public synchronized long provide() {
        long currentTimestamp = getCurrentTimestamp();
        
        if (currentTimestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards");
        }
        
        if (currentTimestamp == lastTimestamp) {
            sequence = (sequence + 1) & 4095; // 12位序列号
            if (sequence == 0) {
                currentTimestamp = waitNextMillis(currentTimestamp);
            }
        } else {
            sequence = 0L;
        }
        
        lastTimestamp = currentTimestamp;
        
        return ((currentTimestamp - START_TIMESTAMP) << 22)
                | (workerId << 13)
                | sequence;
    }
    
    private long waitNextMillis(long currentTimestamp) {
        while (currentTimestamp <= lastTimestamp) {
            currentTimestamp = getCurrentTimestamp();
        }
        return currentTimestamp;
    }
    
    private long getCurrentTimestamp() {
        return System.currentTimeMillis();
    }
}

class RingBuffer {
    private static final int BUFFER_SIZE = 1024;
    private final long[] buffer;
    private final AtomicLong putIndex = new AtomicLong(0);
    private final AtomicLong takeIndex = new AtomicLong(0);
    
    public RingBuffer(int size) {
        this.buffer = new long[size];
    }
    
    public void put(long value) throws InterruptedException {
        long currentPut = putIndex.get();
        long currentTake = takeIndex.get();
        
        // 检查缓冲区是否已满
        while (currentPut - currentTake >= BUFFER_SIZE) {
            Thread.sleep(1);
            currentTake = takeIndex.get();
        }
        
        buffer[(int)(currentPut % BUFFER_SIZE)] = value;
        putIndex.incrementAndGet();
    }
    
    public long take() throws InterruptedException {
        long currentTake = takeIndex.get();
        
        // 等待直到有数据可用
        while (putIndex.get() <= currentTake) {
            Thread.sleep(1);
        }
        
        long value = buffer[(int)(currentTake % BUFFER_SIZE)];
        takeIndex.incrementAndGet();
        return value;
    }
}

WorkerId分配策略

@Component
public class DisposableWorkerIdAssigner implements WorkerIdAssigner {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    public long assignWorkerId() {
        // 获取本机IP
        String ip = getLocalIp();
        
        // 尝试获取已有的workerId
        String selectSql = "SELECT worker_id FROM worker_node WHERE host = ? AND port = ?";
        List<Long> existingIds = jdbcTemplate.query(selectSql,
            new Object[]{ip, getProcessId()},
            (rs, rowNum) -> rs.getLong("worker_id"));
            
        if (!existingIds.isEmpty()) {
            return existingIds.get(0);
        }
        
        // 分配新的workerId
        String insertSql = "INSERT INTO worker_node (host, port, type, launch_date, modified) VALUES (?, ?, ?, NOW(), NOW())";
        KeyHolder keyHolder = new GeneratedKeyHolder();
        
        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS);
            ps.setString(1, ip);
            ps.setString(2, getProcessId());
            ps.setString(3, "ACTUAL");
            return ps;
        }, keyHolder);
        
        return keyHolder.getKey().longValue() % 1024;
    }
    
    private String getLocalIp() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            throw new RuntimeException("Failed to get local IP", e);
        }
    }
    
    private String getProcessId() {
        return ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
    }
}

优缺点分析

优点:

  • 性能极高,RingBuffer缓存
  • 支持自定义workerId分配
  • 可扩展性强
  • 监控完善

缺点:

  • 实现复杂
  • 需要数据库支持
  • 依赖较多

TinyID算法(滴滴)

原理

滴滴开源的分布式ID生成系统,基于号段模式,支持多db、高可用、动态扩容。

核心实现

@Service
public class TinyIdService {
    
    @Autowired
    private TinyIdGeneratorFactory factory;
    
    public Long generateId(String bizType) {
        return factory.getGenerator(bizType).nextId();
    }
    
    public List<Long> generateIds(String bizType, int batchSize) {
        return factory.getGenerator(bizType).nextId(batchSize);
    }
}

@Component
public class TinyIdGeneratorFactory {
    
    @Autowired
    private TinyIdGenerator[] generators;
    
    private final Map<String, TinyIdGenerator> generatorMap = new ConcurrentHashMap<>();
    
    @PostConstruct
    public void init() {
        for (TinyIdGenerator generator : generators) {
            generatorMap.put(generator.getBizType(), generator);
        }
    }
    
    public TinyIdGenerator getGenerator(String bizType) {
        TinyIdGenerator generator = generatorMap.get(bizType);
        if (generator == null) {
            throw new RuntimeException("Generator not found for bizType: " + bizType);
        }
        return generator;
    }
}

public class TinyIdGenerator {
    
    private final String bizType;
    private final SegmentIdService segmentIdService;
    private final SegmentBuffer segmentBuffer;
    
    public TinyIdGenerator(String bizType, SegmentIdService segmentIdService) {
        this.bizType = bizType;
        this.segmentIdService = segmentIdService;
        this.segmentBuffer = new SegmentBuffer();
    }
    
    public synchronized long nextId() {
        Segment current = segmentBuffer.getCurrent();
        
        if (current == null || current.isEmpty()) {
            loadCurrentSegment();
            current = segmentBuffer.getCurrent();
        }
        
        long id = current.nextId();
        
        // 当前号段用完,切换到下一个
        if (current.isEmpty()) {
            switchSegment();
        }
        
        // 异步加载下一个号段
        if (current.getIdle() < current.getTotal() * 0.2) {
            CompletableFuture.runAsync(this::loadNextSegment);
        }
        
        return id;
    }
    
    public List<Long> nextId(int batchSize) {
        List<Long> ids = new ArrayList<>(batchSize);
        for (int i = 0; i < batchSize; i++) {
            ids.add(nextId());
        }
        return ids;
    }
    
    private void loadCurrentSegment() {
        Segment segment = segmentIdService.getNextSegment(bizType);
        segmentBuffer.setCurrent(segment);
    }
    
    private void loadNextSegment() {
        Segment segment = segmentIdService.getNextSegment(bizType);
        segmentBuffer.setNext(segment);
    }
    
    private void switchSegment() {
        Segment next = segmentBuffer.getNext();
        if (next == null) {
            loadNextSegment();
            next = segmentBuffer.getNext();
        }
        segmentBuffer.setCurrent(next);
        segmentBuffer.setNext(null);
    }
    
    public String getBizType() {
        return bizType;
    }
}

@Service
public class SegmentIdService {
    
    @Autowired
    private DataSource dataSource;
    
    public Segment getNextSegment(String bizType) {
        return executeTransaction(connection -> {
            // 获取当前号段信息
            String selectSql = "SELECT max_id, step, delta, remainder FROM tiny_id_info WHERE biz_type = ? FOR UPDATE";
            PreparedStatement selectStmt = connection.prepareStatement(selectSql);
            selectStmt.setString(1, bizType);
            ResultSet rs = selectStmt.executeQuery();
            
            if (!rs.next()) {
                throw new RuntimeException("Business type not found: " + bizType);
            }
            
            long maxId = rs.getLong("max_id");
            int step = rs.getInt("step");
            int delta = rs.getInt("delta");
            int remainder = rs.getInt("remainder");
            
            // 计算新的号段
            long newMaxId = maxId + step;
            
            // 更新号段信息
            String updateSql = "UPDATE tiny_id_info SET max_id = ?, update_time = NOW() WHERE biz_type = ?";
            PreparedStatement updateStmt = connection.prepareStatement(updateSql);
            updateStmt.setLong(1, newMaxId);
            updateStmt.setString(2, bizType);
            updateStmt.executeUpdate();
            
            // 记录号段获取日志
            String insertLogSql = "INSERT INTO tiny_id_token (biz_type, token, remark, create_time) VALUES (?, ?, ?, NOW())";
            PreparedStatement insertLogStmt = connection.prepareStatement(insertLogSql);
            insertLogStmt.setString(1, bizType);
            insertLogStmt.setLong(2, newMaxId);
            insertLogStmt.setString(3, "Generated segment");
            insertLogStmt.executeUpdate();
            
            return new Segment(maxId + 1, newMaxId, delta, remainder);
        });
    }
    
    private <T> T executeTransaction(ConnectionCallback<T> callback) {
        return new JdbcTemplate(dataSource).execute(callback);
    }
}

class Segment {
    private final AtomicLong currentId;
    private final long maxId;
    private final int delta;
    private final int remainder;
    
    public Segment(long startId, long maxId, int delta, int remainder) {
        this.currentId = new AtomicLong(startId);
        this.maxId = maxId;
        this.delta = delta;
        this.remainder = remainder;
    }
    
    public long nextId() {
        long id = currentId.getAndAdd(delta);
        if (id > maxId) {
            throw new RuntimeException("Segment exhausted");
        }
        return id;
    }
    
    public boolean isEmpty() {
        return currentId.get() > maxId;
    }
    
    public long getIdle() {
        return maxId - currentId.get() + 1;
    }
    
    public long getTotal() {
        return maxId - (currentId.get() - delta) + 1;
    }
}

多数据库支持

@Configuration
public class TinyIdDataSourceConfig {
    
    @Bean
    public Map<String, DataSource> dataSourceMap() {
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        
        // 主库
        HikariConfig masterConfig = new HikariConfig();
        masterConfig.setJdbcUrl("jdbc:mysql://master:3306/tinyid");
        masterConfig.setUsername("root");
        masterConfig.setPassword("password");
        dataSourceMap.put("master", new HikariDataSource(masterConfig));
        
        // 从库1
        HikariConfig slave1Config = new HikariConfig();
        slave1Config.setJdbcUrl("jdbc:mysql://slave1:3306/tinyid");
        slave1Config.setUsername("root");
        slave1Config.setPassword("password");
        dataSourceMap.put("slave1", new HikariDataSource(slave1Config));
        
        // 从库2
        HikariConfig slave2Config = new HikariConfig();
        slave2Config.setJdbcUrl("jdbc:mysql://slave2:3306/tinyid");
        slave2Config.setUsername("root");
        slave2Config.setPassword("password");
        dataSourceMap.put("slave2", new HikariDataSource(slave2Config));
        
        return dataSourceMap;
    }
    
    @Bean
    public SegmentIdService segmentIdService() {
        return new MultiDataSourceSegmentIdService(dataSourceMap());
    }
}

public class MultiDataSourceSegmentIdService extends SegmentIdService {
    
    private final Map<String, DataSource> dataSourceMap;
    private final AtomicInteger counter = new AtomicInteger(0);
    
    public MultiDataSourceSegmentIdService(Map<String, DataSource> dataSourceMap) {
        this.dataSourceMap = dataSourceMap;
    }
    
    @Override
    public Segment getNextSegment(String bizType) {
        // 轮询选择数据源
        List<String> dataSourceNames = new ArrayList<>(dataSourceMap.keySet());
        String dataSourceName = dataSourceNames.get(counter.getAndIncrement() % dataSourceNames.size());
        
        DataSource dataSource = dataSourceMap.get(dataSourceName);
        return executeTransaction(dataSource, connection -> {
            // 使用选定的数据源获取号段
            return super.getNextSegment(bizType);
        });
    }
    
    private <T> T executeTransaction(DataSource dataSource, ConnectionCallback<T> callback) {
        return new JdbcTemplate(dataSource).execute(callback);
    }
}

优缺点分析

优点:

  • 支持多数据库,高可用
  • 性能优异,异步预加载
  • 支持动态扩容
  • 监控完善

缺点:

  • 实现复杂
  • 需要维护数据库集群
  • 运维成本高

各种实现方式对比

实现方式唯一性性能可用性趋势递增长度实现复杂度运维成本
UUID极高极高长(36位)简单
数据库自增ID简单
数据库号段中等
Redis中等
Snowflake极高中等
Leaf极高复杂
UidGenerator极高复杂
TinyID极高复杂

选择指南与最佳实践

选择原则

  1. 业务规模

    • 小规模业务:UUID、数据库自增ID
    • 中等规模:数据库号段、Redis
    • 大规模:Snowflake、Leaf、UidGenerator、TinyID
  2. 性能要求

    • 低并发:数据库自增ID、UUID
    • 中等并发:数据库号段、Redis
    • 高并发:Snowflake、Leaf、UidGenerator、TinyID
  3. 可用性要求

    • 一般可用性:数据库自增ID、UUID
    • 高可用性:Redis集群、Snowflake、Leaf、UidGenerator、TinyID
  4. 运维能力

    • 运维能力弱:UUID、Snowflake
    • 运维能力中等:数据库号段、Redis
    • 运维能力强:Leaf、UidGenerator、TinyID

最佳实践

  1. 小规模系统

    // 使用UUID,简单可靠
    public class SimpleIdGenerator {
        public static String generateId() {
            return UUID.randomUUID().toString().replace("-", "");
        }
    }
    
  2. 中等规模系统

    // 使用数据库号段模式
    @Service
    public class MediumScaleIdService {
        
        @Autowired
        private SegmentIdGenerator segmentGenerator;
        
        public long generateOrderId() {
            return segmentGenerator.generateId("order");
        }
        
        public long generateUserId() {
            return segmentGenerator.generateId("user");
        }
    }
    
  3. 大规模系统

    // 使用Snowflake算法
    @Service
    public class LargeScaleIdService {
        
        private final SnowflakeIdGenerator[] generators;
        
        public LargeScaleIdService(int nodeCount) {
            this.generators = new SnowflakeIdGenerator[nodeCount];
            for (int i = 0; i < nodeCount; i++) {
                generators[i] = new SnowflakeIdGenerator(i);
            }
        }
        
        public long generateId() {
            // 轮询选择生成器
            int index = (int)(Thread.currentThread().getId() % generators.length);
            return generators[index].generateId();
        }
    }
    

注意事项

  1. 时钟同步

    • 使用NTP服务同步系统时间
    • 监控时钟回拨情况
    • 实现时钟回拨处理机制
  2. WorkerId管理

    • 建立workerId分配和管理机制
    • 避免workerId冲突
    • 支持动态workerId分配
  3. 监控告警

    • 监控ID生成性能
    • 监控ID使用速率
    • 设置合理的告警阈值
  4. 容灾备份

    • 实现多机房部署
    • 建立降级机制
    • 定期备份ID生成状态

实际应用案例

案例1:电商订单系统

需求: 高并发、趋势递增、可反解
方案: Snowflake算法

@Service
public class OrderIdGenerator {
    
    private final SnowflakeIdGenerator generator;
    
    public OrderIdGenerator() {
        long workerId = getWorkerIdFromConfig();
        this.generator = new SnowflakeIdGenerator(workerId);
    }
    
    public long generateOrderId() {
        return generator.generateId();
    }
    
    public OrderInfo parseOrderId(long orderId) {
        SnowflakeIdGenerator.IdInfo idInfo = SnowflakeIdGenerator.parseId(orderId);
        return new OrderInfo(orderId, idInfo.getTimestamp(), idInfo.getWorkerId());
    }
}

案例2:用户系统

需求: 全球唯一、信息安全
方案: UUID + 数据库

@Service
public class UserIdGenerator {
    
    @Autowired
    private UserRepository userRepository;
    
    public String generateUserId() {
        String userId;
        do {
            userId = UUID.randomUUID().toString().replace("-", "");
        } while (userRepository.existsById(userId));
        
        return userId;
    }
}

案例3:分布式追踪系统

需求: 全局唯一、可反解、高性能
方案: 改进版Snowflake

public class TraceIdGenerator {
    
    private static final long START_TIME = 1609430400000L; // 2021-01-01
    private static final long WORKER_ID_BITS = 10L;
    private static final long SEQUENCE_BITS = 12L;
    
    private final long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    
    public String generateTraceId() {
        long id = generateId();
        return Long.toHexString(id);
    }
    
    private synchronized long generateId() {
        long currentTimestamp = System.currentTimeMillis() - START_TIME;
        
        if (currentTimestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards");
        }
        
        if (currentTimestamp == lastTimestamp) {
            sequence = (sequence + 1) & ((1 << SEQUENCE_BITS) - 1);
            if (sequence == 0) {
                currentTimestamp = waitNextMillis(currentTimestamp);
            }
        } else {
            sequence = 0L;
        }
        
        lastTimestamp = currentTimestamp;
        
        return (currentTimestamp << (WORKER_ID_BITS + SEQUENCE_BITS))
                | (workerId << SEQUENCE_BITS)
                | sequence;
    }
}

总结

分布式ID生成是分布式系统中的核心问题,需要根据具体的业务场景、性能要求、可用性需求和运维能力选择合适的实现方案。从小规模的UUID到大規模的专用分布式ID生成系统,每种方案都有其适用场景和优缺点。在实际应用中,还需要考虑时钟同步、workerId管理、监控告警、容灾备份等因素,确保ID生成系统的稳定可靠运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值