欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

九种分布式ID生成算法详解

程序员文章站 2022-03-08 17:52:27
...

一、分布式ID简介

1、什么是分布式ID?

在我们业务数据量不大的时候,单库单表完全可以支撑现有业务,数据再大一点搞个MySQL主从同步读写分离也能对付。

但随着数据日渐增长,主从同步也扛不住了,就需要对数据库进行分库分表,但分库分表后需要有一个唯一ID来标识一条数据,数据库的自增ID显然不能满足需求;例如我们的订单,需要有一个全局唯一标识的订单号,这个订单号就是分布式ID

2、分布式ID需要满足那些条件?

    1. 全局唯一:必须保证ID是全局性唯一的,基本要求
    1. 高可用:
    1. 高性能:高可用低延时,ID生成响应要块,否则反倒会成为业务瓶颈
    1. 简单通用:拿来即用的原则

 

二、 分布式ID都有哪些生成方式?

  1. UUID
  1. 数据库自增ID
  1. 数据库多主模式
  1. 号段模式
  1. Redis
  1. 雪花算法(SnowFlake)
  1. 滴滴出品(TinyID)
  1. 百度 (Uidgenerator)
  1. 美团(Leaf)
  1. MongoDB的ObjectID

 

1、UUID

   ​String uuid = UUID.randomUUID().toString().replaceAll("-","");​

 

优点:

  • 足够简单,只需要上面一行代码即可生成一个全局唯一的ID,

缺点:

  • 因为UUID是随机的,没有具体的业务含义,如果用UUID作为订单号,是没有意义的,从这个UUID看不到跟订单有任何的关联关系,
  • UUID是一个无序的字符串,不具备趋势自增特性
  • 长度过长,存储以及查询对MySQL的性能消耗较大,如果用来作为主键,索引的性能会很差

 

2、数据库自增ID

基于数据库的​auto_increment​自增ID也可以生成一个分布式ID

 

优点:

  • 实现简单,ID单调自增,数值类型查询速度快

缺点:

  • 访问量激增时MySQL本身就是系统的瓶颈,用它来实现分布式服务风险比较大,不推荐!

 

3、数据库集群模式

为了解决第二种数据库单个DB的风险,可以采用数据库集群的模式来解决这个问题。

  • 可以使用主从模式解决单DB不满足高可用的问题,需要考虑的问题是:主从之间同步是存现时间延迟的,如果主DB挂了可能会有重复的ID生成(原因是什么? --  主从复制延时导致)
  • 如果担心一个主节点挂掉没法用,那就做双主模式集群,也就是两个Mysql实例都能单独的生产自增ID。需要考虑的问题是:1:两个MySQL实例的自增ID都从1开始,会生成重复的ID,怎么解决?2:后续DB扩容怎么办

 

 

九种分布式ID生成算法详解

4、基于数据库的号段模式

 

号段模式是当下分布式ID生成器的主流实现方式之一,号段模式可以理解为从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存,典型的是TinyID。表结构如下:

​CREATE TABLE id_generator (​

​  id int(10) NOT NULL,​

​  max_id bigint(20) NOT NULL COMMENT '当前最大id',​

​  step int(20) NOT NULL COMMENT '号段的布长',​

​  biz_type    int(20) NOT NULL COMMENT '业务类型',​

​  version int(20) NOT NULL COMMENT '版本号',​

​  PRIMARY KEY (`id`)​

​) ​

 

等这批号段ID用完,再次向数据库申请新号段,对​max_id​字段做一次​update​操作,​update max_id= max_id + step​,update成功则说明新号段获取成功,新的号段范围是​(max_id ,max_id +step]​。

SQL语句:

​update id_generator set max_id = #{max_id+step} where biz_type = XXX​

 

为了解决并发更新的问题,使用乐观锁控制,优化后的SQL:

​update id_generator set max_id = #{max_id+step}, version = version + 1 where version = # {version} and biz_type = XXX​

 

优点:

  • 这种分布式ID生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多

 

5、基于Redis模式

Redis也同样可以实现,原理就是利用redis的 incr命令实现ID的原子性自增。

Redis是内存数据库,需要考虑持久化的问题,redis有两种持久化方式RDB和AOF

  • RDB:会定时打一个快照进行持久化,假如连续自增但redis没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。
  • AOF:对每条写命令进行持久化,即使redis挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致Redis重启恢复的数据时间过长。

 

6、基于雪花算法(Snowflake)模式

雪花算法(Snowflake)是twitter公司内部分布式项目采用的ID生成算法,开源后广受国内大厂的好评,在该算法影响下各大公司相继开发出各具特色的分布式生成器。
 

九种分布式ID生成算法详解

Snowflake生成的是Long类型的ID,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特。

Snowflake ID组成结构:​正数位​(占1比特)+ ​时间戳​(占41比特)+ ​机器ID​(占5比特)+ ​数据中心​(占5比特)+ ​自增值​(占12比特),总共64比特组成的一个Long类型。

  • 第一个bit位(1bit):Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。
  • 时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
  • 工作机器id(10bit):也被叫做​workId​,这个可以灵活配置,机房或者机器号组合都可以。
  • ***部分(12bit),自增值支持同一毫秒内同一个节点可以生成4096个ID

Snowflake雪花算法最核心的是中间的10位工作机器ID的分配,做到自动生成workID,避免运维人员的去分配

 

优点:

  • 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
  • 可以根据自身业务特性分配bit位,非常灵活。

缺点:

  • 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

 

 

根据算法的思路,使用java语言可以直接生成一个工具类,进行本地调用用来生成全局唯一的ID,

参考github地址:https://github.com/beyondfengyu/SnowFlake

 

7、滴滴出品(TinyID)

 

​Tinyid​由滴滴开发,Github地址:https://github.com/didi/tinyid

​Tinyid​是基于号段模式每个服务获取一个号段(1000,2000]、(2000,3000]、(3000,4000]

1:具体实现原理:

  • tinyid是基于数据库发号算法实现的,简单来说是数据库中保存了可用的id号段,tinyid会将可用号段加载到内存中,之后生成id会直接内存中产生。
  • 可用号段在第一次获取id时加载,如当前号段使用达到一定量时,会异步加载下一可用号段,保证内存中始终有可用号段。当前号段使用完毕,下一号段会替换为当前号段。依次类推。

2:架构图

九种分布式ID生成算法详解

提供了两种接入方式:

  1. http接入,通过访问tinyid-server来生成ID,其中tinyid-server推荐部署到多个机房的多台机器,这种方式需要考虑网络的延时性
  1. 使用tinyid-client来获取id,

优点:

  • id为本地生成(调用AtomicLong.addAndGet方法),性能大大增加
  • client对server访问变的低频,减轻了server的压力,无须担心网络延迟
  • 即便所有server挂掉,因为client预加载了号段,依然可以继续使用一段时间 

缺点:

  • 如果client机器较多频繁重启,可能会浪费较多的id(原因是什么?

 

8、百度 (Uidgenerator)

UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器。Github地址:https://github.com/baidu/uid-generator

UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于docker等虚拟化环境下实例自动重启、漂移等场景, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费。

具体算法实现:

Snowflake算法:

九种分布式ID生成算法详解

Snowflake算法描述:指定机器 & 同一时刻 & 某一并发序列,是唯一的。据此可生成一个64 bits的唯一ID(long)。默认采用上图字节分配方式:

  • sign(1bit)
  • 固定1bit符号标识,即生成的UID为正数。
  • delta seconds (28 bits)
  • 当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约8.7年
  • worker id (22 bits)
  • 机器id,最多可支持约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。
  • sequence (13 bits)
  • 每秒下的并发序列,13 bits可支持每秒8192个并发。

 

9、美团(Leaf)

Leaf由美团开发,github地址:https://github.com/Meituan-Dianping/Leaf

同时支持号段模式和Snowflake算法模式,可以切换使用。

1:Leaf-segment号段模式:

在使用数据库的方案上,做了如下改变: 

  1. 原方案每次获取ID都得读写一次数据库,造成数据库压力大。改为利用proxy server批量获取,每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力
  2. 各个业务不同的发号需求用biz_tag字段来区分,每个biz-tag的ID获取相互隔离,互不影响

号段模式的具体实现思路同TinyID一样

 

2:Leaf-snowflake雪花算法:

完全沿用snowflake方案的bit位设计,即是“1+41+10+12”的方式组装ID号。为了解决集权下workID的问题,美团的Leaf-snowflake跟百度不一样,百度是通过数据库来生成workID,而美团是通过Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID,所以Leaf是依赖ZK服务的。

特点:

  1. 弱依赖ZooKeeper:除了每次会去ZK拿数据以外,也会在本机文件系统上缓存一个workerID文件。当ZooKeeper出现问题,恰好机器出现问题需要重启时,能保证服务能够正常启动。这样做到了对ZK的弱依赖
  2. 解决时钟问题:因为这种方案依赖时间,如果机器的时钟发生了回拨,那么就会有可能生成重复的ID号,需要解决时钟回退的问题。