1.分布式ID的需求
在分布式系统中,需要全局唯一、趋势递增、高性能的ID(如订单号、消息ID)。数据库自增ID在分库分表后不再唯一;UUID无序且过长,影响索引性能。Twitter开源的雪花算法(Snowflake)产生64位长整型ID:1位符号位+41位时间戳(毫秒级,可用69年)+10位工作机器ID(最多1024节点)+12位序列号(每毫秒最多4096个)。Java是实现雪花算法的热门语言。
2.基本Java实现
核心是一个synchronized方法,记录上次生成时间戳和序列号。当时间戳变化时,序列号重置;当同一毫秒内,序列号自增并检查是否溢出(超过4095),若溢出则等待下一毫秒。
参考:https://xgmoi.cn/category/siji.html
3.时钟回拨问题与解决
雪花算法依赖系统时钟。如果时钟回拨(如NTP调整),可能会生成重复ID。解决方案:
拒绝策略:检测到时钟回拨超过阈值(如5秒),抛异常或阻塞等待时间追上。
使用Zookeeper记录上次时间:当回拨发生时,从ZK获取最后分配的时间戳,暂停直到超过它。
改用美团的Leaf方案:不依赖时钟,而是通过数据库号段或双buffer。
4.高性能优化
单机雪花算法每秒可生成数万ID,瓶颈在synchronized。改进:
预分配序列号:每个Worker预取一批序列号到本地,减少锁竞争。
使用LongAdder或AtomicLong,但需要保证唯一性,不建议。
多worker实例:如果一台机器需要极高QPS,可启动多个worker(不同机器ID),通过负载均衡分配请求。
参考:https://xgmoi.cn/category/xinli.html
5.案例:电商订单中心
某电商每天订单量约5000万,使用雪花算法生成订单ID,部署8个节点,每个节点配置机器ID(1-8)。Java实现:
时钟回拨处理:启动时从数据库读取上次最大时间戳,如果时钟回拨超过1秒,报警并自杀。
序列号位数调整为16位(每毫秒65536),时间戳位数相应减少(可满足50年)。
测试单机最高生成15万ID/秒,满足峰值需求。
6.其他分布式ID方案
数据库号段模式:批量从DB取ID段,缓存到内存。
Redis自增:INCR原子操作,但依赖Redis性能。
UUID:36字符,不适合作为数据库主键(碎片严重)。
7.总结
Java实现雪花算法简单可靠,已在无数分布式系统中验证。合理设计机器ID分配策略和时钟回拨处理,即可获得高性能、全局唯一的ID。对于大多数业务场景,雪花算法是分布式ID的首选。
参考:https://xgmoi.cn