分布式ID

  • 分布式
  • 面试题
  • java
  • 分布式id
大约 6 分钟

一、为什么需要分布式ID

在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。

  • 如在美团点评的金融、支付、餐饮、酒店等业务场景
  • 猫眼电影等产品的系统中数据日渐增长,对数据分库分表后需要有一个唯一ID来表示一条数据或者消息。
  • 特别一点的如订单、骑手、优惠劵也都需要一个唯一ID做为标识。

此时一个能生成唯一ID的系统是非常必要的。

二、分布式ID的要求

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

ID生成系统的可用性要求

  • 高可用:发一个获取分布式ID的请求,服务器就要保证99.999%的情况下给我创建一个唯一分布式ID
  • 低延迟:发一个获取分布式ID的请求,服务器就要快,极速
  • 高QPS:假如并发一口气10万个创建分布式ID请求同时过来,服务器需要顶得住且成功创建10万个分布式ID

三、分布式ID有哪些生成方案?

一口气说出9种分布式ID生成方式open in new window

1、UUID:UUID.randomUUID().toString().replaceAll("-","")

缺点:长度过长,存储冗余,且无序不可读,查询效率低

2、数据库自增ID

可以使用两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。但是会有性能瓶颈,而且不利于后续扩容,实际上单个数据库自身压力还是大,依旧无法满足高并发场景。

3、基于数据库的号段模式

号段模式可以理解为从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存。表结构如下:

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]

由于多业务端可能同时操作,所以采用版本号 version乐观锁方式更新,这种 分布式ID生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。

4、Redis分布式ID生成器

Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所以能保证生成的 ID 肯定是唯一有序的。

redis实现需要注意一点,要考虑到redis持久化的问题。redis有两种持久化方式 RDBAOF

  • RDB会定时打一个快照进行持久化,假如连续自增但 redis没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。
  • AOF会对每条写命令进行持久化,即使 Redis挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致 Redis重启恢复的数据时间过长。

5、雪花算法

6、百度uid-generator

7、美团leaf

8、滴滴TinyId

四、雪花算法

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

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

但是雪花算法由于依赖机器时钟,如果服务器的时间发生了错乱或者回拨,这就直接影响到生成的 ID,有很大概率生成重复的 ID一定会打破递增属性

上次编辑于: