共计 2943 个字符,预计需要花费 8 分钟才能阅读完成。
在单库单表时,业务 ID 能够依赖数据库的自增主键实现,当初咱们把存储拆分到了多处,如果还是用数据库的自增主键,就会呈现主键反复的状况。
所以咱们不得不面对的一个抉择,就是ID 生成器,应用一个惟一的字符串,来标识一条残缺的记录。
这时候,不能应用 md5 或者 sha1 来对整个记录做摘要,因为咱们后续还要改变这个记录。也不能应用单机的计数器,因为计数器容易重启清零,也会存在多台机器上的数值反复,这违反了无状态服务的建设指标。
UUID
尽管 UUID 在大多数语言中都有相干的类库,但除非迫不得以,咱们个别不会应用它。UUID 尽管不会反复,但它十分的长,长的让人望而却步。
规范的 UUID 有 5 个局部组成:8-4-4-4-12,一共 32 个十六进制字符。因而,一共是 128 位。当把 UUID 作为数据库的索引时,会 因为它没有程序性造成索引的随机散布 和因为数据量微小造成查问性能升高。
- 且无序会造成每一次 UUID 数据的插入都会对主键的 b + 树进行很大的批改, 会产生离散 IO,从而产生性能瓶颈。
同时,UUID 也是不可读的,如果你把它打印在纸质的订单上,并不是一个好的主见。UUID 同时还有信息安全的隐患,它的数据计算里有 MAC 地址的参加,比拟出名的是,曾被用于寻找梅丽莎病毒的制作者地位。
MySQL8 当前
MySQL 8.0 推出了函数 UUID_TO_BIN,它能够把 UUID 字符串:
- 通过参数将工夫高位放在最前,解决了 UUID 插入时乱序问题;
- 去掉了无用的字符串 ”-“,精简存储空间;
- 将字符串其转换为二进制值存储,空间最终从之前的 36 个字节缩短为了 16 字节。
同时还提供了 BIN_TO_UUID,反对将二进制值反转为 UUID 字符串,不必放心 UUID 的性能和存储占用的空间问题,相干的插入性能测试,后果如下表所示:
因为 UUID_TO_BIN 转换为的后果是 16 字节,仅比自增 ID 减少 8 个字节,最初存储占用的空间也仅比自增大了 3G。
而且因为 UUID 能保障全局惟一,因而应用 UUID 的收益远远大于自增 ID。在海量并发的互联网业务场景下,更举荐 UUID 这样的全局惟一值做主键。
但请牢记:分布式数据库架构,仅用 UUID 做主键仍然是不够的。
数据库自增 ID
当数据量宏大时,在数据库分库分表后,数据库自增 id 不能满足惟一 id 来标识数据;因为每个表都按本人节奏自增,会造成 id 抵触,无奈满足需要
革新工夫戳
如果你是单机利用,那么应用工夫戳没什么问题,即便不必纳秒,应用毫秒也是足够的。但在分布式环境上面,工夫戳同样不是一个好的抉择。
即便你在机器装置了 ntpd 工夫同步,但因为网络和机器的差别,计算机的时钟总是存在差别,你的工夫戳总会呈现反复。为了解决这个问题,你须要减少一些其余的标识,比方机器的 ID,或者更多细分的信息缩小工夫的碰撞。
这种自定义的 ID 生成器,只适宜特定的业务,做着做着你就会发现,它实质上是雪花算法的变种。
全局 ID 生成器服务
能够设计一个全局 ID 生成器服务,每次找服务索要主键,这样尽管能够在业务间实现全局惟一,然而齐全依赖全局 ID 生成服务,依赖性大,服务一旦宕机,会影响所有相干依赖服务。
例如应用 Redis 的计数器,原子性自增,益处在于应用内存,并发性能好,但存在数据失落;自增数据量泄露的问题
雪花算法
Twitter 雪花算法生成后是一个 64bit 的 long 型的数值,默认字符串长度是 19 位,它分为 4 个局部,根本放弃了自增
蕴含四个组成部分
不应用:1bit,最高位是符号位,0 示意正,1 示意负,固定为 0
工夫戳:41bit,毫秒级的工夫戳(41 位的长度能够应用 69 年)
标识位:5bit 数据中心 ID,5bit 工作机器 ID,两个标识位组合起来最多能够反对部署 1024 个节点(2^10 = 1024 个节点)
如果是分布式应用部署应保障每个工作过程的标识位 id 是不同的
序列号:12bit 递增序列号,示意节点毫秒内生成反复,通过序列号示意惟一,12bit 每毫秒可产生 4096 个 ID
通过序列号 1 毫秒能够产生 4096 个不反复 ID,则 1 秒能够生成 4096 * 1000 = 409w ID
默认的雪花算法是 64 bit,具体的长度能够自行配置。如果心愿运行更久,减少工夫戳的位数 ;如果须要反对更多节点部署, 减少标识位长度 ;如果并发很高, 减少序列号位数
总结:雪花算法并不是变化无穷的,能够依据零碎内具体场景进行定制
SnowFlake 算法的长处:
- 高性能高可用:生成时不依赖于数据库,齐全在内存中生成
- 高吞吐:每秒钟能生成数百万的自增 ID
- ID 自增:存入数据库中,索引效率高
SnowFlake 算法的毛病: 依赖与零碎工夫的一致性,如果零碎工夫被回调,或者扭转,可能会造成 ID 抵触或者反复
实用场景
因为雪花算法有序自增,保障了 MySQL 中 B+ Tree 索引构造插入高性能
所以,日常业务应用中,雪花算法更多是被利用在数据库的主键 ID 和业务关联主键
存在的问题
机器标识位统一
标识位反复的状况下,雪花 ID 也可能会反复,比方:
- 服务通过集群的形式部署,其中局部机器标识位统一
时钟回拨的问题
为什么会有时钟回拨问题
- 有人篡改了宿主机的零碎工夫
- 集群中可能会进行整体的时钟同步,从而批改机器的本地工夫
时钟回拨对雪花算法的影响
如果篡改了本地工夫,那就 有危险产生反复的 ID,而且无奈满足趋势递增了。
解决思路
- 计划一:想方法探测到时钟回拨,而后做出对应的策略
- 计划二:摸索一种 ID 生成的形式,不齐全依附工夫戳来保障雪花算法,或者间接应用别的策略代替工夫戳
JS 的坑
值得注意的是,雪花算法在 JavaScript 中有一个坑。后端在返回 ID 的时候,须要应用 String 类型代替 Long 类型,否则会产生料想不到的谬误。
这是因为。在 JavaScript 中,存在两种数字。Number 和 BigInt。最罕用的,就是 number。
最大的 Number,叫做Number.MAX_SAFE_INTEGER
,它的值为:
- 2^53-1 或者
- +/- 9,007,199,254,740,991
家喻户晓,Java 中的 Long,是 64 位的。Js 中的这个平安 Integer,齐全达不到 Java 中定义的长度。
这就是万恶的 IEEE_754
标准,它在 Long 长度大于 17 位时会呈现精度失落的问题。
常见实现计划
百度(uid-generator)
uid-generator
是由百度技术部开发,我的项目地址:uid-generator
uid-generator
是基于 Snowflake
算法实现的,与原始的 snowflake
算法不同在于,uid-generator
反对自定义工夫戳、工作机器 ID 和序列号等各局部的位数,而且 uid-generator
中采纳用户自定义 workId
的生成策略。
uid-generator
须要与数据库配合应用,须要新增一个 WORKER_NODE
表。 当利用启动时会向数据库表中去插入一条数据,插入胜利后返回的自增 ID 就是该机器的 workId
数据由 host
,port
组成。
美团(Leaf)
github 地址:Leaf
美团的 Leaf
也是一个分布式 ID 生成框架。它十分全面,即反对号段模式,也反对 snowflake
模式。
号段模式:依赖于数据库,然而区别于数据库主键自增的模式。假如 100 为一个号段 100,200,300,每取一次能够取得 100 个 ID,性能显著进步。