分布式id解决方案

分布式id解决方案文章目录 1 分布式 id 实现方案 1 1 uuid1 2 数据库主键自增 1 3Redis 自增 1 4 号段模式 1 5 雪花算法 snowflake 1 5 1 百度 uid generator 1 5 2 美团 Leaf 所谓 id 就是能够用作唯一标识的记号 在我们日常的设计中 对于单体架构 我们一般使用数据库的自增 Id 来作为表的主键 但是对于一个分布式系统 就会出现 ID 冲突 所以对于分布式 ID 而言 也需要具备分布式系统的特点 高并发 高可用 高性能等特点 1 分布式 id 实现方案我们先看看常见的分布 id 解

所谓id就是能够用作唯一标识的记号。
在我们日常的设计中,对于单体架构,我们一般使用数据库的自增Id来作为表的主键,但是对于一个分布式系统,就会出现ID冲突,所以对于分布式ID而言,也需要具备分布式系统的特点:高并发,高可用,高性能等特点。

1.分布式id实现方案

我们先看看常见的分布id解决方案以及各自特点的对比

  • 1.UUID 这种方案复杂度最低,但是会影响存储空间和性能
  • 2.利用单机数据库的自增主键,作为分布式ID的生成器,复杂度适中,ID长度较UUID更短,但是受到单机数据库性能的限制,并发量大的时候,该方案也不是最优方案。
  • 3.利用redis,zookeeper的特性来生成id,如:redis的自增命令,zookeeper的顺序节点,这种方案和单机数据库(mysql)性能相比,性能会有所提高,可以适当选用。
  • 4.雪花算法:一切问题如果能直接用算法解决,那是最合适的,利用雪花算法可以生成分布式Id,其底层原理就是通过某台机器在某一毫秒内对某一个数字自增,这种方案也能保证分布式架构中的系统id唯一,但是只能保证趋势递增。

下面我们具体看看这些方案的对比:

描述 优点 缺点
UUID UUID是通用唯一标识码的缩写,其目的是上分布式系统中的所有元素都有唯一的辨识信息,而不需要通过中央控制器来指定唯一标识。 1. 降低全局节点的压力,使得主键生成速度更快;2. 生成的主键全局唯一;3. 跨服务器合并数据方便 1. UUID占用16个字符,空间占用较多;2. 不是递增有序的数字,数据写入IO随机性很大,且索引效率下降
数据库主键自增 MySQL数据库设置主键且主键自动增长 1. INT和BIGINT类型占用空间较小;2. 主键自动增长,IO写入连续性好;3. 数字类型查询速度优于字符串 1. 并发性能不高,受限于数据库性能;2. 分库分表,需要改造,复杂;3. 自增:数据量泄露
Redis自增 Redis计数器,原子性自增 使用内存,并发性能好 1. 数据丢失;2. 自增:数据量泄露
号段模式 依赖于数据库,但是区别于数据库主键自增的模式 较自增id性能有显著的提升 受限于数据库性能;
雪花算法(snowflake) 大名鼎鼎的雪花算法,分布式ID的经典解决方案 1. 不依赖外部组件;2. 性能好 时钟回拨

上面提到了五种解决方案,目前流行的分布式ID解决方案有两种:「号段模式」和「雪花算法」。

1.1.uuid

这种方式很简单,在每次需要新增数据的时候,先生成一个uuid

String id=UUID.randomUUID().toString(); 

1.2 数据库主键自增

CREATE TABLE SEQID.SEQUENCE_ID ( id bigint(20) unsigned NOT NULL auto_increment, stub char(10) NOT NULL default '', PRIMARY KEY (id), UNIQUE KEY stub (stub) ); 

在每次新增的时候,先向该表新增一条数据,然后获取返回新增的主键作为要插入的主键Id,我们可以使用下面的语句生成并获取到一个自增ID

begin; replace into SEQUENCE_ID (stub) VALUES ('anyword'); select last_insert_id(); commit; 

可以看到数据库里面永远只有一条数据。

这种生成分布式ID的机制,需要一个单独的Mysql实例,虽然可行,但是基于性能与可靠性来考虑的话都不够,业务系统每次需要一个ID时,都需要请求数据库获取,性能低,并且如果此数据库实例下线了,那么将影响所有的业务系统。

1.3 Redis自增

在这里插入图片描述

使用redis的效率是非常高的,但是要考虑持久化的问题。Redis支持RDBAOF两种持久化的方式。

  • RDB持久化相当于定时打一个快照进行持久化,如果打完快照后,连续自增了几次,还没来得及做下一次快照持久化,这个时候Redis挂掉了,重启Redis后会出现ID重复。
  • AOF持久化相当于对每条写命令进行持久化,如果Redis挂掉了,不会出现ID重复的现象,但是会由于incr命令过多,导致重启恢复数据时间过长。

1.4 号段模式

我们可以使用号段的方式来获取自增ID,号段可以理解成批量获取,比如DistributIdService从数据库获取ID时,如果能批量获取多个ID并缓存在本地的话,那样将大大提供业务应用获取ID的效率。

CREATE TABLE id_generator ( id int(10) NOT NULL, current_max_id bigint(20) NOT NULL COMMENT '当前最大id', increment_step int(10) NOT NULL COMMENT '号段的长度', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

这个数据库表用来记录自增步长以及当前自增ID的最大值(也就是当前已经被申请的号段的最后一个值),因为自增逻辑被移到DistributIdService中去了,所以数据库不需要这部分逻辑了。

这种方案不再强依赖数据库,就算数据库不可用,那么DistributIdService也能继续支撑一段时间。但是如果DistributIdService重启,会丢失一段ID,导致ID空洞。

UPDATE id_generator SET current_max_id = #{newMaxId}, version=version+1 where version = #{version}  

因为newMaxId是DistributIdService中根据oldMaxId+步长算出来的,只要上面的update更新成功了就表示号段获取成功了。

为了提供数据库层的高可用,需要对数据库使用多主模式进行部署,对于每个数据库来说要保证生成的号段不重复,这就需要利用最开始的思路,再在刚刚的数据库表中增加起始值和步长,比如如果现在是两台Mysql,那么 mysql1将生成号段(1,1001],自增的时候序列为1,3,5,7… mysql2将生成号段(2,1002],自增的时候序列为2,4,6,8,10…

在TinyId中还增加了一步来提高效率,在上面的实现中,ID自增的逻辑是在DistributIdService中实现的,而实际上可以把自增的逻辑转移到业务应用本地,这样对于业务应用来说只需要获取号段,每次自增时不再需要请求调用DistributIdService了。

1.5 雪花算法(snowflake)

snowflake是twitter开源的分布式ID生成算法,是一种算法,所以它和上面的三种生成分布式ID机制不太一样,它不依赖数据库。

  • 符号位为0,0表示正数,ID为正数,所以固定为0。
  • 时间戳位不用多说,用来存放时间戳,单位是ms,时间戳部分占41bit,这个是毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的ID从更小值开始。
  • 工作机器id位用来存放机器的id,通常分为5个区域位+5个服务器标识位。这里比较灵活,比如,可以使用前5位作为数据中心机房标识,后5位作为单机房机器标识,可以部署1024个节点。
  • 序号位是自增。

雪花算法能存放多少数据?

  • 时间范围:2^41 / (1000L * 60 * 60 * 24 * 365) = 69年
  • 工作进程范围:2^10 = 1024
  • 序列号范围:2^12 = 4096,表示1ms可以生成4096个ID。

根据这个算法的逻辑,只需要将这个算法用Java语言实现出来,封装为一个工具方法,那么各个业务应用可以直接使用该工具方法来获取分布式ID,只需保证每个业务应用有自己的工作机器id即可,而不需要单独去搭建一个获取分布式ID的应用。下面是推特版的Snowflake算法:

public class SnowFlake { 
      / * 起始的时间戳 */ private final static long START_STMP = 31L; / * 每一部分占用的位数 */ private final static long SEQUENCE_BIT = 12; //序列号占用的位数 private final static long MACHINE_BIT = 5; //机器标识占用的位数 private final static long DATACENTER_BIT = 5;//数据中心占用的位数 / * 每一部分的最大值 */ private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); / * 每一部分向左的位移 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId; //数据中心 private long machineId; //机器标识 private long sequence = 0L; //序列号 private long lastStmp = -1L;//上一次时间戳 public SnowFlake(long datacenterId, long machineId) { 
      if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { 
      throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { 
      throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); } this.datacenterId = datacenterId; this.machineId = machineId; } / * 产生下一个ID * * @return */ public synchronized long nextId() { 
      long currStmp = getNewstmp(); if (currStmp < lastStmp) { 
      throw new RuntimeException("Clock moved backwards. Refusing to generate id"); } if (currStmp == lastStmp) { 
      //相同毫秒内,序列号自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列数已经达到最大 if (sequence == 0L) { 
      currStmp = getNextMill(); } } else { 
      //不同毫秒内,序列号置为0 sequence = 0L; } lastStmp = currStmp; return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分 | datacenterId << DATACENTER_LEFT //数据中心部分 | machineId << MACHINE_LEFT //机器标识部分 | sequence; //序列号部分 } private long getNextMill() { 
      long mill = getNewstmp(); while (mill <= lastStmp) { 
      mill = getNewstmp(); } return mill; } private long getNewstmp() { 
      return System.currentTimeMillis(); } public static void main(String[] args) { 
      SnowFlake snowFlake = new SnowFlake(2, 3); for (int i = 0; i < (1 << 12); i++) { 
      System.out.println(snowFlake.nextId()); } } } 

1.5.1 百度(uid-generator)

uid-generator使用的就是snowflake,只是在生产机器id,也叫做workId时有所不同。

uid-generator中的workId是由uid-generator自动生成的,并且考虑到了应用部署在docker上的情况,在uid-generator中用户可以自己去定义workId的生成策略,默认提供的策略是:应用启动时由数据库分配。

说的简单一点就是:应用在启动时会往数据库表(uid-generator需要新增一个WORKER_NODE表)中去插入一条数据,数据插入成功后返回的该数据对应的自增唯一id就是该机器的workId,而数据由host,port组成。

对于uid-generator中的workId,占用了22个bit位,时间占用了28个bit位,序列化占用了13个bit位,需要注意的是,和原始的snowflake不太一样,时间的单位是秒,而不是毫秒,workId也不一样,同一个应用每重启一次就会消费一个workId。

1.5.2 美团(Leaf)

  • 全局唯一,绝对不会出现重复的ID,且ID整体趋势递增。
  • 高可用,服务完全基于分布式架构,即使MySQL宕机,也能容忍一段时间的数据库不可用。
  • 高并发低延时,在CentOS 4C8G的虚拟机上,远程调用QPS可达5W+,TP99在1ms内。
  • 接入简单,直接通过公司RPC服务或者HTTP调用即可接入。




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

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

(0)
上一篇 2026年3月19日 下午4:26
下一篇 2026年3月19日 下午4:27


相关推荐

  • JS前端去掉emoji表情和Java后台处理emoji表情方法

    莫非定律 : 任何事情都没表面看去来那么简单!emoji表情在项目中使用,因为其特殊的编码格式,经常导致在网络传输、编解码、以及数据入库中带来一些问题!下面简单介绍使用Js和java处理移除emoji表情!Emoji 表情的相关基础内容介绍,请读者自行在网上查找资料!前端JS代码//emojob编码集,因为emoji表情在不断地增加,下面的reg可能在未来会不能满足…

    2022年2月27日
    252
  • python+OpenCV图像处理(八)边缘检测

    python+OpenCV图像处理(八)边缘检测边缘检测边缘检测是图像处理和计算机视觉中的基本问题 边缘检测的目的是标识数字图像中亮度变化明显的点 图像属性中的显著变化通常反映了属性的重要事件和变化 边缘检测是特征提取中的一个研究领域 图像边缘检测大幅度地减少了数据量 并且剔除了可以认为不相关的信息 保留了图像重要的结构属性 有许多方法用于边缘检测 它们的绝大部分可以划分为两类 基于查找一类和基于零穿越的一类 基于查找的方法通过寻找图像

    2026年3月19日
    2
  • MySQL数据删除语句

    MySQL数据删除语句MySQL 数据删除语句在 MySQL 中 可以使用 DELETE 语句来删除表的一行或者多行数据 基础语法删除单个表中的数据使用 DELETE 语句从单个表中删除数据 语法格式为 DELETEFROM 表名 WHERE 子句 ORDERBY 子句 LIMIT 子句 语法说明如下 表名 指定要删除数据的表名 ORDERBY 子句 可选项 表示删除时 表中各行将按照子句中指定的顺序进行删除 WHERE 子句 可选项 表示为删除操作限定删除条件 表名 表名

    2026年3月18日
    2
  • 简单三步走!电脑接入 DeepSeek R1 超简教程

    简单三步走!电脑接入 DeepSeek R1 超简教程

    2026年3月15日
    2
  • Kimi Claw – Kimi 云端 OpenClaw,开箱即用

    Kimi Claw – Kimi 云端 OpenClaw,开箱即用

    2026年3月15日
    2
  • 长轮询的使用实现_长轮询和短轮询

    长轮询的使用实现_长轮询和短轮询轮询(Polling):是指不管服务器端有没有更新,客户端(通常是指浏览器)都定时的发送请求进行查询,轮询的结果可能是服务器端有新的更新过来,也可能什么也没有,只是返回个空的信息。不管结果如何,客户端处理完后到下一个定时时间点将继续下一轮的轮询。长轮询(LongPolling):长轮询的服务其客户端是不做轮询的,客户端在发起一次请求后立即挂起,一直到服务器端有更新的时候,服务器才会主动推送信息到…

    2025年6月17日
    3

发表回复

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

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