分布式 ID 生成
大约 9 分钟约 2803 字
分布式 ID 生成
简介
分布式系统中,数据库自增 ID 在分库分表或多服务实例场景下会产生冲突。分布式 ID 生成方案保证全局唯一、趋势递增和高性能,是分布式架构的基础设施。
特点
雪花算法(Snowflake)
ID 结构
0 | 00000000 00000000 00000000 00000000 00000000 0 | 00000 00000 | 000000000000
符号 | 41位时间戳(毫秒级) | 数据中心 机器 | 12位序列号
1位 | 约69年 | 5位 5位 | 4096/毫秒.NET 实现
public class SnowflakeIdGenerator
{
private readonly long _machineId;
private readonly long _datacenterId;
private long _sequence = 0;
private long _lastTimestamp = -1;
// 起始时间戳(2024-01-01)
private const long Epoch = 1704067200000L;
// 各部分位数
private const int MachineIdBits = 5;
private const int DatacenterIdBits = 5;
private const int SequenceBits = 12;
// 最大值
private const long MaxMachineId = (1L << MachineIdBits) - 1; // 31
private const long MaxDatacenterId = (1L << DatacenterIdBits) - 1; // 31
private const long MaxSequence = (1L << SequenceBits) - 1; // 4095
private static readonly object _lock = new();
public SnowflakeIdGenerator(long machineId, long datacenterId)
{
if (machineId < 0 || machineId > MaxMachineId)
throw new ArgumentException($"机器ID范围: 0-{MaxMachineId}");
if (datacenterId < 0 || datacenterId > MaxDatacenterId)
throw new ArgumentException($"数据中心ID范围: 0-{MaxDatacenterId}");
_machineId = machineId;
_datacenterId = datacenterId;
}
public long NextId()
{
lock (_lock)
{
var timestamp = GetCurrentTimestamp();
if (timestamp < _lastTimestamp)
throw new InvalidOperationException("时钟回拨,拒绝生成ID");
if (timestamp == _lastTimestamp)
{
_sequence = (_sequence + 1) & MaxSequence;
if (_sequence == 0)
timestamp = WaitNextMillis(_lastTimestamp);
}
else
{
_sequence = 0;
}
_lastTimestamp = timestamp;
return ((timestamp - Epoch) << (MachineIdBits + DatacenterIdBits + SequenceBits))
| (_datacenterId << (MachineIdBits + SequenceBits))
| (_machineId << SequenceBits)
| _sequence;
}
}
private long GetCurrentTimestamp()
{
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
private long WaitNextMillis(long lastTimestamp)
{
var timestamp = GetCurrentTimestamp();
while (timestamp <= lastTimestamp)
{
Thread.Sleep(1);
timestamp = GetCurrentTimestamp();
}
return timestamp;
}
}
// 使用
var generator = new SnowflakeIdGenerator(machineId: 1, datacenterId: 1);
for (int i = 0; i < 10; i++)
{
Console.WriteLine(generator.NextId());
}
// 1868234567890123456
// 1868234567890123457注册为服务
// 注册单例
builder.Services.AddSingleton<SnowflakeIdGenerator>(sp =>
{
// 从配置读取机器ID
var machineId = builder.Configuration.GetValue<long>("Snowflake:MachineId", 1);
var datacenterId = builder.Configuration.GetValue<long>("Snowflake:DatacenterId", 1);
return new SnowflakeIdGenerator(machineId, datacenterId);
});
// 使用
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly SnowflakeIdGenerator _idGenerator;
public OrdersController(SnowflakeIdGenerator idGenerator)
{
_idGenerator = idGenerator;
}
[HttpPost]
public IActionResult Create([FromBody] CreateOrderRequest request)
{
var orderId = _idGenerator.NextId();
// ...
return Ok(new { OrderId = orderId.ToString() });
}
}UUID
GUID 方案
// 标准 GUID(无序)
var guid = Guid.NewGuid(); // 乱序,不适合数据库索引
// UUID v7(时间排序,.NET 9+)
// .NET 9 引入 Guid.CreateVersion7()
var orderedGuid = Guid.NewGuid(); // 需要使用支持 v7 的库
// 顺序 GUID(SQL Server NEWSEQUENTIALID 风格)
public static Guid NewSequentialGuid()
{
var timestamp = DateTime.UtcNow.Ticks / 10000L; // 毫秒
var guidBytes = Guid.NewGuid().ToByteArray();
// 将时间戳写入前8字节(确保递增)
var timestampBytes = BitConverter.GetBytes(timestamp);
if (BitConverter.IsLittleEndian) Array.Reverse(timestampBytes);
Buffer.BlockCopy(timestampBytes, 0, guidBytes, 0, 8);
return new Guid(guidBytes);
}
// 有序 GUID 插入数据库性能更好
for (int i = 0; i < 5; i++)
{
Console.WriteLine(NewSequentialGuid());
}数据库号段模式
号段预分配
// 号段表
public class IdSegment
{
public string BizTag { get; set; } = ""; // 业务标识
public long MaxId { get; set; } // 当前最大ID
public int Step { get; set; } // 步长(号段大小)
public int Version { { get; set; } // 乐观锁版本
}
public class SegmentIdGenerator
{
private readonly DbContext _context;
private long _currentId;
private long _maxId;
private readonly object _lock = new();
public SegmentIdGenerator(DbContext context)
{
_context = context;
}
public long NextId(string bizTag)
{
lock (_lock)
{
if (_currentId >= _maxId)
{
LoadNextSegment(bizTag);
}
return ++_currentId;
}
}
private void LoadNextSegment(string bizTag)
{
using var transaction = _context.Database.BeginTransaction();
try
{
var segment = _context.Set<IdSegment>()
.FromSqlRaw("SELECT * FROM id_segment WHERE biz_tag = {0} FOR UPDATE", bizTag)
.First();
_currentId = segment.MaxId;
_maxId = segment.MaxId + segment.Step;
segment.MaxId = _maxId;
_context.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}Redis 自增
原子递增
// Redis 自增生成有序 ID
public class RedisIdGenerator
{
private readonly IDatabase _db;
public RedisIdGenerator(IDatabase db)
{
_db = db;
}
// 简单自增
public async Task<long> NextIdAsync(string key)
{
return await _db.StringIncrementAsync($"id:{key}");
}
// 按天自增(每天重置)
public async Task<string> NextDailyIdAsync(string key)
{
var today = DateTime.UtcNow.ToString("yyyyMMdd");
var redisKey = $"id:{key}:{today}";
var seq = await _db.StringIncrementAsync(redisKey);
await _db.KeyExpireAsync(redisKey, TimeSpan.FromDays(2));
return $"{today}{seq:D6}"; // 20240115000001
}
// 带前缀的业务ID
public async Task<string> NextBizIdAsync(string prefix)
{
var today = DateTime.UtcNow.ToString("yyyyMMdd");
var key = $"id:{prefix}:{today}";
var seq = await _db.StringIncrementAsync(key);
await _db.KeyExpireAsync(key, TimeSpan.FromDays(7));
return $"{prefix}{today}{seq:D8}"; // ORD2024011500000001
}
}方案对比
| 方案 | 有序性 | 性能 | 依赖 | 适用场景 |
|---|---|---|---|---|
| 雪花算法 | 趋势递增 | 极高 | 无 | 通用推荐 |
| UUID v7 | 时间有序 | 极高 | 无 | 简单场景 |
| 顺序 GUID | 递增 | 极高 | 无 | SQL Server |
| 号段模式 | 严格递增 | 高 | 数据库 | 中等规模 |
| Redis 自增 | 严格递增 | 高 | Redis | 需要连续 ID |
雪花算法增强
时钟回拨处理
/// <summary>
/// 增强版雪花算法 — 时钟回拨容忍与机器 ID 自动分配
/// </summary>
public class EnhancedSnowflakeIdGenerator
{
private long _machineId;
private long _datacenterId;
private long _sequence = 0;
private long _lastTimestamp = -1;
private const long Epoch = 1704067200000L;
private const int MachineIdBits = 5;
private const int DatacenterIdBits = 5;
private const int SequenceBits = 12;
private const long MaxMachineId = (1L << MachineIdBits) - 1;
private const long MaxDatacenterId = (1L << DatacenterIdBits) - 1;
private const long MaxSequence = (1L << SequenceBits) - 1;
// 时钟回拨容忍阈值(毫秒)
private const long MaxClockDrift = 5000;
private static readonly object _lock = new();
/// <summary>
/// 自动分配机器 ID(基于 MAC 地址哈希)
/// </summary>
public static EnhancedSnowflakeIdGenerator Create()
{
var machineId = GetMachineIdFromMac();
var datacenterId = GetMachineIdFromIp();
return new EnhancedSnowflakeIdGenerator(machineId, datacenterId);
}
private static long GetMachineIdFromMac()
{
var nics = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();
var mac = nics.FirstOrDefault(n => n.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up)
?.GetPhysicalAddress().ToString() ?? "000000000000";
var hash = mac.GetHashCode();
return Math.Abs(hash) % (MaxMachineId + 1);
}
private static long GetMachineIdFromIp()
{
var ip = System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())
.FirstOrDefault(a => a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
return ip != null ? Math.Abs(ip.GetHashCode()) % (MaxDatacenterId + 1) : 0;
}
public EnhancedSnowflakeIdGenerator(long machineId, long datacenterId)
{
_machineId = machineId & MaxMachineId;
_datacenterId = datacenterId & MaxDatacenterId;
}
public long NextId()
{
lock (_lock)
{
var timestamp = GetCurrentTimestamp();
// 时钟回拨处理
if (timestamp < _lastTimestamp)
{
var drift = _lastTimestamp - timestamp;
if (drift <= MaxClockDrift)
{
// 短时间回拨:等待时钟追上
Thread.Sleep((int)drift);
timestamp = GetCurrentTimestamp();
}
else
{
// 长时间回拨:抛出异常
throw new InvalidOperationException(
$"时钟回拨过大: {drift}ms,拒绝生成 ID");
}
}
if (timestamp == _lastTimestamp)
{
_sequence = (_sequence + 1) & MaxSequence;
if (_sequence == 0)
{
// 毫秒内序列号耗尽,等待下一毫秒
timestamp = WaitNextMillis(_lastTimestamp);
}
}
else
{
_sequence = 0;
}
_lastTimestamp = timestamp;
return ((timestamp - Epoch) << (MachineIdBits + DatacenterIdBits + SequenceBits))
| (_datacenterId << (MachineIdBits + SequenceBits))
| (_machineId << SequenceBits)
| _sequence;
}
}
/// <summary>
/// 从雪花 ID 中提取信息
/// </summary>
public static (DateTime Timestamp, long MachineId, long DatacenterId, long Sequence) ParseId(long id)
{
var sequence = id & MaxSequence;
var machineId = (id >> SequenceBits) & MaxMachineId;
var datacenterId = (id >> (SequenceBits + MachineIdBits)) & MaxDatacenterId;
var timestamp = (id >> (SequenceBits + MachineIdBits + DatacenterIdBits)) + Epoch;
return (DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime,
machineId, datacenterId, sequence);
}
}多数据中心 ID 分配
/// <summary>
/// 基于 ZooKeeper/Consul 的机器 ID 分配
/// </summary>
public class DistributedIdAllocator
{
private readonly IDistributedLock _lock;
private readonly IDistributedCache _cache;
public DistributedIdAllocator(IDistributedLock distributedLock, IDistributedCache cache)
{
_lock = distributedLock;
_cache = cache;
}
/// <summary>
/// 分配全局唯一的机器 ID(0-31)
/// </summary>
public async Task<int> AllocateMachineIdAsync(string serviceGroup)
{
var lockKey = $"id_alloc:lock:{serviceGroup}";
await using (await _lock.AcquireAsync(lockKey))
{
// 读取已分配的机器 ID 集合
var usedIdsKey = $"id_alloc:used:{serviceGroup}";
var usedIdsJson = await _cache.GetStringAsync(usedIdsKey);
var usedIds = usedIdsJson != null
? JsonSerializer.Deserialize<HashSet<int>>(usedIdsJson)!
: new HashSet<int>();
// 找到最小可用 ID
int machineId = 0;
for (; machineId <= 31; machineId++)
{
if (!usedIds.Contains(machineId))
break;
}
if (machineId > 31)
throw new InvalidOperationException("机器 ID 已耗尽(最大 32 个实例)");
usedIds.Add(machineId);
await _cache.SetStringAsync(usedIdsKey, JsonSerializer.Serialize(usedIds),
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromHours(1) });
return machineId;
}
}
}ID 在 EF Core 中的集成
自定义 ID 生成器
/// <summary>
/// EF Core 中使用雪花 ID
/// </summary
public class SnowflakeIdValueGenerator : ValueGenerator<long>
{
private readonly SnowflakeIdGenerator _generator;
public SnowflakeIdValueGenerator(SnowflakeIdGenerator generator)
{
_generator = generator;
}
public override long Next(EntityEntry entry)
{
return _generator.NextId();
}
public override ValueTask<long> NextAsync(
EntityEntry entry, CancellationToken ct = default)
{
return ValueTask.FromResult(_generator.NextId());
}
public override bool GeneratesTemporaryValues => false;
}
// 在 DbContext 中配置
public class AppDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id)
.ValueGeneratedOnAdd()
.HasValueGenerator<SnowflakeIdValueGenerator>();
});
}
}ULID 方案
排序安全的 UUID 替代
/// <summary>
/// ULID(Universally Unique Lexicographically Sortable Identifier)
/// 26 字符,时间有序,URL 安全
/// 格式: 01ARZ3NDEKTSV4RRFFQ69G5FAV
/// 前 10 位: 时间戳(毫秒)
/// 后 16 位: 随机数
/// </summary>
public static class UlidGenerator
{
private static readonly char[] Encoding = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".ToCharArray();
public static string Generate()
{
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var bytes = new byte[16];
Span<byte> timeBytes = stackalloc byte[6];
// 写入时间戳(前 6 字节,大端序)
timeBytes[0] = (byte)(timestamp >> 40);
timeBytes[1] = (byte)(timestamp >> 32);
timeBytes[2] = (byte)(timestamp >> 24);
timeBytes[3] = (byte)(timestamp >> 16);
timeBytes[4] = (byte)(timestamp >> 8);
timeBytes[5] = (byte)(timestamp);
timeBytes.CopyTo(bytes);
// 后 10 字节为随机数
RandomNumberGenerator.Fill(bytes.AsSpan(6));
// 编码为 Base32
var chars = new char[26];
for (int i = 0; i < 26; i++)
{
var byteIndex = i * 8 / 5;
var bitOffset = i * 8 % 5;
chars[i] = Encoding[(bytes[byteIndex] >> (3 - bitOffset)) & 0x1F];
}
return new string(chars);
}
// 从 ULID 中提取时间戳
public static DateTimeOffset ExtractTimestamp(string ulid)
{
long timestamp = 0;
for (int i = 0; i < 10; i++)
{
var value = Array.IndexOf(Encoding, ulid[i]);
timestamp = (timestamp << 5) | value;
}
return DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
}
}
// ULID 优势:
// 比 UUID v4 短(26 vs 36 字符)
// 天然时间有序
// URL 安全(不含 - 和特殊字符)
// 大小写不敏感优点
缺点
总结
分布式 ID 推荐方案:雪花算法(Snowflake),本地生成无网络依赖,每毫秒可生成 4096 个 ID。UUID v7 适合简单场景。数据库号段模式适合需要严格递增的场景。Redis 自增适合需要连续业务 ID 的场景。雪花算法注意时钟回拨问题和机器 ID 分配策略。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
适用场景
- 当你准备把《分布式 ID 生成》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《分布式 ID 生成》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《分布式 ID 生成》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《分布式 ID 生成》最大的收益和代价分别是什么?
