X

分布式系统ID的生成方法之UUID、数据库、算法、Redis、Leaf方案

前言

一般单机或者单数据库的项目可能规模比较小,适应的场景也比较有限,平台的访问量和业务量都较小,业务ID的生成方式比较原始但是够用,它并没有给这样的系统带来问题和瓶颈,所以这种情况下我们并没有对此给予太多的关注。但是对于大厂的那种大规模复杂业务、分布式高并发的应用场景,显然这种ID的生成方式不会像小项目一样仅仅依靠简单的数据自增序列来完成,而且在分布式环境下这种方式已经无法满足业务的需求,不仅无法完成业务能力,业务ID生成的速度或者重复问题可能给系统带来严重的故障。所以这一次,我们看看大厂都是怎么分析和解决这种ID生成问题的,同时,我也将我之前使用过的方式拿出来对比,看看有什么问题,从中能够得到什么启发。

分布式ID的生成特性

在分析之前,我们先明确一下业务ID的生成特性,在此特性的基础上,我们能够对下面的这几种生成方式有更加深刻的认识和感悟。

  • 全局唯一,这是基本要求,不能出现重复。
  • 数字类型,趋势递增,后面的ID必须比前面的大,这是从MySQL存储引擎来考虑的,需要保证写入数据的性能。
  • 长度短,能够提高查询效率,这也是从MySQL数据库规范出发的,尤其是ID作为主键时。
  • 信息安全,如果ID连续生成,势必会泄露业务信息,甚至可能被猜出,所以需要无规则不规则。
  • 高可用低延时,ID生成快,能够扛住高并发,延时足够低不至于成为业务瓶颈。

分布式ID的几种生成办法

下面介绍几种我积累的分布式ID生成办法,网络上都能够找得到,我通过学习积累并后期整理加上自己的感悟分享于此。虽然平时可能因为项目规模小而用不着,但是这种提出方案的思想还是很值得学习的,尤其是像美团的Leaf方案,我感觉特别的酷。

目录:

基于UUID

基于数据库主键自增

基于数据库多实例主键自增

基于类Snowflake算法

基于Redis生成办法

基于美团的Leaf方案(ID段、双Buffer、动态调整Step)

基于UUID

这是很容易想到的方案,毕竟UUID全球唯一的特性深入人心,但是,但凡熟悉MySQL数据库特性的人,应该不会用此来作为业务ID,它不可读而且过于长,在此不是好主意,除非你的系统足够小而且不讲究这些,那就另说了。下面我们简要总结下使用UUID作为业务ID的优缺点,以及这种方式适用的业务场景。

优点

  • 代码实现足够简单易用。
  • 本地生成没有性能问题。
  • 因为具备全球唯一的特性,所以对于数据库迁移这种情况不存在问题。

缺点

  • 每次生成的ID都是无序的,而且不是全数字,且无法保证趋势递增。
  • UUID生成的是字符串,字符串存储性能差,查询效率慢。
  • UUID长度过长,不适用于存储,耗费数据库性能。
  • ID无一定业务含义,可读性差。

适用场景

  • 可以用来生成如token令牌一类的场景,足够没辨识度,而且无序可读,长度足够。
  • 可以用于无纯数字要求、无序自增、无可读性要求的场景。

基于数据库主键自增

使用数据库主键自增的方式算是比较常用的了,以MySQL为例,在新建表时指定主键以auto_increment的方式自动增长生成,或者再指定个增长步长,这在小规模单机部署的业务系统里面足够使用了,使用简单而且具备一定业务性,但是在分布式高并发的系统里面,却是不适用的,分布式系统涉及到分库分表,跨机器甚至跨机房部署的环境下,数据库自增的方式满足不了业务需求,同时在高并发大量访问的情况之下,数据库的承受能力是有限的,我们简单的陈列一下这种方式的优缺点。

优点

  • 实现简单,依靠数据库即可,成本小。
  • ID数字化,单调自增,满足数据库存储和查询性能。
  • 具有一定的业务可读性。

缺点

  • 强依赖DB,存在单点问题,如果数据库宕机,则业务不可用。
  • DB生成ID性能有限,单点数据库压力大,无法扛高并发场景。

适用场景

  • 小规模的,数据访问量小的业务场景。
  • 无高并发场景,插入记录可控的场景。

基于数据库多实例主键自增

上面我们大致讲解了数据库主键自增的方式,讨论的时单机部署的情况,如果要以此提高ID生成的效率,可以横向扩展机器,平衡单点数据库的压力,这种方案如何实现呢?那就是在auto_increment的基础之上,设置step增长步长,让DB之前生成的ID趋势递增且不重复。

这种方案巧妙地把64位分别划分成多段,分开表示时间戳差值、机器标识和随机序列,先以此生成一个64位地二进制正整数,然后再转换成十进制进行存储。

其中,1位标识符,不使用且标记为0;41位时间戳,用来存储时间戳的差值;10位机器码,可以标识1024个机器节点,如果机器分机房部署(IDC),这10位还可以拆分,比如5位表示机房ID,5位表示机器ID,这样就有32*32种组合,一般来说是足够了;最后的12位随即序列,用来记录毫秒内的计数,一个节点就能够生成4096个ID序号。所以综上所述,综合计算下来,理论上Snowflake算法方案的QPS大约为409.6w/s,性能足够强悍了,而且这种方式,能够确保集群中每个节点生成的ID都是不同的,且区间内递增。

优点

  • 每秒能够生成百万个不同的ID,性能佳。
  • 时间戳值在高位,中间是固定的机器码,自增的序列在地位,整个ID是趋势递增的。
  • 能够根据业务场景数据库节点布置灵活挑战bit位划分,灵活度高。

缺点

  • 强依赖于机器时钟,如果时钟回拨,会导致重复的ID生成,所以一般基于此的算法发现时钟回拨,都会抛异常处理,阻止ID生成,这可能导致服务不可用。

适用场景

  • 雪花算法有很明显的缺点就是时钟依赖,如果确保机器不存在时钟回拨情况的话,那使用这种方式生成分布式ID是可行的,当然小规模系统完全是能够使用的。

基于Redis生成办法

Redis的INCR命令能够将key中存储的数字值增一,得益于此操作的原子特性,我们能够巧妙地使用此来做分布式ID地生成方案,还可以配合其他如时间戳值、机器标识等联合使用。

优点

  • 有序递增,可读性强。
  • 能够满足一定性能。

缺点

  • 强依赖于Redis,可能存在单点问题。
  • 占用宽带,而且需要考虑网络延时等问题带来地性能冲击。

适用场景

  • 对性能要求不是太高,而且规模较小业务较轻的场景,而且Redis的运行情况有一定要求,注意网络问题和单点压力问题,如果是分布式情况,那考虑的问题就更多了,所以一帮情况下这种方式用的比较少。

Redis的方案其实可靠性有待考究,毕竟依赖于网络,延时故障或者宕机都可能导致服务不可用,这种风险是不得不考虑在系统设计内的。

回到顶部(go to top)

基于美团的Leaf方案

从上面的几种分布式ID方案可以看出,能够解决一定问题,但是都有明显缺陷,为此,美团在数据库的方案基础上做了一个优化,提出了一个叫做Leaf-segment的数据库方案。

原方案我们每次获取ID都需要去读取一次数据库,这在高并发和大数据量的情况下很容易造成数据库的压力,那能不能一次性获取一批ID呢,这样就无需频繁的造访数据库了。

Leaf-segment的方案就是采用每次获取一个ID区间段的方式来解决,区间段用完之后再去数据库获取新的号段,这样一来可以大大减轻数据库的压力,那怎么做呢?

很简单,我们设计一张表如下: