分布式环境下如何保证 ID 的唯一性

分布式环境下如何保证 ID 的唯一性

作者: 鸭血粉丝 2021-07-02 06:54:43

开发

前端

分布式 首先说下我们为什么需要分布式 ID,以及分布式 ID 是用来解决什么问题的。当我们的项目还处于单体架构的时候,我们使用数据库的自增 ID 就可以解决很多数据标识问题。

[[408786]]

本文转载自微信公众号「Java极客技术」,作者鸭血粉丝。转载本文请联系Java极客技术公众号。

前言

首先说下我们为什么需要分布式 ID,以及分布式 ID 是用来解决什么问题的。当我们的项目还处于单体架构的时候,我们使用数据库的自增 ID 就可以解决很多数据标识问题。但是随着我们的业务发展我们的架构就会逐渐演变成分布式架构,那么这个时候再使用数据的自增 ID 就不行了,因为一个业务的数据可能会放在好几个数据库里面,此时我们就需要一个分布式 ID 用来标识一条数据,因此我们需要一个分布式 ID 的生成服务。那么分布式 ID 的服务有什么要求和挑战呢?

要求

全局唯一:既然是用来标识数据唯一的,那么一个分布式 ID 肯定要是全局唯一的,在同一业务下的每个服务下面都是一致的,不会变的,这是一个基本的要求;

全局递增:递增这个也很好理解,我们要保证生成的 ID 是依次递增的,因为很多时候 ID 是给人看的,如果说不具备递增性,就缺乏了很多的可读性;

信息安全:分布式 ID 的安全性也很重要,因为我们提到生成的 ID 是递增的,这就有可能会给竞争对手知道我们的 ID 的生成频率,这种在电商等场景会有很大的问题,但是这个往往跟全局递增有点冲突;

高可用性:分布式 ID 的生成服务必须是高可用,毕竟一旦不能生成 ID,后续的所有服务都无法继续使用;

常见的分布式 ID 实现

在当下的互联网当中,根据业务场景以及需求的不同,对于分布式 ID 的实现有如下几种实现方式:

  1. UUID;
  2. Redis;
  3. 变形的数据库自增 ID;
  4. 推特雪花算法
  5. 美团的 Leaf——雪花算法的变形;

UUID

写 Java 的朋友对 UUID 肯定不陌生,7dbb9f04-d15e-4c88-b74b-72a35e0d7580 这是一个标准的 UUID,虽然都说 UUID 是全球唯一,具备我们前面提到的要求中的第一点,但是很显然不具备全局递增,这种分布式 ID 可读性很差,如果说只是用来记录日志或者不需要人去理解的场景是可以用,但是不适合我们这里说的业务数据的唯一标识。而且这种无序的 UUID 如果作为主键会很严重影响性能。

Redis

Redis 有个 incr 的命令,这个命令是能保证原子递增的,在某种程度上也是可以生成全局 ID,不过使用 Redis 有两个问题:

  1. 不美观,虽然说我们需要的是一个全局 ID,但是 incr 命令是从 1 开始的整型,所以会导致全局 ID 的长度不一致,虽然说也可以用来标识唯一业务数据,但是某些场景也缺少可读性,因为不携带日期信息;
  2. 依赖 Redis 的高可用,因为 Redis 是基于内存的,为了保证 ID 的不丢失所以需要对 Redis 进行持久化,但是关于 Redis 的两种持久化的方式各有优缺点,详细的可以参考公众号之前的文章 面试官:请说下 Redis 是如何保证在宕机后数据不丢失的;

数据库自增 ID

前面我们提到单个数据库在分布式环境下已经没办法使用自增 ID 了,因为每个 MySQL 的实例自增 ID 都是从 1 开始,并且步长都按照 1依次递增,这种情况下我们很容易想到是不是可以考虑给每个数据库设置不同的步长。如果我们设置了不同的步长,这样就可以保证每个数据库实例都可以生成 ID,并且不会重复。虽然简单的系统可以这样用,但是也有几个问题:

依赖数据库 DB,在分布式环境下,如果过多的依赖数据库是有风险的,无法支持高并发的情况,特别是对于一些电商交易的场景,每秒几十万的 QPS,数据库是扛不住的;

不同数据库实例的数据不能直接关联上,需要额外的存储,才能把数据串起来,增加业务复杂度;

推特的雪花算法—— snowflake

snowflake 算法是推特开源的分布式 ID 生成算法,这个算法提供了一个标准的思路,很多公司都参考这个算法做了自己的实现,比较有名的是美团的 Leaf。这里我们就着重看下雪花算法是怎么实现的。

感兴趣的可以去参考文章 https://tech.meituan.com/2017/04/21/mt-leaf.html 看下美团的 leaf 的实现原理。

雪花算法的思想是化整为零,将分布式 ID 的生成分散到每个机房和机器上,采用一个 64 位 long 类型的的结构来表示一个 ID,64 的结构如下所示,第一位符号位 0,然后是 41 位的时间戳,接下来的 10 位是机房加机器,最后的 12 位是序列号。

上面这个结构是雪花算法的基本结构,不同公司根据自身的业务会进行相应的调整,有的可以采用 32 位或者其他位数,而且时间戳的位数也可以根据实际情况进行调整,10 位的 workerID 有机房的公司可以用机房加机器组成,没有机房的公司可以直接用机器来组成,序列位也可以根据情况适当调整。

我们可以简单算一下,41 位的时间位是2 ^ 41 / (365 * 24 * 3600 * 1000) = 69 年,每个机器每毫秒可以生成 2 ^ 12 = 4096 个 ID。

那是不是说我们这个代码只能运行 69 年呢?其实不是的,这里服务在启动的时候会设置一个初始值,这里的时间戳是用机器的时间减去初始值的差值。那 SnowFlake 算法有什么优缺点呢?

  1. 因为有时间戳,所以满足自增的要求,同时也具备一定的可读性;
  2. 化整为零每个服务在各自的机器上可以直接生成唯一 ID,只需要配置好机房和机器编号即可;
  3. 长度可以根据业务自行调整;
  4. 缺点是依赖机器的时钟,如果说机器的时钟有问题,会导致生成的 ID 可能会重复,这个需要控制;

结合上面的原理,我们可以通过 Java 代码来具体实现,代码如下:

  1. public class SnowFlakeUtil { 
  2.  
  3.     //初始时间戳 
  4.     private final static long START_TIMESTAMP = 1624796691000L; 
  5.     //数据中心占用的位数 
  6.     private final static long DATA_CENTER_BIT = 5; 
  7.     //机器标识占用的位数 
  8.     private final static long MACHINE_BIT = 5; 
  9.     //序列号占用的位数 
  10.     private final static long SEQUENCE_BIT = 12; 
  11.  
  12.  
  13.     /** 
  14.      * 每一部分的最大值 
  15.      */ 
  16.     private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT); 
  17.     private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT); 
  18.     private final static long MAX_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT); 
  19.  
  20.     /** 
  21.      * 每一部分向左的位移 
  22.      */ 
  23.     private final static long MACHINE_LEFT = SEQUENCE_BIT; 
  24.     private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; 
  25.     private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT; 
  26.  
  27.     private final long idc; 
  28.     private final long serverId; 
  29.     private long sequence = 0L; 
  30.     private long lastTimeStamp = -1L; 
  31.  
  32.     private long getNextMill() { 
  33.         long mill = System.currentTimeMillis(); 
  34.         while (mill <= lastTimeStamp) { 
  35.             mill = System.currentTimeMillis(); 
  36.         } 
  37.         return mill; 
  38.     } 
  39.  
  40.     /** 
  41.      * 根据指定的数据中心ID和机器标志ID生成指定的序列号 
  42.      * 
  43.      * @param idc      数据中心ID 
  44.      * @param serverId 机器标志ID 
  45.      */ 
  46.     public SnowFlakeUtil(long idc, long serverId) { 
  47.         if (idc > MAX_DATA_CENTER_NUM || idc < 0) { 
  48.             throw new IllegalArgumentException("IDC 数据中心编号非法!"); 
  49.         } 
  50.         if (serverId > MAX_MACHINE_NUM || serverId < 0) { 
  51.             throw new IllegalArgumentException("serverId 机器编号非法!"); 
  52.         } 
  53.         this.idc = idc; 
  54.         this.serverId = serverId; 
  55.     } 
  56.  
  57.     /** 
  58.      * 生成下一个 ID 
  59.      * 
  60.      * @return 
  61.      */ 
  62.     public synchronized long genNextId() { 
  63.         long currTimeStamp = System.currentTimeMillis(); 
  64.         if (currTimeStamp < lastTimeStamp) { 
  65.             throw new RuntimeException("Clock moved backwards.  Refusing to generate id"); 
  66.         } 
  67.         if (currTimeStamp == lastTimeStamp) { 
  68.             //相同毫秒内,序列号自增 
  69.             sequence = (sequence + 1) & MAX_SEQUENCE; 
  70.             //同一毫秒的序列数已经达到最大 
  71.             if (sequence == 0L) { 
  72.                 currTimeStamp = getNextMill(); 
  73.             } 
  74.         } else { 
  75.             //不同毫秒内,序列号置为0 
  76.             sequence = 0L; 
  77.         } 
  78.         lastTimeStamp = currTimeStamp; 
  79.         return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | idc << DATA_CENTER_LEFT | serverId << MACHINE_LEFT | sequence
  80.     } 
  81.  
  82.     public static void main(String[] args) { 
  83.         SnowFlakeUtil snowFlake = new SnowFlakeUtil(4, 3); 
  84.         for (int i = 0; i < 100; i++) { 
  85.             System.out.println(snowFlake.genNextId()); 
  86.         } 
  87.     } 

参考

知乎·一口气说出9种分布式ID生成方式,面试官有点懵了 

Leaf——美团点评分布式ID生成系统

 

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/252482.html<

(0)
运维的头像运维
上一篇2025-04-29 11:15
下一篇 2025-04-29 11:17

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

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