为什么需要全局唯一id
在分布式架构下,经常有需求需要生成全局唯一id,比如优惠券等券码,或者分库分表,每个表都用自增id,会导致每个表的id都不唯一,都需要生成全局唯一id。
这里介绍几种分布式架构下全局唯一id的生成方式,然后再结合具体场景说下实际项目中遇到哪些问题和解决方案,最后介绍下美团的全局唯一id生成服务leaf的实现。
常用解决方案
一、UUID
这是最容易想到的方案,UUID(Universally Unique Identifier)是32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,目前生成方式有5种,可参考A Universally Unique IDentifier (UUID) URN Namespace
好处就是生成很简单,每个系统本地生成就可以。
坏处更加明显,就是太长和无序,会导致以下问题:
1、很多业务场景不适用,比如优惠券的发放,可能12位的券池就能满足发放的需要了,太长不但不易于存储,还会导致无法用于券码的展示
2、不利于做索引。mysql存储时,uuid的长度太长不利于做主键,而且无序性会导致作为主键插入时数据位置频繁变动,影响性能
所以,很多场景或者用于主键的情况下,都不能使用uuid
二、数据库自增主键
可以创建一个表,通过数据插入获取对应的自增主键,作为全局唯一id
缺点也很明显,就是高并发的场景下,受限于单台mysql的性能。而且可用性差,DB出现问题会导致id无法生成。
当然可以通过主从的方式增强可用性,同时增加表采用不同自增步长的方式增加并发性能,比如假设我们要部署N台机器,步长需设置为N,每台的初始值依次为0,1,2…N-1;但是还会带来新的问题,比如不利于拓展,想提升性能就要堆机器,但是步长已经确定不好更改,主从延迟会导致唯一id的不唯一,。
三、snowflake
这是twitter开源的分布式id生成算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflake中的64-bit分别表示如下图(图片来自网络)所示:
上面第一个部分,是1个bit:0,这个是无意义的上面第二个部分是41个bit:表示的是时间戳;上面第三个部分是5个bit:表示的是机房id,10001上面第四个部分是5个bit:表示的是机器id,1 1001上面第五个部分是12个bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的id的序号,0000 00000000
优点就是灵活,5位可以用来做业务标识
缺点呢就是比较依赖时钟
实际应用
场景一:分库分表后数据唯一id
需求:
- id要作为主键,即要满足趋势递增
- id长度尽量小
这种场景我们在项目中使用了数据库自增主键的方式,创建了32张表,步长32并设置递增初始值的方式,生成唯一id,可以较好的满足该场景。
场景二:团购券券码
需求:
- 券码唯一且不能趋势递增,不然用户会猜测券码,不安全
- 券码长度控制在可接受范围
这种场景项目中用了碰撞的方式,随机生成的固定长度券码保存在券码表,券码唯一索引,job定时补充券码表并捞取可用券码到缓存券池。