ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

分布式ID生成方案全解析:从数据库自增到雪花算法

分布式ID生成方案全解析:从数据库自增到雪花算法 1. 分布式ID的典型业务场景与核心挑战在分布式系统中生成全局唯一ID这件事听起来简单但实际暗藏玄机。我经历过一个电商项目初期直接用数据库自增ID结果分库分表后出现大量ID冲突促销活动时订单系统直接瘫痪。这才意识到分布式ID生成是分布式架构的基础设施级问题。典型需要分布式ID的场景包括电商系统的订单号、支付流水号社交网络的内容ID、评论ID物联网设备的唯一标识符金融交易的流水号日志系统的traceID这些场景对ID生成有四个核心要求全局唯一性这是最基本要求任何两个ID不能相同有序性ID最好能按时间递增这对数据库索引友好高可用性ID生成服务必须99.99%可用高性能单机每秒至少能生成10万 ID注意很多初级开发者会忽略有序性要求导致数据库索引频繁分裂写入性能急剧下降。我曾见过一个系统因为使用完全随机的UUID作为主键TPS从3000暴跌到200。2. 数据库自增ID方案及其优化2.1 基础版单数据库自增ID这是最简单的实现方式CREATE TABLE ids ( id bigint(20) NOT NULL AUTO_INCREMENT, stub char(1) NOT NULL DEFAULT , PRIMARY KEY (id), UNIQUE KEY stub (stub) ) ENGINEInnoDB;每次获取ID时执行REPLACE INTO ids (stub) VALUES (a); SELECT LAST_INSERT_ID();优点实现简单ID严格递增缺点单点故障性能瓶颈每秒最多几千分库分表时需要额外处理2.2 改进版数据库集群步长设置通过设置不同步长实现多数据库同时发号-- 数据库1 SET auto_increment_offset 1; SET auto_increment_increment 2; -- 数据库2 SET auto_increment_offset 2; SET auto_increment_increment 2;优化技巧建议使用3台数据库做冗余定期检查步长设置是否被意外修改监控自增ID使用进度提前扩容我在金融项目中使用这种方案时遇到过步长被MySQL配置覆盖的问题后来通过增加ZooKeeper监听配置变更解决了。3. UUID方案深度解析3.1 标准UUID的四种版本// Java生成UUID示例 UUID uuid UUID.randomUUID(); // 版本4版本生成方式特点v1时间戳MAC地址可预测有隐私风险v2DCE安全UUID很少使用v3MD5哈希基于命名空间v4随机数最常用v5SHA-1哈希类似v3但更安全3.2 UUID方案的优缺点优点本地生成无网络开销全球唯一性有保障致命缺点128位太长存储占用大完全无序导致数据库写入性能差无业务含义实战建议如果必须用UUID至少应该去掉横杠32字符变16字节或者考虑用ULID替代。4. Redis生成方案与原子性保障4.1 基础INCR命令INCR global:id4.2 集群模式下的Lua脚本local current redis.call(incr,KEYS[1]) if current % 100 0 then redis.call(expire,KEYS[1], 3600) end return current性能数据单Redis节点约8万QPSRedis集群约20万QPS踩坑记录一定要设置过期时间防止key无限增长集群模式下注意key必须哈希到同一slot网络分区时可能产生重复ID5. 雪花算法(Snowflake)实现细节5.1 标准Snowflake结构0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 0000000000001位符号位 41位时间戳 5位数据中心ID 5位机器ID 12位序列号5.2 Go语言实现示例type Snowflake struct { epoch int64 machineID int64 datacenterID int64 sequence int64 lastStamp int64 lock sync.Mutex } func (s *Snowflake) NextID() int64 { s.lock.Lock() defer s.lock.Unlock() currStamp : time.Now().UnixNano()/1e6 - s.epoch if currStamp s.lastStamp { panic(Clock moved backwards!) } if currStamp s.lastStamp { s.sequence (s.sequence 1) sequenceMask if s.sequence 0 { for currStamp s.lastStamp { currStamp time.Now().UnixNano()/1e6 - s.epoch } } } else { s.sequence 0 } s.lastStamp currStamp return (currStamp)timestampShift | (s.datacenterID datacenterShift) | (s.machineID machineShift) | s.sequence }关键参数调优时间戳位数影响可用年限41位≈69年序列号位数影响单机QPS12位4096/ms机器ID分配建议用ZooKeeper动态分配6. 美团Leaf方案解析6.1 双Buffer优化Leaf的核心创新是提前加载号段到内存双BufferBuffer A [1-1000] Buffer B [1001-2000]当Buffer A用完时异步加载Buffer C实现无缝切换。6.2 异常处理机制DB宕机使用本地缓存继续服务号段用完提前10%触发加载时钟回拨记录最后时间戳拒绝服务直到追上性能对比方案QPS平均延迟原生Snowflake12万0.8msLeaf-Segment15万1.2msLeaf-Snowflake18万0.5ms7. 百度UidGenerator实现7.1 Cached模式工作原理Worker节点 - 预分配ID段 - 本地RingBuffer - 消费者7.2 关键配置参数# 时间位 timeBits28 # 机器位 workerBits22 # 序列号位 seqBits13 # 时间基准 epochStr2023-01-01优势支持自定义各段位数内置秒级监控提供Spring Boot Starter8. 滴滴TinyID方案特点8.1 客户端SDK设计// 初始化 TinyIdClient.init(http://tinyid-server/, 300); // 获取ID Long id TinyId.nextId(order);8.2 性能优化手段本地缓存1000个ID异步批量获取多server负载均衡压测数据单机QPS25万平均延迟0.3ms99线1.2ms9. 其他创新方案对比9.1 MongoDB ObjectID5f9d7a3e6b8e4c001e3d7b1a4字节时间戳3字节机器标识2字节进程ID3字节计数器9.2 时钟序列方案def generate_id(): now time.time_ns() with atomic_counter.get_lock(): if atomic_counter.last_time now: atomic_counter.seq 1 else: atomic_counter.seq 0 atomic_counter.last_time now return (now 16) | atomic_counter.seq9.3 业务混合ID例如电商订单号20230815123456001ABCD前14位年月日时分秒中间3位序列号后4位业务编码10. 选型决策树与实战建议10.1 方案选择决策树是否需要有序性 ├─ 是 → QPS要求 │ ├─ 1万 → 数据库自增 │ ├─ 1-10万 → Redis/Leaf-Segment │ └─ 10万 → Snowflake/Leaf-Snowflake └─ 否 → UUIDv4/ULID10.2 各方案适用场景方案适用场景不适用场景数据库自增中小型系统高并发分库分表UUID临时标识数据库主键Redis已有Redis基础设施强一致性要求Snowflake大规模分布式系统时钟敏感环境Leaf互联网公司资源有限的小公司10.3 我的踩坑经验时钟回拨问题曾经因为NTP同步导致Snowflake生成重复ID后来增加了ZooKeeper时钟监控机器ID分配在Kubernetes环境中改用Pod IP后几位作为机器ID突发流量Leaf方案需要合理设置步长我们最终设置为常规时段的3倍峰值监控指标必须监控ID生成速率、剩余比例、时钟偏移等关键指标
返回列表