我正在参与「启航计划」

在单库单表时,事务 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 算法的优点:

  1. 高功能高可用:生成时不依托于数据库,彻底在内存中生成
  2. 高吞吐:每秒钟能生成数百万的自增 ID
  3. 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数据由hostport组成。

美团(Leaf)

github地址:Leaf

美团的Leaf也是一个散布式ID生成结构。它十分全面,即支撑号段形式,也支撑snowflake形式。

号段形式:依托于数据库,可是区别于数据库主键自增的形式。假设100为一个号段100,200,300,每取一次能够获得100个ID,功能显著进步。