分布式ID

记录下关于分布式ID相关知识点

分布式ID基本要求

  1. 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
  2. 趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
  3. 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
  4. 信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。
  5. 分布式id里面最好包含时间戳,这样就能够在开发中快速了解这个分布式id的生成时间。

上述的基本要求中,3和4点其实是互斥的,要根据实际业务要求来进行判断。

生成方案

其实主要有两种方式生成,一种是根据某种算法组合生成,另外一种是依赖数据库、缓存中间件、ZK等协助生成。下面分别介绍下几种常见的生成方式。

snowflake

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,由于ID大都为正数,所以是0。

snowflake-photo

时间回拨问题

由于snowflake是包含时间戳组成的,如果出现了部署服务器时间回退的问题,则可能会产生重复的id。一般情况下生产环境的机器是不会随意调整时间的,但是也可能会出现特殊情况,需要对此做一些应对。

每个时间段下,并不一定会在该时间段内生成达到上限,所以一定会在该短时间内有大量的id被浪费。这些被浪费的资源就可以利用起来。

比如在内存中保存每个时间戳的生成id,在发生时间回拨的时候,取上一次时间最后记录的id对序列部分进行+1使用。

也可以利用扩展位,因为snowflake算法的极限是每毫秒的每一个节点生成4059个id值,也就是说每毫秒的极限是生成023*4059=4 152 357个id值,这对于中小公司来说根本就是浪费,所以我们可以把这里的位数给减少一些,就比如把12位的序列号变成10位,留两位给时间回拨,这样就很容易的解决了时间回拨问题,如果发生时间回拨,那么就直接给当前id加1。

UUID

通用唯一标识符(UUID)是一个128位的用于在计算机系统中以识别信息的数目。通常在Microsoft创建的软件中也使用术语全球唯一标识符(GUID)。

在其规范的文本表示形式中,UUID 的16 个八位字节表示为32个十六进制(基数16)数字,以5个组显示,由连字符分隔,格式为8-4-4-4-12,共36个字符(32个字母数字字符和4个连字符)。例如:

1
2
123e4567-e89b-12d3-a456-426655440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

4位M和1到3位N字段编码UUID本身的格式。数字的4位M是UUID版本,数字的1至3个最高有效位对NUUID变体进行编码。(请参见下文。)在示例中,M为1,N为a(10xx 2),这意味着这是版本1,版本1的UUID。即基于时间的DCE/RFC 4122 UUID。

规范的8-4-4-4-12格式字符串基于UUID的16个字节的记录布局:

UUID记录布局

名称 长度(字节) 长度(十六进制数字) 内容
time_low 4 8 给出时间的低32位的整数
time_mid 2 4 给出时间的中间16位的整数
time_hi_and_version 2 4 最高有效位为4位“版本”,然后是时间的高12位
clock_seq_hi_and_res clock_seq_low 2 4 最高有效位为1至3位“变量”,随后为13至15位时钟序列
节点 6 12 48位节点ID

JDK提供的工具类

可以直接使用java.util.UUID来生成UUID

1
2
3
public static void main(String[] args) {
System.out.println(UUID.randomUUID().toString().replace("-", "").toLowerCase());
}

UUID是否适合做分布式ID

如果需求是只保证唯一性,那么UUID也是可以使用的,但是按照上面的分布式id的要求, UUID其实是不能做成分布式id的,原因如下:

  1. 首先分布式id一般都会作为主键,但是安装mysql官方推荐主键要尽量越短越好,UUID每一个都很长,所以不是很推荐
  2. 既然分布式id是主键,然后主键是包含索引的,然后mysql的索引是通过b+树来实现的,每一次新的UUID数据的插入,为了查询的优化,都会对索引底层的b+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键地城的b+树进行很大的修改,这一点很不好
  3. 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。

Redis集群生成

数据库递增

参考资料

投食入口