📖 数据密集型设计

分布式ID生成策略

深入探讨分布式ID生成算法、Snowflake算法原理及ID碰撞解决方案

一、分布式ID概述

分布式ID是分布式系统中唯一标识数据的关键,在数据密集型应用中,需要生成全局唯一、有序、高性能的ID。分布式ID生成策略直接影响系统的可用性和数据一致性。

二、ID生成策略对比

2.1 ID生成策略对比

策略 描述 优点 缺点 适用场景
UUID 通用唯一标识符 生成简单、无中心化 无序、存储量大 不需要有序的场景
Snowflake Twitter开源算法 有序、高性能 依赖时钟 高并发场景
数据库自增 数据库生成 简单、有序 性能瓶颈 低并发场景
Redis自增 Redis生成 高性能、有序 单点故障 中等并发场景
分段ID 批量获取段 高性能、可扩展 实现复杂 高并发场景

2.2 Snowflake算法结构

graph LR A[64位ID] --> B[符号位 1位] A --> C[时间戳 41位] A --> D[机器ID 10位] A --> E[序列号 12位] B --> B1[固定为0] C --> C1[约69年] C1 --> C2[从epoch开始] D --> D1[数据中心 5位] D1 --> D2[机器 5位] D2 --> D3[最多1024台] E --> E1[每毫秒4096个] E1 --> E2[同一机器同一毫秒] F[示例ID] --> F1[0] F1 --> F2[1288834974657] F2 --> F3[101] F3 --> F4[0] F2 --> F5[时间戳] F3 --> F6[机器ID] F4 --> F7[序列号]

三、Snowflake算法实现

3.1 Snowflake算法实现

public class SnowflakeIdGenerator
{
    private readonly long _epoch = 1609459200000L;
    private readonly long _machineId;
    private readonly long _dataCenterId;
    private long _sequence = 0L;
    private long _lastTimestamp = -1L;
    private readonly object _lock = new();
    
    private const long MachineIdBits = 5L;
    private const long DataCenterIdBits = 5L;
    private const long SequenceBits = 12L;
    
    private const long MaxMachineId = -1L ^ (-1L << (int)MachineIdBits);
    private const long MaxDataCenterId = -1L ^ (-1L << (int)DataCenterIdBits);
    
    private const long MachineIdShift = SequenceBits;
    private const long DataCenterIdShift = SequenceBits + MachineIdBits;
    private const long TimestampLeftShift = SequenceBits + MachineIdBits + DataCenterIdBits;
    private const long SequenceMask = -1L ^ (-1L << (int)SequenceBits);
    
    public SnowflakeIdGenerator(long machineId, long dataCenterId)
    {
        if (machineId > MaxMachineId || machineId < 0)
        {
            throw new ArgumentException($"MachineId must be between 0 and {MaxMachineId}");
        }
        
        if (dataCenterId > MaxDataCenterId || dataCenterId < 0)
        {
            throw new ArgumentException($"DataCenterId must be between 0 and {MaxDataCenterId}");
        }
        
        _machineId = machineId;
        _dataCenterId = dataCenterId;
    }
    
    public long GenerateId()
    {
        lock (_lock)
        {
            var timestamp = GetCurrentTimestamp();
            
            if (timestamp < _lastTimestamp)
            {
                throw new InvalidOperationException("时钟回退");
            }
            
            if (timestamp == _lastTimestamp)
            {
                _sequence = (_sequence + 1) & SequenceMask;
                
                if (_sequence == 0)
                {
                    timestamp = WaitNextMillis(_lastTimestamp);
                }
            }
            else
            {
                _sequence = 0L;
            }
            
            _lastTimestamp = timestamp;
            
            return ((timestamp - _epoch) << (int)TimestampLeftShift) |
                   (_dataCenterId << (int)DataCenterIdShift) |
                   (_machineId << (int)MachineIdShift) |
                   _sequence;
        }
    }
    
    private long GetCurrentTimestamp()
    {
        return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
    }
    
    private long WaitNextMillis(long lastTimestamp)
    {
        var timestamp = GetCurrentTimestamp();
        
        while (timestamp <= lastTimestamp)
        {
            timestamp = GetCurrentTimestamp();
        }
        
        return timestamp;
    }
    
    public (long Timestamp, long DataCenterId, long MachineId, long Sequence) ParseId(long id)
    {
        var timestamp = (id >> (int)TimestampLeftShift) + _epoch;
        var dataCenterId = (id >> (int)DataCenterIdShift) & MaxDataCenterId;
        var machineId = (id >> (int)MachineIdShift) & MaxMachineId;
        var sequence = id & SequenceMask;
        
        return (timestamp, dataCenterId, machineId, sequence);
    }
}

3.2 时钟回退处理

public class ClockSafeSnowflakeGenerator : SnowflakeIdGenerator
{
    private readonly IClockMonitor _clockMonitor;
    private readonly TimeSpan _maxClockDrift = TimeSpan.FromSeconds(5);
    
    public ClockSafeSnowflakeGenerator(long machineId, long dataCenterId, IClockMonitor clockMonitor)
        : base(machineId, dataCenterId)
    {
        _clockMonitor = clockMonitor;
    }
    
    public new long GenerateId()
    {
        var lastTimestamp = GetLastTimestamp();
        var currentTimestamp = GetCurrentTimestamp();
        
        if (currentTimestamp < lastTimestamp)
        {
            var drift = lastTimestamp - currentTimestamp;
            
            if (drift > _maxClockDrift.TotalMilliseconds)
            {
                _clockMonitor.ReportClockDrift(drift);
                
                throw new ClockDriftException($"时钟回退超过 {_maxClockDrift.TotalSeconds} 秒");
            }
            
            Thread.Sleep((int)(lastTimestamp - currentTimestamp + 1));
        }
        
        return base.GenerateId();
    }
}

public interface IClockMonitor
{
    void ReportClockDrift(long driftMilliseconds);
    bool IsClockStable();
}

四、UUID优化

4.1 COMB UUID

public class CombUuidGenerator
{
    public Guid Generate()
    {
        var guid = Guid.NewGuid();
        var timestamp = DateTime.Now.Ticks;
        
        var guidBytes = guid.ToByteArray();
        var timestampBytes = BitConverter.GetBytes(timestamp);
        
        Array.Copy(timestampBytes, 2, guidBytes, 6, 6);
        
        return new Guid(guidBytes);
    }
    
    public Guid GenerateWithDateTime(DateTime dateTime)
    {
        var guid = Guid.NewGuid();
        var timestamp = dateTime.Ticks;
        
        var guidBytes = guid.ToByteArray();
        var timestampBytes = BitConverter.GetBytes(timestamp);
        
        Array.Copy(timestampBytes, 2, guidBytes, 6, 6);
        
        return new Guid(guidBytes);
    }
    
    public DateTime ExtractDateTime(Guid combGuid)
    {
        var guidBytes = combGuid.ToByteArray();
        var timestampBytes = new byte[8];
        
        Array.Copy(guidBytes, 6, timestampBytes, 2, 6);
        
        var timestamp = BitConverter.ToInt64(timestampBytes, 0);
        
        return new DateTime(timestamp);
    }
}

4.2 顺序UUID

public class SequentialUuidGenerator
{
    private readonly object _lock = new();
    private long _lastTimestamp = 0;
    private long _sequence = 0;
    
    public Guid Generate()
    {
        lock (_lock)
        {
            var timestamp = DateTime.UtcNow.Ticks;
            
            if (timestamp == _lastTimestamp)
            {
                _sequence++;
            }
            else
            {
                _sequence = 0;
                _lastTimestamp = timestamp;
            }
            
            var bytes = new byte[16];
            
            var timestampBytes = BitConverter.GetBytes(timestamp);
            Array.Copy(timestampBytes, bytes, 8);
            
            var sequenceBytes = BitConverter.GetBytes(_sequence);
            Array.Copy(sequenceBytes, 0, bytes, 8, 4);
            
            var randomBytes = Guid.NewGuid().ToByteArray();
            Array.Copy(randomBytes, 12, bytes, 12, 4);
            
            return new Guid(bytes);
        }
    }
    
    public string GenerateString()
    {
        return Generate().ToString("N");
    }
}

五、数据库分段ID

5.1 分段ID生成器

public class SegmentIdGenerator
{
    private readonly IDbConnectionFactory _dbConnectionFactory;
    private readonly string _tableName = "id_generator";
    private readonly Dictionary _segments = new();
    private readonly object _lock = new();
    private readonly int _segmentSize = 1000;
    
    public async Task GenerateIdAsync(string businessType)
    {
        if (!_segments.TryGetValue(businessType, out var segment))
        {
            segment = await FetchSegmentAsync(businessType);
            _segments[businessType] = segment;
        }
        
        if (segment.IsExhausted)
        {
            segment = await FetchSegmentAsync(businessType);
            _segments[businessType] = segment;
        }
        
        return segment.GetNextId();
    }
    
    private async Task FetchSegmentAsync(string businessType)
    {
        using var connection = _dbConnectionFactory.CreateConnection();
        await connection.OpenAsync();
        
        using var transaction = await connection.BeginTransactionAsync();
        
        var sql = $@"
            UPDATE {_tableName} 
            SET max_id = max_id + @SegmentSize 
            WHERE business_type = @BusinessType;
            
            SELECT max_id - @SegmentSize + 1 as start_id, max_id as end_id 
            FROM {_tableName} 
            WHERE business_type = @BusinessType;
        ";
        
        var parameters = new { BusinessType = businessType, SegmentSize = _segmentSize };
        
        var result = await connection.QueryFirstAsync(sql, parameters, transaction);
        
        await transaction.CommitAsync();
        
        return new Segment(result.StartId, result.EndId);
    }
    
    public async Task InitializeBusinessTypeAsync(string businessType, long initialValue = 1)
    {
        using var connection = _dbConnectionFactory.CreateConnection();
        await connection.OpenAsync();
        
        var sql = $@"
            INSERT INTO {_tableName} (business_type, max_id) 
            VALUES (@BusinessType, @InitialValue)
            ON DUPLICATE KEY UPDATE max_id = @InitialValue;
        ";
        
        await connection.ExecuteAsync(sql, new { BusinessType = businessType, InitialValue = initialValue });
    }
}

public class Segment
{
    private readonly long _startId;
    private readonly long _endId;
    private long _currentId;
    
    public Segment(long startId, long endId)
    {
        _startId = startId;
        _endId = endId;
        _currentId = startId - 1;
    }
    
    public bool IsExhausted => _currentId >= _endId;
    
    public long GetNextId()
    {
        if (IsExhausted)
        {
            throw new InvalidOperationException("分段已用尽");
        }
        
        return Interlocked.Increment(ref _currentId);
    }
    
    public long Remaining => _endId - _currentId;
}

public class SegmentRange
{
    public long StartId { get; set; }
    public long EndId { get; set; }
}

六、Redis自增ID

6.1 Redis自增ID生成器

public class RedisIdGenerator
{
    private readonly IDistributedCache _cache;
    private readonly string _prefix = "id:";
    private readonly long _defaultStep = 1;
    
    public async Task GenerateIdAsync(string businessType)
    {
        var key = $"{_prefix}{businessType}";
        
        var value = await _cache.StringIncrementAsync(key, _defaultStep);
        
        return value;
    }
    
    public async Task GenerateIdAsync(string businessType, long step)
    {
        var key = $"{_prefix}{businessType}";
        
        var value = await _cache.StringIncrementAsync(key, step);
        
        return value;
    }
    
    public async Task GetCurrentValueAsync(string businessType)
    {
        var key = $"{_prefix}{businessType}";
        
        var value = await _cache.GetStringAsync(key);
        
        return string.IsNullOrEmpty(value) ? 0 : long.Parse(value);
    }
    
    public async Task InitializeAsync(string businessType, long initialValue)
    {
        var key = $"{_prefix}{businessType}";
        
        var exists = await _cache.StringSetAsync(key, initialValue.ToString(), 
            new DistributedCacheEntryOptions(), 
            new DistributedCacheEntryOptions());
        
        return initialValue;
    }
    
    public async Task> GenerateBatchAsync(string businessType, int count)
    {
        var ids = new List();
        
        for (int i = 0; i < count; i++)
        {
            ids.Add(await GenerateIdAsync(businessType));
        }
        
        return ids;
    }
}

七、ID碰撞解决方案

7.1 ID碰撞检测

public class IdCollisionDetector
{
    private readonly HashSet _generatedIds = new();
    private readonly object _lock = new();
    
    public bool IsCollision(long id)
    {
        lock (_lock)
        {
            if (_generatedIds.Contains(id))
            {
                return true;
            }
            
            _generatedIds.Add(id);
            
            if (_generatedIds.Count > 1000000)
            {
                _generatedIds.Clear();
            }
            
            return false;
        }
    }
    
    public async Task IsCollisionAsync(long id, string businessType)
    {
        var key = $"id:collision:{businessType}:{id}";
        
        var exists = await _cache.StringGetAsync(key);
        
        if (exists.HasValue)
        {
            return true;
        }
        
        await _cache.StringSetAsync(key, "1", TimeSpan.FromHours(24));
        
        return false;
    }
    
    public async Task RegisterIdAsync(long id, string businessType)
    {
        var key = $"id:collision:{businessType}:{id}";
        
        await _cache.StringSetAsync(key, "1", TimeSpan.FromHours(24));
    }
}

7.2 ID生成策略选择

场景 推荐策略 理由
高并发、需要有序 Snowflake 高性能、有序、分布式
需要跨数据库迁移 UUID/COMB UUID 无中心化、全局唯一
需要数据库主键 数据库自增/分段ID 简单、有序
中等并发、需要高性能 Redis自增 高性能、低延迟
需要批量生成 分段ID 批量获取、减少DB压力

7.3 ID生成最佳实践

  • 选择合适的ID生成策略
  • 考虑ID的有序性
  • 处理时钟回退问题
  • 避免ID碰撞
  • 监控ID生成性能

八、总结

分布式ID生成是分布式系统的基础问题。通过选择合适的ID生成策略(Snowflake、UUID、数据库自增、Redis自增、分段ID),能够满足不同场景的需求。Snowflake算法是高并发场景的首选,具有高性能和有序性的特点。UUID适合不需要有序性的场景。分段ID通过批量获取减少数据库压力。遵循ID生成最佳实践,能够构建稳定可靠的ID生成系统。