为什么需要全局唯一 ID?分布式服务架构模式下分库分表的设计,使得多个库或多个表存储相同的业务数据。这种情况根据数据库的自增ID就会产生相同ID的情况,不能保证主键的唯一性
业务系统对 ID 的要求有哪些呢?
对于 3、4 而言,需求是互斥的,无法使用同一个方案解决
通常使用号段模式满足 3,雪花算法满足 4
前后端联调的坑(long 传给前端接受的坑):JS Number 采用的 IEEE 754 规范,也就是相当于 Java 的 double;double 最大允许的正整数为 2 53 − 1 2^{53} - 1 253−1,long 最大值为 2 63 − 1 2^{63} - 1 263−1,所以可能造成数据精度的丢失
号段模式:一次性向数据库申请一批连续的 ID 放入 JVM 缓存,当用完时再从数据库中拉取
号段模式的优点:本机单调递增、全局趋势递增
号段模式的问题:
上诉问题优化:
多实例支持将引入新的问题,可能造成拉取重复 id,如何避免?
引入内步长的概念。如有三台数据库实例,则将内步长置为 3。起始序列号设 A 为 1,B 为 2,C 为 3
则每次向不同实例拉取时,id + 内步长
此时 A 的 增长趋势为 1 4 7 10 …
此时 B 的 增长趋势为 2 5 8 11 …
此时 C 的 增长趋势为 3 6 9 12 …
号段模式业界经典实现:美团 Leaf-segment,滴滴 tinyid
雪花算法组成:1 + 41 + 10 + 12;1 是符号位始终为 0 保证 id 为正数;41 是时间戳具体到毫秒(从 1970 年开始)理论上限为 69 年;10 是所允许机器 ID 数,最大允许 1024 台机器;12 是同一时间点上最大允许生成的 id 数,即每毫秒最多分配 4096 个 ID
雪花算法优化:时间戳从 1970 年开始,白白浪费几十年,可以从实际投入使用时间开始,如 2022 年;雪花算法各个组成都可以按需进行分配
雪花算法的优点:趋势递增,不依赖第三方组件,按需分配 bit
为什么使用雪花算法,而不是使用号段模式?出于安全性考虑,如:通过 id 猜测订单数量。严禁 id 号生成单调递增
为什么使用雪花算法,而不是使用 UUID?出于性能考虑,插入数据库时主键乱序则可能导致页分裂,严重影响插入效率
雪花算法的缺陷:时间回拨;因为雪花算法生成的 ID 是基于时间的,现实世界的时间是单调的,但对于计算机而言却不是
时间回拨:服务器时间突然倒退到之前的时间,则可能产生重复 ID
造成时间回拨的原因:1. 手动改服务器时间(一般不会);2. 服务器开启 NTP 服务做时间校准(重点)
什么是 ntp?网络时间协议,用以同步分布式场景下各服务器时间;基于 UDP 协议
为什么需要 ntp,而不是使用服务器自己的时间?长时间允许的服务器会造成不可容忍的误差,所以需要同步时钟
解决时间回拨方案:等待时钟同步,如果当前生成的 ID 时间戳小于前一个就不是有效的 ID,需要循环生成直到为有效 ID;此法可能造成长时间的空转,所以需要设置最大容忍阈值
雪环算法经典业界实现:MongoDB ObjectID,美团 Leaf-snowflake,百度 UidGenerator