三分钟带你入门分布式ID生成方案

804 阅读6分钟

三分钟带你入门分布式ID方案

概述

在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。比如MySQL一般都是采用分库分表的架构,那么我们就需要保证所有数据的ID全局唯一,而不仅仅是在单个MySQL数据库表中唯一。此时一个能生成唯一ID的方案是非常必要的。而此时对于生成的全局ID,我们要考虑方案的几个方面:

  1. 全局唯一
  2. 递增性:由于数据是存储在MySQL上的,MySQL索引以B+树的形式组织,递增的ID可以提高MySQL的性能
  3. 信息安全:外界是否能通过ID判断出系统的一些信息,防止通过ID进行一些恶意的操作
  4. 性能:在如今高并发的环境下,我们需要考虑生成ID的性能

而目前业界生成分布式唯一ID的方案大概有以下几种

  • UUID
  • 数据库发号
    • 主键自增发号
    • 分段发号
  • 随机字符串/随机数
  • Snowflake算法

UUID

最常见的ID生成方案,简单的来说,UUID可以让服务器在不需要任何外界依赖的情况下,基于当前时间、硬件标识等等信息生成的唯一ID。一般表现为16字节的字符串。

优点:

  1. 无任何依赖,无需中心化的服务器
  2. 实现简单
  3. 生成的UUID没有任何可用信息,保护系统信息安全

缺点:

  1. 生成的UUID没有任何可用信息,可读性差
  2. 占用空间太多
  3. UUID字符串,不递增,不适合作为数据库主键,会影响数据库性能

场景:通常可以作为一些临时性唯一标识,例如用户登陆后,生成一个UUID作为登录的SessionID,作为key存储在Redis中,Value是用户相关的信息。

数据库发号

我们可以通过单点数据库发号来实现

根据主键自增发号

直接通过主键自增连续发号,有单机和多机两种方式。

单机数据库主键自增

通过操作数据库中的一张表,插入一条数据后或者该数据的主键ID,用主键ID作为全局的唯一ID

优点:

  1. 实现简单,方便接入,只依赖于数据库
  2. 完全单调递增,有利于数据库性能的提高

缺点:

  1. 性能较低,由于发号依赖于数据库插入数据,所以并发比较低
  2. 易出现单点故障,一旦单机数据库发生宕机,就没法生成ID,系统就无法使用
  3. ID连续,容易泄露信息,比如恶意刷API,比如通过ID推测出系统数据量级

场景:适用于并发量不高的业务

多数据库主键自增

对于多数据库的主键自增,我们要保证所有数据库都不重复,那么就可以给不同数据库设置不同的步长,可以让不同数据库生成的ID不同。

比如说有4台机器,那么就可以设置步长为4

  1. 1 、5、9、13、17
  2. 2、6、10、14、18
  3. 3、7、11、15、19
  4. 4、8、12、16、20

然后业务层通过Hash轮询不同的数据库来获取生成的ID,这样就通过多台数据库完成了发号操作。

在MySQL中设置步长的命令为

alter table <table name> auto_increment= 4;

优点:

  1. 相比单台数据库来说,性能更高,因为可以多台数据库同时发号

缺点:

  1. 实现比较复杂,依赖多台数据库
  2. 不方便扩容

场景:在没有分库分表的框架以前,业务上的分库分表就是使用这种方案来实现的。

分段发号

简单来说,就是用一个数据库表充当发号器,但是并不是用主键自增,而是用一个单独的字段。

字段意义
tag用于区分每种Id应用的业务
max_id记录当前已生成的最大ID
step每次可以获取Id的数量
description描述信息
update_time更新时间

每次使用下面这条语句从数据库获取step数量的id,并且更新max_id的值,将step数量的ID存储在内存中,供业务方通过HTTP,RPC,Client等方式来获取。此处由于多线程并发,所以存在内存中的ID应保证其操作具有线程安全性,比如在Java中应该使用原子类AtomicLong。

UPDATE leaf_alloc SET max_id = max_id + step WHERE tag = #{tag}

该方法也是美团的开源项目Leaf所采用的方式之一。

优点:

  1. 效率较高,生成的效率取决于Step的大小

缺点:

  1. 单点故障问题依旧存在
  2. ID连续,容易出现信息安全问题

场景:适用于系统仅有单台数据库但并发量较高的情况。

Snowflake算法

Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。而 Java中64bit的整数是Long类型,所以在 Java 中 SnowFlake 算法生成的 ID 就是 long 来存储的。

  • 第1位占用1bit,其值始终是0,可看做是符号位不使用。
  • 第2位开始的41位是时间戳,41-bit位可表示2^41个数,每个数代表毫秒,那么雪花算法可用的时间年限是(1L<<41)/(1000L360024*365)=69 年的时间。
  • 中间的10-bit位可表示机器数,即2^10 = 1024台机器,但是一般情况下我们不会部署这么台机器。如果我们对IDC(互联网数据中心)有需求,还可以将 10-bit 分 5-bit 给 IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,具体的划分可以根据自身需求定义。
  • 最后12-bit位是自增序列,可表示2^12 = 4096个数。

这样的划分之后相当于在一毫秒一个数据中心的一台机器上可产生4096个有序的不重复的ID。但是我们 IDC 和机器数肯定不止一个,所以毫秒内能生成的有序ID数是翻倍的。

优点:

  1. 性能高
  2. ID不连续,保证了信息安全
  3. ID趋势递增,保证了数据库的性能

缺点:

  1. 一般类Snowflake算法会引入注册中心,增加了系统的复杂性
  2. 存在系统时钟回拨导致重复ID的问题,可以通过注册中心解决,保证全局唯一时钟

总结

分布式ID的生成在如今分布式环境下越来越重要了,读者也可以根据文章的几种方式,自己实现一些ID生成的Demo