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

九种分布式ID生成算法详解一 分布式 ID 简介 1 什么是分布式 ID 在我们业务数据量不大的时候 单库单表完全可以支撑现有业务 数据再大一点搞个 MySQL 主从同步读写分离也能对付 但随着数据日渐增长 主从同步也扛不住了 就需要对数据库进行分库分表 但分库分表后需要有一个唯一 ID 来标识一条数据 数据库的自增 ID 显然不能满足需求 例如我们的订单 需要有一个全局唯一标识的订单号 这个订单号就是分布式 ID2 分布式 ID 需要满足那些条件 全局唯一 必须保证 ID 是全局性唯一的 基本要求 高可用 高性能 高可

一、分布式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)模式

九种分布式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号,需要解决时钟回退的问题。

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/214300.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午4:35
下一篇 2026年3月18日 下午4:36


相关推荐

  • Js之Navigator对象「建议收藏」

    Js之Navigator对象「建议收藏」敬请关注博客,后期不断更新优质博文,谢谢Window对象的navigator属性引用的是包含浏览器厂商和版本信息的Navigator对象。Navigator对象的命名是为了纪念Netscape之后NavigatorBU览器译注2,不过所有其他的浏览器也支持它(IE还支持clientlnformation属性,它作为navigator的厂商中立同义词。遗憾的是,其他浏览器并不支持这一更直观…

    2025年10月26日
    4
  • 一个刷博客的小工具

    一个刷博客的小工具在基本做完了机房机房收费系统后,今天用了将近一天的时候,做了一个刷博客的小工具。由于本人自知水平有限,所以软件也很小,很简陋。在接下的时间,我会再善一下,然后贴出来与大家分享。      下面说一下做我时的感受吧!      在做之前,我可以说是一片茫然

    2022年6月22日
    33
  • linux中kill命令详解_linux kill函数

    linux中kill命令详解_linux kill函数linuxkill命令详解一、命令格式:kill[参数][进程号]二、命令功能:发送指定的信号到相应进程。不指定型号将发送SIGTERM(15)终止指定进程。如果任无法终止该程序可用“-KILL”参数,其发送的信号为SIGKILL(9),将强制结束进程,使用ps命令或者jobs命令可以查看进程号。root用户将影响用户的进程,非root用户只能影响自己的进程。三、命令参数:-l信号,若果不加信号的编号参数,则使用“-l”参数会列出全部的信号名称-a当处理当前进程时,不

    2025年7月1日
    5
  • 高德地图实现多点标注marker和动态信息窗体[通俗易懂]

    高德地图实现多点标注marker和动态信息窗体[通俗易懂]先说一下项目对地图的需求:在后台新增地图管理模块,要求,每一辆车都在地图上有标注,而且点击标注时要显示出车辆的相关信息,比如车牌和车辆的当前状态。下图就是实现的效果。当然从高德地图api也能查看到这一块,比如多点标注,简单信息窗体,这些都有api,可以先看官网的,然后,再看我这篇文章,怎么融合一起,记住数据全部来自后台数据库,这样才能保证一切数据都是动态的,可实时更新的。首

    2022年5月11日
    78
  • 程序员一般都如何接私活,我也想接,有什么可行的方法吗?[通俗易懂]

    程序员一般都如何接私活,我也想接,有什么可行的方法吗?[通俗易懂]二哥,你好,我想知道一般程序猿都如何接私活,我也想接,能告诉我一些方法吗?

    2022年9月29日
    5
  • 代理模式的使用总结

    代理模式的使用总结目录一、代理模式二、静态代理(一)静态代理(二)静态代理简单实现三、动态代理(一)动态代理(二)动态代理简单实现四、动态代理原理分析五、InvocationHandler接口和Proxy类详解六、JDK动态代理和CGLIB动态代理代码示例比较与总结(一)定义创建用户管理接口(二)用户管理实现类,实现用户管理接口(被代理的实现类)(三)采用JDK代…

    2022年4月27日
    34

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号