什么症状吃肝胃气痛片| 阴囊潮湿是什么原因| 红牛加什么提高性功能| 脸上长藓用什么药| 神经梅毒有什么症状| 鸭肉不能和什么一起吃| 梦见恐龙是什么预兆| 什么是零重力座椅| 梦到生儿子有什么预兆| 犬子是什么意思| 为什么要做包皮手术| 长寿面什么时候吃| 庸人自扰是什么意思| 处女座男生喜欢什么样的女生| 苏州立夏吃什么| 指甲凹凸不平是什么原因| 11.23是什么星座| 肩膀骨头响是什么原因| 什么是干燥综合症| 野鸡吃什么食物| 十二月二号是什么星座| 宫腔镜手术是什么原因才要做| 月经病是什么意思啊| 第一次查怀孕挂什么科| 四百分能上什么大学| 未时是什么时候| 一国两制是什么时候提出的| 智商130算什么水平| 王久是什么字| 孙红雷的老婆叫什么名字| 哎一古是什么意思| 有鸟飞进屋是什么预兆| 急性胃肠炎用什么抗生素| 活动性胃炎是什么意思| 医院特需号是什么意思| 2006属狗的五行缺什么| 盆腔炎吃什么消炎药效果好| 不来月经吃什么药催经| 桃子可以做什么美食| 子宫息肉有什么症状| 晔字为什么不能取名| 大姨妈期间可以吃什么水果| 摩羯座是什么星象| 肺结核钙化是什么意思| 把头是什么意思| 胃反酸吃点什么能缓解| 挛是什么意思| 血稠吃什么食物好得快| 通草和什么炖最催奶了| 三十六计第一计是什么计| 结婚32年是什么婚| 富字五行属什么| 口干舌燥口苦吃什么药| 喝竹叶水有什么好处| 腿水肿是什么原因引起的| s是什么牌子| 丹毒用什么药膏| 氮泵是什么| ca153是什么检查项目| 三千烦恼丝什么意思| doki是什么意思| 芒果什么人不能吃| 酒精肝吃什么药| 所向披靡是什么意思| 刚出生的小猫吃什么| 什么是毛囊炎及症状图片| 什么是年金| 头臂长是什么意思| 宝宝大便发白是什么原因| 股骨头坏死是什么原因引起的| 春宵一刻值千金是什么意思| 凤梨和菠萝的区别是什么| 鱼白是什么东西| 逆袭什么意思| 什么的大叫| 经常出汗是什么原因| 全组副鼻窦炎什么意思| 梦见陌生人死了是什么意思| 芒硝是什么东西| 胸闷喘不上气什么原因| 喇蛄和小龙虾什么区别| 什么医院才是正规医院| 脑供血不足是什么症状| 蛇字五行属什么| 激素是什么| 罗姓男孩取什么名字好| 举什么什么举| 肠癌有什么症状| 哈喇味是什么味道| 眼睛干涩用什么药| 什么相处| 鹅口疮是什么原因引起的| 塌腰是什么意思| 晚上看到黄鼠狼什么预兆| 比干是什么神| 荧惑守心是什么意思| 迟缓是什么意思| 九朵玫瑰花代表什么意思| 黄体破裂吃什么药| 抗美援朝是什么时候| 香菜炒什么好吃| 晚上12点是什么时辰| 肺火吃什么中成药| 钠低是什么原因造成的| 舟可是什么字| 空调有异味是什么原因| 嗓子嘶哑吃什么药| 7月8号是什么星座| 什么是盐| 什么是淡盐水| 凌霄什么意思| 红黑相间的蛇是什么蛇| 什么地溜达| 什么是意淫| 胆管结石用什么药能把它除掉| 一产二产三产分别包括什么| 606是什么意思| 4月18日什么星座| 夏天吃什么菜最好| 下午六点多是什么时辰| tj什么意思| 白事的礼金叫什么| 营养科都检查什么项目| 减肥吃什么水果| 人活一辈子到底为了什么| 妙手回春是什么意思| 手脚浮肿是什么原因引起的| 散粉是干什么用的| 什么牌子的笔记本电脑好| 脑梗死吃什么药| 胆汁反流性胃炎吃什么药| 什么颜色加什么颜色等于绿色| 肠手术后吃什么恢复快| 地西泮是什么药| 恩施玉露属于什么茶| 才下眉头却上心头是什么意思| 阿胶补血口服液适合什么人喝| 儿童肚子疼挂什么科| 苹果五行属什么| hpv病毒是什么原因引起的| 葡萄籽有什么功效| 什么的城楼| 白带发黄是什么妇科病| 谷草谷丙比值偏高代表什么| 罗马布是什么面料| 吃什么能解决便秘| 冰心的原名是什么| 卵巢保养最好的方法是什么| 为什么一站起来就头晕眼前发黑| 腋下检查挂什么科| 皮质醇低是什么原因| 胃窦炎是什么原因引起的| 什么是a货翡翠| 防蓝光眼镜有什么好处| 什么人容易得阿尔兹海默症| 耳朵后面痒是什么原因| 男性经常手淫有什么危害| 行善积德是什么生肖| 传教士是什么姿势| 什么是sp| 号外是什么意思| 血压高有什么好办法| 痛经吃什么水果能缓解疼痛| 考c1驾照需要什么条件| 心肌梗塞是什么症状| 馐什么意思| 福禄寿什么意思| 多吃蔬菜对身体有什么好处| 众星捧月是什么意思| 水杯用什么材质的好| 烦恼的意思是什么| 鼻塞是什么原因| 香港特首是什么级别| 肌肉痛吃什么药| peace是什么牌子| 幽门螺杆菌阳性是什么意思| 宝宝头爱出汗是什么原因| 震颤是什么病| 扁桃和芒果有什么区别| meme什么意思| 咀嚼食用是什么意思| 圆形脸适合什么样的发型| 天伦之乐是什么意思| 什么情况下打破伤风针| 人设崩塌是什么意思| 弓形虫是什么| 专著是什么| 开宠物医院需要什么条件| 一个月不来月经是什么原因| 医院脱毛挂什么科| 缄默症是什么病| 瑶五行属性是什么| 玫瑰糠疹吃什么药| rip什么意思| 98什么意思| 阴囊积液是什么原因引起的| 胸口闷疼是什么原因| 先天性心脏病是什么原因造成的| 先明后不争什么意思| 桂鱼是什么鱼| 骨质疏松吃什么药| 家里有蜈蚣是什么原因| ky是什么| 三七花泡水喝有什么功效和作用| 经常喝蜂蜜水有什么好处和坏处| 蚊子不咬什么血型的人| 肾有问题有什么症状| 试管婴儿是什么意思| 左侧卵巢囊性结构什么意思| 老油条什么意思| ca125高是什么原因| 黑洞是什么东西| 梦见钱包丢了是什么意思| 均可是什么意思| 食用棕榈油是什么油| 金箔金命是什么意思| 什么的小鸡| 缺铁性贫血吃什么食物好| 得偿所愿什么意思| 男人精液少是什么原因| 经期洗头有什么危害| domestic是什么意思| poems是什么意思| 嘴唇暗紫色是什么原因| 甲硝唑的副作用是什么| 镜里观花是什么生肖| 胸闷气短挂什么科室| 逐是什么意思| 清宫手术后需要注意什么| 魔芋是什么做的| 大肠杆菌属于什么菌| 肝火旺吃什么调理| 尿酸高中医叫什么病| 减肥期间晚上可以吃什么| 医院什么时候上班| 74岁属什么| 太瘦的人吃什么能长胖| 肠镜检查前需要做什么准备工作| 大便出血吃什么药好得快| 小便无力吃什么药| 油烟机没有吸力是什么原因| 元首是什么意思| 梦见呕吐是什么意思| 不疼不痒的红疹是什么| 心脏病人吃什么水果好| 令人发指是什么意思| 扁桃体发炎什么症状| 什么食用油最好最健康| 金是什么结构的字| 什么是结核病| 人工流产和无痛人流有什么区别| 学业是什么意思| 什么时候吃榴莲最好| 县长是什么级别| 缺陷是什么意思| 托马斯是什么意思| 刮宫后能吃什么水果| 盐水洗脸有什么好处| 惊厥是什么原因引起的| 定海神针是什么意思| 湿疹是什么皮肤病| 什么叫滑精| 晗是什么意思| tgi是什么意思| 百度

重庆市市政管理委员会关于公开征求规范性文件清...

百度 65岁的侯二河说着一口武安话,可官兵们却听得津津有味。

大家好,我是IT孟德,You can call me Aman(阿瞒,阿弥陀佛的ē,Not阿门的ā),一个喜欢所有对象(热爱技术)的男人。我正在创作架构专栏,秉承ITer开源精神分享给志同道合(爱江山爱技术更爱美人)的朋友。专栏更新不求速度但求质量(曹大诗人传世作品必属精品,请脑补一下《短歌行》:对酒当歌,红颜几何?譬如媳妇,吾不嫌多...青青罗裙,一见动心,但为佳人,挂念至今...),通过朴实无华、通俗易懂的图文将十六载开发和架构实战经验娓娓道来,让读者茅塞顿开、相见恨晚...如有吹牛,不吝赐教。关注wx公众号:IT孟德,一起修炼吧!?

专栏文章推荐:

软件界牛马:Nginx核心原理与性能优化全攻略

网关有什么用?如何进行网关选型?

?架构实战:2万字真实案例全方位解析高并发系统性能优化之道

?架构师指南:像呵护感情一样构建无懈可击的系统稳定性防线

系统性能评估:如何定义并发数、响应时间和吞吐量

实战:混合云架构Nginx+Lua实现流量跨机房分发

架构图的魅力:如何用UML提升架构设计的质量

为什么要做架构设计?架构设计包含哪些内容?

架构师的职责是什么?程序员如何转型为架构师?

什么是系统架构?架构如何演进?

Mysql数据库连接池druid、hikaricp优化实战

1、什么是分布式ID?


????????分布式ID是指在分布式系统或集群环境中,用于全局唯一地标识一个对象(如用户、订单、设备等)的标识符。其主要特征如下:

  • 全局唯一性: 在整个系统(跨节点、跨服务、跨时间)中,ID必须唯一,避免冲突。这是数据正确性和系统一致性的基础,绝对不能出现重复。

  • 可扩展性: 支持水平扩展,即使新增节点,也能保证ID生成的唯一性和效率。

  • 高性能: ID的生成速度快,低延迟,不影响系统整体性能。

  • 有序性(可选):某些场景要求ID趋势递增(不一定连续),有利于数据库索引优化或按时间排序。

  • 安全性(可选): ID本身避免包含业务敏感信息或可能导致敏感信息泄露的可预测模式,如用户ID连续可能导致数据泄露风险。

  • 去中心化(可选):生成算法减少依赖外部服务(如数据库或协调服务),提升可用性。

  • 空间占用合理(可选): ID避免过长(如使用128位UUID虽然唯一但空间占用大,可能对存储和索引效率产生影响),应尽可能短小精悍。

2、为什么要用分布式ID?


????????在单体应用或单数据库业务系统中,通常使用数据库的自增字段(如MySQL的AUTO_INCREMENT或PostgreSQL的SERIAL)就能很好地满足唯一ID的需求。但分布式系统中数据被拆到多个节点/数据库,依赖数据库自增存在很多局限性:

  • ID冲突:分布式系统中,数据通常会分散存储(比如分库分表:订单业务拆分为32个库、每个库32张表),或由多个服务独立生成数据(比如微服务架构中多个订单服务同时创建订单),如果用传统数据库自增ID,每个数据库节点都有自己的自增序列,生成的 ID只在本地唯一,合并后全局范围很可能会出现大量重复;不同服务生成的ID也可能重复。而分布式ID的核心作用就是在整个分布式系统中,为任意数据生成全局唯一的标识,避免重复。

  • 性能瓶颈: 分布式系统通常有高并发场景(如秒杀、大促)。如果依赖单一的主数据库或某个中心化服务来生成所有ID(使用其自增功能或序列),这个中心节点就会面临巨大的并发压力,极易成为系统的性能瓶颈,且一旦这个中心节点宕机,整个系统的所有业务都受影响。分布式D方案(如雪花算法、Redis 自增、分布式发号器等)会通过 “无锁设计”“集群部署” 等方式保证高性能和高可用。

  • 安全性低:传统自增ID有一个隐患,容易泄露业务数据量。如看到订单ID=100000,能猜到平台总共才100000个订单,暴露业务规模;攻击者可能通过自增ID遍历数据(比如按 ID=1,2,3... 批量爬取用户信息)。而分布式 ID 可以通过 “无规则结构”(如混合时间戳、机器标识、随机数)避免这个问题。

3、分布式ID方案有哪些?


3.1、UUID/GUID

原理:GUID(Globally Unique Identifier) 和 UUID(Universally Unique Identifier)在技术上是完全相同的概念,都是指基于时间戳、机器信息、随机数等生成一个128位(16字节)的全局唯一标识符。其标准格式为:xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx(M为版本号,表示算法、N为变体标识,表示编码),常见版本如下:

版本

算法

示例

v1

时间戳 + MAC地址

550e8400-e29b-11d4-a716-446655440000

v4

随机数

550e8400-e29b-41d4-a716-446655440000

v3/v5

命名空间 + 名称 + 哈希(MD5/SHA1)

550e8400-e29b-31d4-a716-446655440000

v7

时间戳+随机数

017f5e9a-3b8e-7c30-b73c-9d46450a28d3

优点:

  • 去中心化: 本地生成,无任何网络开销和外部依赖,高可用。

  • 简单易用:大多数编程语言和数据库(如PostgreSQL、MySQL)内置 UUID 支持。

  • 性能好:本地CPU计算,生成速度非常快。

  • 隐私保护:V4版本完全随机,不暴露时间或设备信息。

缺点:

  • 存储开销大:128位16字节),比Int或BigInt类型自增ID(4~8字节)占用更多空间,存储和传输成本高(如数据库主键索引占用空间大)。

  • 无序性:ID无递增趋势(尤其是随机数实现的UUID v4),无法作为数据库主键(B+树索引插入无序会导致大量页分裂,性能下降)。

  • 可读性差:16进制字符串对人类不友好。

  • 版本差异:不同版本的UUID适用场景差异大,完全随机的v4版本在极端高并发下存在极小重复概率。

适用场景:适合对ID有序性、长度无要求,且无需依赖外部服务的场景(如临时凭证、一次性令牌、文件命名),不适合需要数据库索引优化或对ID可读性有要求的场景(如订单ID)。

3.2、数据库自增ID

基础方案(单库自增):直接使用数据库的AUTO_INCREMENT(MySQL)或SERIAL(PostgreSQL)生成自增ID,通过单库单表保证唯一性。

改进方案(分库分表场景):若分库分表(多库多表),直接用单库自增会因多库重复导致ID冲突,需通设置自增步长+初始偏移量改进:

  • 为每个分库/分表分配独立的“自增区间”:例如分2个库,库1自增ID从1开始、步长为2(生成1,3,5...),库2从2开始、步长为2(生成2,4,6...)。

  • 按分表编号设置偏移:分表1偏移0,分表2偏移1000万,避免ID重叠。

号段方案:仍然利用数据库的自增字段能力,但不是每次取一个ID,而是批量获取一个号段(ID区间),本地缓存使用,用完后再从数据库申请新号段;美团Leaf Segment模式采用双Buffer(双号段)预加载下一个号段,减少分配延迟和请求阻塞。

优点:

  • 实现简单:依赖数据库原生功能,无需额外开发。

  • 趋势递增:天然符合数据库索引优化需求。

  • 可读性好:ID为数字且连续,便于人工识别和排查问题。

缺点:

  • 性能瓶颈:单库自增依赖数据库单点,高并发下写入压力集中(即使分库分表,ID生成仍依赖数据库,无法脱离数据库瓶颈)。

  • 扩展性差:分库分表时步长/偏移量固定,新增分库分表需重新调整规则,易导致ID冲突。

  • 可用性风险:依赖数据库可用性,数据库宕机则无法生成ID。

  • 号段浪费: 应用进程意外重启,号段模式可能导致尚未使用的号段被浪费。

适用场景:适合低并发、小规模、对ID连续性、可读性要求高,且分库分表规模固定的业务,如内部管理等稳定优先、简单优先的系统。

3.3、基于Redis的自增ID

原理:利用Redis的INCR(自增)或INCRBY(指定步长自增)命令的原子性,生成全局唯一ID。

  • 通过Redis维护一个ID计数器,每次生成ID时调用INCR key,返回的结果即为新ID。

  • 为避免Redis压力,可批量生成(如一次获取1000个ID,本地缓存使用,用完再从Redis取)。

优点:

  • 高性能:Redis单节点QPS可达10万+,支持批量获取,性能远超数据库。

  • 趋势递增:ID严格递增,适合数据库索引。

  • 实现简单:依赖Redis原生命令,无需复杂逻辑。

缺点:

  • 强依赖Redis:强依赖Redis的高可用和数据持久化。Redis节点故障或主从切换时,服务不可用;且需平衡性能与数据可靠性(同步持久化严重影响性能,异步有丢数据风险)。

  • 内存成本: 存储序列键本身需要占用内存,尤其当需要为大量不同用途生成独立序列时。

  • ID连续性问题:若Redis重启且未持久化(或持久化数据丢失),可能导致ID跳变(如从1000跳到1,重复风险);即使持久化(RDB/AOF),也可能因最后一次自增未写入磁盘导致小范围跳变。

适用场景: 对性能要求极高且可容忍少量潜在ID丢失或不连续风险的场景;已有成熟Redis高可用集群(Sentinel/Cluster)架构;并发量很大的系统。需谨慎评估其对Redis的强依赖和数据可靠性要求。

3.4、雪花算法(Snowflake)

原理:由Twitter开源的分布式ID生成算法,基于“时间戳+机器ID+序列号”生成64位长整型ID。

优点:

  • 高性能:去中心化本地生成,无网络依赖,QPS可达百万级。

  • 趋势递增:时间戳在高位,ID按时间递增,适合数据库索引;ID本身可反解析出大致创建时间(可节省额外存储时间戳字段)

  • 可控性强:可自定义各段位数(如调整机器ID位数支持更多节点)。

  • 高可用:不依赖数据库、Redis等服务,可用性极高;各节点独立工作,系统整体扩展性好。

  • 空间占用小: 64位整数,存储传输效率较高。

缺点:

  • 时钟回拨问题:若机器时钟回拨(如NTP同步调整、服务器重启),可能生成重复ID(例如:A机器在10:00生成ID,时钟回拨到9:59后,会生成与9:59时重复的ID)。

  • 机器ID管理成本:需手动分配机器ID(如通过配置文件、注册中心),若重复分配会导致ID冲突;在动态弹性环境下(如K8s容器频繁启停),如何可靠、唯一地分配和回收机器ID(WorkerID/DatacenterID) 是个挑战。

  • 时间戳耗尽: 41位时间戳在选定的起点后约69年耗尽,需提前规划。

适用场景:适用于几乎一切中大型分布式系统(电商、社交、金融等),尤其是对性能、有序性、存储空间要求高的场景。需要做好机器ID管理和时钟回拨防护。 雪花算法变种众多(如百度的UidGenerator、美团的Leaf-snowflake),优化了时钟和机器ID问题。

4、雪花算法(Snowflake


4.1、原理解析

????????雪花算法实现简单、适配性强,无论是电商订单、日志追踪还是分布式存储,都能满足 “唯一、有序、高效、可扩展” 的核心需求,因此成为分布式ID主流选择。雪花算法生成的ID是一个64位的整数,由多段不同意义的数字拼接而成,这种分段设计让每个ID既带着时间印记,又能规避多机器冲突,就像身份证通过地址码、出生日期码、顺序码等分段信息实现全国唯一标识,既有序又精准。

符号位 + 时间戳 + 数据中心ID + 机器ID + 序列号

  1. 符号位(1位):始终为0(表示正数)。这保证了生成的 ID 是正整数。

  2. 时间戳(41位):雪花算法的核心部分, 记录生成ID时的毫秒级时间戳当前时间减去起始时间的差值,该部分保证了ID的大体有序性。41 位能表示的时间范围约为 2^41 毫秒 ≈ 69年。使用一个最近的起始时间(如 2025-08-04 00:00:00),可以大幅减少时间戳占用的位数。

  3. 数据中心ID(5位):用于标识生成ID的逻辑数据中心,允许最多 2^5 = 32个数据中心。

  4. 工作节点ID(5位):用于标识数据中心内的具体工作节点(机器、服务进程、Pod 等),允许每个数据中心最多 2^5 = 32个工作节点。在实际开发中,数据中心(高位)+ 工作节点(低位)经常被视为一个整体10位的机器ID,用于标识集群中的唯一节点(机器/服务实例),最多允许 2^10 = 1024个唯一节点。

  5. 序列号(12位):用来解决同一节点在同一毫秒内生成多个 ID 时的冲突问题。每个节点在每毫秒内都可以独立地从0开始递增生成序列号,当序列号用完(达到 4095)后,会强制等待到下一毫秒再继续生成。对于12位序列号,单节点每毫秒最多生成4096个ID,要达到这个并发量很极端(单节点超过400万QPS),现实中很难溢出。

????????以下为java实现的雪花算法代码示例(未考虑时钟回拨),起始时间决定了算法能生成ID的有效时长,通常将起始时间设为项目上线日期。

public class SnowflakeIdGenerator {
    // 起始时间戳,这里以2025-08-04 00:00:00为基准
    private final long startTimeStamp = 1751299200000L;
    
    // 机器ID所占位数
    private final long workerIdBits = 5L;
    // 数据中心ID所占位数
    private final long dataCenterIdBits = 5L;
    // 序列号所占位数
    private final long sequenceBits = 12L;
    
    // 机器ID最大值 31
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 数据中心ID最大值 31
    private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
    
    // 机器ID向左移位数
    private final long workerIdShift = sequenceBits;
    // 数据中心ID向左移位数
    private final long dataCenterIdShift = sequenceBits + workerIdBits;
    // 时间戳向左移位数
    private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;
    
    // 序列号掩码 4095
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    
    // 工作机器ID
    private final long workerId;
    // 数据中心ID
    private final long dataCenterId;
    // 序列号
    private long sequence = 0L;
    // 上次生成ID的时间戳
    private long lastTimestamp = -1L;
    
    // 构造函数
    public SnowflakeIdGenerator(long workerId, long dataCenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("Worker ID 不能大于 " + maxWorkerId + " 或小于 0");
        }
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException("数据中心 ID 不能大于 " + maxDataCenterId + " 或小于 0");
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }
    
    // 生成下一个ID
    public synchronized long nextId() {
        long currentTimestamp = System.currentTimeMillis();
        
        if (currentTimestamp == lastTimestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                // 当前毫秒内序列号已用完,等待下一毫秒
                currentTimestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            // 时间戳改变,重置序列号
            sequence = 0L;
        }
        
        lastTimestamp = currentTimestamp;
        
        // 按规则组合生成ID
        return ((currentTimestamp - startTimeStamp) << timestampShift) |
                (dataCenterId << dataCenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }
    
    // 等待下一毫秒
    private long waitNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
    
    // 测试示例
    public static void main(String[] args) {
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
        for (int i = 0; i < 10; i++) {
            System.out.println(idGenerator.nextId());
        }
    }
}    

4.2、为什么会出现重复ID?

????????雪花算法虽代码量少、实现简单,却并非万无一失。不少研发人员常常直接从网上拷贝现成的工具类,或是用大模型生成代码后直接用于生产环境 —— 直到某天突然收到用户反馈:自己账号的数据出现了错乱,明明只买了一件衣服,订单却显示多个其他辣眼的商品,还附带陌生的收货地址。手忙脚乱一顿排查后,竟发现数据库中出现了少量订单SN重复的异常数据,不由得心生疑惑:雪花算法不是每毫秒能生成4096个不重复编号吗?订单服务部署了十几个节点,但业务量真有这么大吗?到底为什么会出现重复呢?我们一起来一探究竟。

4.2.1、机器ID重复

  • 为什么重复: 多个运行的节点使用相同的数据中心ID(datacenter-id)和工作节点ID(worker-id)。即在同一毫秒内,如果多个节点的机器ID相同、系统时间戳相同,序列号就可能从相同起点开始分配并重叠,导致生成完全相同的ID三元组 (时间戳, 机器ID, 序列号)。

  • 典型现象: 多数研发人员会将数据中心ID和工作节点ID硬编码在代码中,或在配置文件里设置了相同的 datacenter-id与worker-id,这直接导致无论部署多少个节点,机器ID都完全一致。

  • 如何解决:

????????核心原则必须确保整个分布式集群中,任何两个同时工作的节点,它们的 (数据中心ID, 工作节点ID) 二元组(或者将二者视为10位合并的“机器ID”)必须是唯一的!

(1)手动配置文件:在启动服务前,为每个节点的配置文件(如 application.properties, application.yml, configmap 等)显式配置一个唯一的 datacenter-id 和 worker-id。该方案简单直观,但繁琐,易出错(配置冲突),适合小型、静态集群,不适用于节点动态伸缩的集群。

(2)系统环境变量:在部署节点(物理机、虚拟机、容器)时,通过启动脚本、容器编排系统(如K8s Deployment/StatefulSet 的env)为每个实例设置唯一的 SNOWFLAKE_DATACENTER_ID和SNOWFLAKE_WORKER_ID环境变量,服务启动时读取这些环境变量。

(3)利用基础设施的唯一性:

  • Kubernetes StatefulSet会为每个 Pod 分配一个固定且有序的唯一索引(从0开始)。比如名为snowflake-app的StatefulSet有3个Pod:snowflake-app-0, snowflake-app-1, snowflake-app-2。应用程序可以读取 spec.podName(通常是 HOSTNAME环境变量),解析末尾的数字索引,将这个索引直接用作工作节点ID。若业务需要扩容至超过worker-id最大阈值(如32个以上Pod),直接使用索引会导致worker-id重复,需结合数据中心ID(datacenter-id)拆分(如用 StatefulSet 名称哈希作为datacenter-id)。

  • 公有云(如阿里云、华为云、腾讯云ECS)会为每个虚拟机实例分配一个唯一ID,Pod(如Deployment)运行时也有自己的ID。应用程序可以在启动时通过查询实例/容器的元数据服务获取这个唯一ID,然后对这个较长的ID进行哈希并取模,映射到可用的datacenter-id和worker-id范围内(如总ID%1024,得到 0-1023的一个值)。该方案需要依赖特定平台的 API/服务。哈希取模存在极小冲突风险,需要设计好映射逻辑。

  • 利用IP地址 (网络标识):应用程序直接获取其运行环境(Pod、容器、虚拟机、物理机)的IP地址,对整个IP地址字符串或二进制表示计算哈希值取模,然后取模,映射为datacenter-id和worker-id。在Kubernetes 中,在Kubernetes中,Pod通常可以通过status.podIP获得,Deployment Pod重建通常会获得新IP;虚拟机/物理机IP也可能因维护、迁移或网络配置变更而改变。该方案同样存在极小概率ID冲突,且需容忍获取IP的性能开销和失败风险。

// 获取机器ID
private static long getNodeId() {
    try {
        InetAddress address = findFirstNonLoopbackAddress();
        String ip = address.getHostAddress();
        int hash = ip.hashCode();
        // 确保非负数并取模最大节点ID
        long nodeId = (hash & 0x7FFFFFFF) % (MAX_NODE_ID + 1);
        System.out.println("使用IP地址: " + ip + " 生成机器ID: " + nodeId);
        return nodeId;
    } catch (Exception e) {
        // 异常时随机生成节点ID
        long nodeId = new Random().nextInt((int) (MAX_NODE_ID + 1));
        System.out.println("获取IP失败,随机生成机器ID: " + nodeId);
        return nodeId;
    }
}

// 查找第一个非环回IPv4地址
private static InetAddress findFirstNonLoopbackAddress() throws SocketException {
    Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
    while (interfaces.hasMoreElements()) {
        NetworkInterface iface = interfaces.nextElement();
        if (iface.isLoopback() || iface.isVirtual() || !iface.isUp()) {
            continue;
        }
        Enumeration<InetAddress> addresses = iface.getInetAddresses();
        while (addresses.hasMoreElements()) {
            InetAddress addr = addresses.nextElement();
            if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) {
                return addr;
            }
        }
    }
    throw new RuntimeException("未找到非环回IPv4地址");
}

(4)外部协调服务:使用分布式协调服务(如ZooKeeper, etcd, Redis, 数据库)来注册节点并分配唯一的机器 ID。如Leaf-Snowflake改进了雪花算法,机器ID由Zookeeper协调分配、百度UID Generator启动时向DB注册节点分配唯一worker_id。

  • 流程示例:

  1. 节点启动时,连接到协调服务。

  2. 如果节点宕机或与协调服务断开连接(session超时),协调服务会自动删除其对应的临时节点,该机器ID被释放,可以被新节点申请使用。

  3. 节点将这个唯一的序号作为它的机器ID(或从中计算 datacenter-id 和 worker-id,如序号 % 1024)。序号在服务运行期间保持不变。

  4. 节点读取自己创建的节点的序号(如 0000000005)。

  5. 协调服务保证创建的有序节点的名称(包含一个单调递增的序号)是唯一的。

  6. 节点尝试在一个预设的路径下(如 /snowflake/workers)创建一个临时有序节点

  • 优点: 无需预配置,自动处理节点加入/离开,ID分配唯一且可靠,支持大规模集群。

  • 缺点: 增加了外部依赖和复杂度。

(5)设计机器ID位数的考虑:默认10位能支持 1024 个节点,对大多数公司规模通常够用。可依据业务规模灵活调整:

  • 并发量高但集群规模不大(节点少): 可以减少datacenter-id和worker-id 总位数(比如降到8位甚至更少),把节省出来的位数加到sequence序列号上。这样每个节点每毫秒可以生成更多的ID。
  • 集群规模巨大(超过1024节点): 需要增加datacenter-id和worker-id总位数(比如设为12位)。这时需要牺牲timestamp或sequence 的位数(如时间戳减到40,序列号减到11位)。牺牲时间戳位数会缩短系统的可用年限;牺牲序列号会降低单节点/毫秒的最大并发量。

4.2.2、时钟回拨

  • 为什么重复:系统时间因为NTP同步失败、闰秒调整、虚拟机/容器挂起恢复、人为设置错误等原因发生了向后跳跃,导致雪花算法生成ID时使用了之前已生成ID的时间戳部分,进而可能产生重复ID。

  • 如何解决:大部分雪花算法的优秀实现都包含了时钟回拨检测和处理机制,如抛出异常、短暂等待、使用备用逻辑。

(1)预防为主:禁止手动时间修改;NTP通过频率调整、分散度控制、时钟筛选、步进限制等机制防止时间回拨,如使用chrony进行平滑时间调整(stepping → slewing)、配置clock slew而非 jump避免突变。

(2)抛出异常:当检测到时钟回拨时,直接抛出异常,停止生成ID,等待人工干预或时间恢复正常。该方案简单安全,但影响业务连续性。

//处理时钟回拨
if (currentTimestamp < lastTimestamp) {
    throw new ClockBackwardException(
        "Clock moved backwards. Refusing to generate id for " + 
        (lastTimestamp - currentTimestamp) + " milliseconds");
}

(3)等待时钟恢复(适合毫秒级轻度回拨若发现回拨,不立即报错,而是阻塞等待 ,直到系统时间 ≥ lastTimestamp。该方案短暂阻塞,可能影响性能。

// 处理时钟回拨
if (currentTimestamp < lastTimestamp) {
    long offset = lastTimestamp - currentTimestamp;
    // 回拨时间小于1秒,阻塞等待
    if (offset <= MAX_BACKWARD_TIME) {
        currentTimestamp = waitForClockRecovery(lastTimestamp);
    } else {
        // 回拨时间超过1秒,抛出异常
        throw new RuntimeException("Clock moved backwards too much: " + offset + "ms");
    }
}

private long waitForClockRecovery(long lastTimestamp) {
    long timestamp = System.currentTimeMillis();
    while (timestamp < lastTimestamp) {
        //短暂休眠避免CPU空转
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Interrupted while waiting for clock recovery", e);
        }
        timestamp = System.currentTimeMillis();
    }
    System.out.println("Clock recovered after " + (timestamp - lastTimestamp) + "ms");
    return timestamp;
}

(4)回拨补偿:通过累积所有历史回拨时间,使生成器内部时间永远领先于系统时间,可避免使用Thread.sleep()造成的性能瓶颈。

// 可容忍的最大时钟回拨(毫秒)
private static final long MAX_BACKWARD_MS = 1000;

// 发生时钟回拨
if (currentTimestamp < lastTimestamp) {
    long backwardMs = lastTimestamp - currentTimestamp;

    // 超过容忍阈值则抛出异常
    if (backwardMs > MAX_BACKWARD_MS) {
        throw new IllegalStateException("Clock moved backwards by " + backwardMs + " ms, exceeding maximum allowed value");
    }
    // 记录回拨时间用于补偿
    clockOffset += backwardMs;
    // 补偿当前时间戳
    currentTimestamp = System.currentTimeMillis()+clockOffset;
}

(5)扩展位机制(秒级以上严重回拨修改雪花算法结构,预留几位用于表示“是否处于回拨状态”或“回拨次数”。当发生回拨时,增加“回拨版本号”,即使时间戳相同,版本不同也能区分ID。

5、Snowflake变种


????????美团 Leaf是由美团点评开源的一款高性能、高可用的分布式ID生成服务。它提供Web控制台,可以查看当前节点状态、TPS、WorkerID分配情况等,便于排查问题。Leaf支持Segment号段和Snowflake两种模式。

(1)Leaf-Snowflake:对原生Snowflake做了优化,使用ZooKeeper管理节点ID;当检测到系统时间回拨时,会抛出告警或进入等待/拒绝策略,防止ID冲突。

(2)Leaf-Segment(主流推荐)

原理

  • DB预分配号段:类似于数据库批量取号段,服务每次从数据库获取一个连续的ID范围缓存在内存中消费,减少数据库压力。
  • 双Buffer优化:异步填充下一个号段,避免分配阻塞。

优点:

  • 完全回避时钟问题:ID基于DB自增,与时间无关。
  • 高吞吐低延迟:本地缓存号段,QPS可达数万级。

缺点:

  • 依赖DB:DB需高可用(主从+监控),故障影响ID生成。
  • 号段跨步浪费:服务重启时缓存未使用号段会丢弃。

????????百度UID Generator的目标是解决高并发场景下的性能瓶颈,同时降低运维成本。它基于Snowflake改进,提供两种生成器:

  • DefaultUidGenerator:基础版,调整了Snowflake的结构(用 “秒级时间戳” 替代毫秒级,32位秒级时间戳可使用约136年;16 位机器ID+16位序列号),降低时间戳精度以换取更长的可用周期。

  • CachedUidGenerator:高性能版,引入 “RingBuffer(环形缓冲区)” 预生成 ID。启动时在内存中初始化一个RingBuffer,后台线程提前生成一批ID并填充到缓冲区;生成ID时直接从缓冲区 “取走” 即可(无需实时计算),缓冲区快空时自动触发补充。这种 “预生成 + 缓存” 机制大幅提升了性能。

优点

  • 性能超高:取ID仅内存操作,单机QPS可达百万级
  • 无时钟问题:ID由DB序列生成,时间戳只作为ID字段。

缺点

  • 重启初始化慢:首次启动需填充整个缓存(如200万ID)。
  • 缓存未消费浪费:服务关闭时未使用ID丢失。

维度

原生Snowflake

美团Leaf

百度UID Generator

定位

Twitter 开源,分布式 ID 生成的经典方案,面向无外部依赖的轻量场景

美团内部开源,面向业务灵活性设计,支持多模式适配不同场景(兼顾性能与易用性)

百度内部开源,基于 Snowflake优化,面向高并发场景,强调自动化与高性能

原理

基于「时间戳 + 机器 ID + 序列号」的 64 位 ID 生成,纯内存计算

支持两种模式:
1. 号段模式(从 DB 取连续号段,本地递增生成)
2. Snowflake 模式(优化版雪花算法)

基于 Snowflake 改进,支持两种生成器:
DefaultUidGenerator和CachedUidGenerator

ID结构

常规结构:
41位时间戳+10 位机器 ID+12 位序列号

号段模式:无固定结构(由 DB 号段 + 本地递增组成,可自定义位数)
Snowflake 模式:同 Snowflake(微调机器 ID/序列号位数)

DefaultUidGenerator:32位时间戳+16位机器ID+16位序列号
CachedUidGenerator:结构同上,通过 RingBuffer预生成

机器ID分配

需手动配置(如通过配置文件 / 部署脚本指定,需保证集群内唯一)

号段模式:无需机器ID(依赖 DB 号段隔离)
Snowflake 模式:手动配置或基于IP哈希生成

自动分配(基于DB维护 workerID表,启动时从 DB获取,支持动态扩容)

时钟回拨处理

无原生处理逻辑(时钟回拨时可能生成重复 ID)

号段模式:无时钟依赖(ID 基于号段递增,与时间无关)
Snowflake 模式:检测到回拨时,等待时钟恢复或触发报警

记录历史最大时间戳:
回拨幅度较小时等待恢复,幅度较大时拒绝生成(避免重复)

性能表现

纯内存计算,无外部交互,单机性能极高(可达10万+/ 秒)

号段模式:减少DB交互(一次取一段ID,本地生成),性能较好(万级 / 秒,取决于号段大小)
Snowflake模式:同原生Snowflake

CachedUidGenerator:基于 RingBuffer 预生成ID,无实时计算开销,性能最优(单机百万级/秒)
DefaultUidGenerator:性能略低(万级/秒)

外部依赖

无任何外部依赖(仅依赖本地系统时钟)

号段模式:依赖关系型数据库(如MySQL,用于存储号段)
Snowflake模式:默认强依赖 Zookeeper,可选mysql和local

依赖关系型数据库(用于存储workerID 分配信息)

ID连续性

不连续(时间戳 / 机器 ID 变化会导致 ID 跳变)

号段模式:局部连续(同号段内 ID 连续,跨号段跳变)
Snowflake模式:不连续

不连续(时间戳 / 序列号递增,但存在机器 ID 隔离导致的跳变)

ID有序性

全局大致有序(按时间戳递增,同毫秒内按序列号递增)

号段模式:局部有序(同号段内严格递增)
Snowflake模式:同原生Snowflake

全局大致有序(时间戳 + 序列号递增,缓存预生成不影响最终顺序)

优缺点

优点:无外部依赖、实现简单、性能极高
缺点:机器ID需手动维护、时钟回拨易出问题、ID无业务含义

优点:支持两种模式(灵活适配场景)、号段模式可兼容旧系统
缺点:号段模式依赖 DB、Snowflake模式仍需处理机器ID

优点:机器ID自动分配(无需手动配置)、高性能(缓存预生成)、时钟回拨处理完善
缺点:依赖 DB、实现较复杂(RingBuffer 设计)

适用场景

无 DB 依赖需求、节点数量固定(≤1024)、对 ID生成性能要求高的场景(如分布式系统基础 ID)

需灵活切换模式(如部分场景需连续 ID、部分需无DB依赖)、可接受 DB依赖的业务

高并发场景(如秒杀、订单)、节点动态扩容(无需手动配置机器 ID)、需严格避免ID重复

????????原生Snowflake、美团Leaf和百度UID Generator三者没有绝对优劣,核心是匹配业务对 “依赖、性能、连续性” 的优先级 —— 分布式ID的设计本质,是对 “唯一、有序、性能、无依赖” 的取舍与平衡。


专栏文章推荐:

网关有什么用?如何进行网关选型?

?架构实战:2万字真实案例全方位解析高并发系统性能优化之道

?架构师指南:像呵护感情一样构建无懈可击的系统稳定性防线

系统性能评估:如何定义并发数、响应时间和吞吐量

实战:混合云架构Nginx+Lua实现流量跨机房分发

架构图的魅力:如何用UML提升架构设计的质量

为什么要做架构设计?架构设计包含哪些内容?

架构师的职责是什么?程序员如何转型为架构师?

什么是系统架构?架构如何演进?

Mysql数据库连接池druid、hikaricp优化实战

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT孟德

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值
骨质增生吃什么药 小孩有积食吃什么调理 六味地黄丸什么牌子好 牙龈发炎吃什么药 男性左下腹痛是什么原因
cnd是什么意思 天地不仁以万物为刍狗什么意思 什么人不适合做收银员 生性凉薄是什么意思 跑团什么意思
慢阻肺吃什么药最有效 就这样吧是什么意思 出cos是什么意思 什么人喜欢天天下雨 为什么女生
血糖偏高可以吃什么水果 举案齐眉是什么意思 白茶和绿茶有什么区别 小便是红色的是什么原因男性 逼上梁山什么意思
地奥心血康软胶囊主治什么病hcv7jop6ns2r.cn 道谢是什么意思hcv9jop1ns9r.cn 一什么鼻子hcv8jop5ns4r.cn 高玩是什么意思hcv9jop1ns3r.cn 脑血管堵塞是什么症状hcv9jop5ns7r.cn
胸是什么hcv8jop2ns1r.cn 羊属于什么科huizhijixie.com 思诺思是什么药bfb118.com 谷读什么hcv9jop8ns2r.cn 水厄痣是什么意思hcv8jop6ns2r.cn
性格什么意思hcv8jop2ns6r.cn 春天都有什么花开luyiluode.com 联系是什么意思hcv9jop0ns2r.cn 毫无意义是什么意思hcv8jop1ns2r.cn 无语什么意思hcv9jop3ns0r.cn
三月18号是什么星座的hcv9jop1ns3r.cn 经常拉肚子挂什么科hcv8jop9ns2r.cn 蚂蚁的天敌是什么hcv8jop6ns8r.cn 查颈椎挂什么科hcv8jop0ns4r.cn 阿托品属于什么类药物hcv9jop4ns4r.cn
百度