如何在分布式场景下生成全局惟一 ID ?

在分布式系统中,有一些场景须要使用全局惟一 ID ,能够和业务场景有关,好比支付流水号,也能够和业务场景无关,好比分库分表后须要有一个全局惟一 ID,或者用做事务版本号、分布式链路追踪等等,好的全局惟一 ID 须要具有这些特色:node

  • 全局惟一:这是最基本的要求,不能重复;
  • 递增:有些特殊场景是必须递增的,好比事务版本号,后面生成的 ID 必定要大于前面的 ID ;有些场景递增比不递增要好,由于递增有利于数据库索引的性能;
  • 高可用:若是是生成惟一 ID 的系统或服务,那么必定会有大量的调用,那么保证其高可用就很是关键了;
  • 信息安全:若是 ID 是连续的,那么很容易被恶意操做或泄密,好比订单号是连续的,那么很容易就被看出来一天的单量大概是多少;
  • 另外考虑到存储压力,ID 固然是越短越好。

那么分布式场景下有哪些生成惟一 ID 的方案呢?算法

利用数据库生成

先说最容易理解的方案,利用数据库的自增加序列生成:数据库生成惟一主键,并经过服务提供给其余系统;若是是小型系统,数据总量和并发量都不是很大的状况下,这种方案足够支撑。数据库

若是每次生成一个 ID 可能会对数据库有压力,能够考虑一次性生成 N 个 ID 放入缓存中,若是缓存中的 ID 被取光,再经过数据库生成下一批 ID 。缓存

  • 优势: 理解起来最容易,实现起来也最简单。
  • 缺点: 也很是明显了,每种数据库的实现不一样,若是数据库须要迁移的话比较麻烦;最大的问题是性能问题,并发量到必定级别的时候这个方法估计会很难知足性能需求;另外经过数据库自增生成的 ID 携带的信息太少,只能起到一个标识的做用,同时自增 ID 也是连续的。

利用其余组件/软件/中间件生成

利用 Redis / MongoDB / zookeeper 生成:Redis 利用 incr 和 increby ;MongoDB 的 ObjectId;zk 经过 znode 数据版本;均可以生成全局的惟一标识码。安全

咱们用 MongoDB 的 ObjectId 来举例:网络

{"_id": ObjectId("5d47ca7528021724ac19f745")}

MongoDB 的 ObjectId 共占 12 个字节,其中:架构

  • 3.2 以前的版本(包括 3.2): 4 字节时间戳 + 3 字节机器标识符 + 2 字节进程 ID + 3字节随机计数器
  • 3.2 以后版本: 4 字节时间戳 + 5 字节随机值 + 3 字节递增计数器

不论是老版本仍是新版本,MongoDB 的 ObjectId 至少均可以保证集群内的惟一,咱们能够搭建一个全局惟一 ID 生成的服务,利用 MongoDB 生成 ObjectId 并对外提供服务(MongoDB 的各语言驱动都实现了 ObjectId 的生成算法)。并发

  • 优势: 性能高于数据库;可使用集群部署;ID 内自带一些含义,好比时间戳;
  • 缺点: 和数据库同样,须要引入对应的组件/软件,增长了系统的复杂度;最关键的是,这两种方案都意味着生成全局惟一 ID 的系统(服务),会成为一个单点,在软件架构中,单独就意味着风险;若是这个服务出现问题,那么全部依赖于这个服务的系统都会崩溃掉。

UUID

这个是分布式架构中,生成惟一标识码最经常使用的算法。为了保证 UUID 的惟一性,生成因素包括了MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素;UUID 有多个版本,每一个版本的算法不一样,应用范围也不一样:框架

  • Version 1: 基于时间的 UUID,是经过时间戳 + 随机数 + MAC地址获得;若是应用直接局域网内使用,可使用 IP 地址替代 MAC 地址;高度惟一(MAC 地址泄漏,也是一个安全问题)。
  • Version 2: DCE 安全的 UUID,把 Version 1 中的时间戳前 4 位置换为 POSIX 的 UID 或 GID ;高度惟一。
  • Version 3: 基于名字的 UUID(MD5),经过计算名字和名字空间的 MD5 散列值获得;必定范围内惟一。
  • Version 4: 随机 UUID,根据随机数或伪随机数生成 UUID;有必定几率重复。
  • Version 5: 基于名字的UUID(SHA1),和 Version 3 相似,只是散列值计算使用SHA1算法;必定范围内惟一。
public class CreateUUID {
 public static void main(String[] args) {
  String uuid = UUID.randomUUID().toString();
  System.out.println("uuid : " + uuid);
​
  uuid = UUID.randomUUID().toString().replaceAll("-","");
  System.out.println("uuid : " + uuid);
 }
}
  • 优势: 本地生成,没有网络消耗,不须要第三方组件(也就没有单点的风险),生成比较简单,性能好。
  • 缺点: 长度长,不利于存储,而且没有排序,相对来讲还会影响性能(好比 MySQL 的 InnoDB 引擎,若是 UUID 做为数据库主键,其无序性会致使数据位置频繁变更)。

Snowflake

若是但愿 ID 能够本地生成,可是又不要和 UUID 那样无序,能够考虑使用 Snowflake 算法(Twitter开源)。dom

SnowFlake 算法生成 ID 是一个 64 bit 的整数,包括:

  • 1 bit : 不使用,固定是 0 ;
  • 41 bit : 时间戳(毫秒),数值范围是:0 至 2的41次方 - 1 ;转换成年的话,大约是 69 年;
  • 10 bit : 机器 ID ;5 位机房 ID + 5 位机器 ID ;(服务集群数量比较小的时候,能够手动配置,服务规模大的话,能够采用第三方组件进行自动配置,好比美团的 Leaf-snowflake,就是经过 Zookeeper 的持久顺序节点作为机器 ID)
  • 12 bit : 序列号,用来记录同一个毫秒内生成的不一样 ID 。

在Java中,SnowFlake 算法生成的 ID 正好能够用 long 来进行存储。

  • 优势: 本地生成,没有网络消耗,不须要第三方组件(也就没有单点的风险),必定范围内惟一(基本能够知足大部分场景),性能好,按时间戳递增(趋势递增);
  • 缺点: 依赖于机器时钟,同一台机器若是把时间回拨,生成的 ID 就会有重复的风险。

image

此外,还有不少优秀的互联网公司也提供了惟一 ID 生成的方案或框架,好比美团开源的 Leaf ,百度开源的 UidGenerator 等等。

@Resource
private UidGenerator uidGenerator;
​
@Test
public void testSerialGenerate() {
    // Generate UID
    long uid = uidGenerator.getUID();
    System.out.println(uidGenerator.parseUID(uid));
}
会点代码的大叔 | 文【原创】

敬请关注会点代码的大叔

相关文章
相关标签/搜索