关于java:特好用8种分布式ID生成方法

3次阅读

共计 4744 个字符,预计需要花费 12 分钟才能阅读完成。

前言

业务量小于 500W 或数据容量小于 2G 的时候独自一个 mysql 即可提供服务,再大点的时候就进行读写拆散也能够应酬过去。但当主从同步也扛不住的时候就须要分表分库了,但分库分表后须要有一个惟一 ID 来标识一条数据,且这个惟一 ID 还必须有规定,能辅助咱们解决分库分表的一些问题。

数据库的自增 ID 显然不能满足需要;特地一点的如订单、优惠券也都须要有惟一 ID 做标识。此时一个可能生成全局惟一 ID 的零碎是十分必要的,那么这个全局惟一 ID 就叫分布式 ID。

分布式 ID 需满足那些条件

  • 全局惟一:根本要求就是必须保障 ID 是全局性惟一的。
  • 高性能:高可用低延时,ID 生成响应要快。
  • 高可用:有限靠近于 100% 的可用性
  • 好接入:遵循拿来主义准则,在零碎设计和实现上要尽可能的简略
  • 趋势递增:最好趋势递增,这个要求就得看具体业务场景了,个别不严格要求

UUID

UUID 是指 Universally Unique Identifier,翻译为中文是通用惟一识别码,UUID 的目标是让分布式系统中的所有元素都能有惟一的辨认信息。模式为 8-4-4-4-12,总共有 36 个字符。用起来非常简单

public static void main(String[] args) {String uuid = UUID.randomUUID().toString().replaceAll("-","");
  System.out.println(uuid);
}

输入后果 99a7d0925b294a53b2f4db9d5a3fb798,但 UUID 却并不适用于理论的业务需要。订单号用 UUID 这样的字符串没有丝毫的意义,看不出和订单相干的有用信息;而对于数据库来说用作业务主键 ID,它不仅是太长还是字符串,存储性能差查问也很耗时,所以不举荐用作分布式 ID。

长处: 生成足够简略,本地生成无网络耗费,具备唯一性

毛病: 无序的字符串,不具备趋势自增个性,没有具体的业务含意。如此长的字符串当 MySQL 主键并非理智抉择。

数据库自增 ID

基于数据库的 auto_increment 自增 ID 齐全能够充当分布式 ID,具体实现:须要一个独自的 MySQL 实例用来生成 ID,建表构造如下:

CREATE TABLE SoWhat_ID (`id` bigint(20) unsigned NOT NULL auto_increment, 
    `value` char(10) NOT NULL default '',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
) ENGINE=MyISAM;

当咱们须要一个 ID 的时候,向表中插入一条记录返回主键 ID,但这种形式有一个比拟致命的毛病,访问量激增时 MySQL 自身就是零碎的瓶颈,用它来实现分布式服务危险比拟大,不举荐!

长处: 实现简略,ID 枯燥自增,数值类型查问速度快

毛病: DB 单点存在宕机危险,无奈扛住高并发场景

数据库集群模式

后面说了单点数据库形式不可取,那对上边的形式做一些高可用优化,换成主从模式集群。胆怯一个主节点挂掉没法用,那就做双主模式集群,也就是两个 Mysql 实例都能独自的生产自增 ID。那这样还会有个问题,两个 MySQL 实例的自增 ID 都从 1 开始,会生成反复的 ID 怎么办?解决方案:设置起始值和自增步长

MySQL_1 配置:

set @@auto_increment_offset = 1;     -- 起始值
set @@auto_increment_increment = 2;  -- 步长

MySQL_2 配置:

set @@auto_increment_offset = 2;     -- 起始值
set @@auto_increment_increment = 2;  -- 步长 

这样两个 MySQL 实例的自增 ID 别离就是:

1、3、5、7、9 
2、4、6、8、10

然而如果两个还是无奈满足咋办,须要进行扩容呢?

减少第三台 MySQL 实例须要人工批改一、二两台 MySQL 实例的起始值和步长,把第三台机器的 ID 起始生成地位设定在比现有最大自增 ID 的地位远一些,但必须在一、二两台 MySQL 实例 ID 还没有增长到第三台 MySQL 实例的起始 ID 值的时候,否则自增 ID 就要呈现反复了,必要时可能还须要停机批改。

长处: 解决 DB 单点问题

毛病: 不利于后续扩容,而且实际上单个数据库本身压力还是大,仍旧无奈满足高并发场景。

Redis 模式

Redis 也同样能够实现,原理就是 Redis 是单线程的,因而咱们能够利用 redis 的 incr 命令实现 ID 的原子性自增。

127.0.0.1:6379> set seq_id 1     // 初始化自增 ID 为 1
OK
127.0.0.1:6379> incr seq_id      // 减少 1,并返回递增后的数值
(integer) 2

实现思路,大抵和下面讲数据库的原理相似。用 redis 实现须要留神一点,要思考到 redis 长久化的问题。redis 有两种长久化形式 RDB 和 AOF。

号段模式

号段模式是当下分布式 ID 生成器的支流实现形式之一,号段模式能够了解为从数据库(当然这边存储层也可用其余的,比方 redis、Mongdb 等)批量的获取自增 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`)
)
  • max_id:以后最大的可用 id
  • step:代表号段的长度
  • biz_type:代表不同业务类型
  • version:是一个乐观锁,每次都更新 version,保障并发时数据的正确性

等这批号段 ID 用完,再次向数据库申请新号段,对 max_id 字段做一次 update 操作

update max_id= max_id + step

update 胜利则阐明新号段获取胜利,新的号段范畴是(max_id ,max_id +step]。

因为多业务端可能同时操作,所以采纳版本号 version 乐观锁形式更新。

长处: 这种分布式 ID 生成形式不强依赖于数据库,不会频繁的拜访数据库,对数据库的压力小很多

毛病: 如果遇到了双十一或者秒杀相似的流动还是会对数据库有比拟高的拜访,且如果再申请新号段的时候,遇到数据库不可用时,ID 生成也会呈现问题。

雪花算法模式

SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:应用一个 64 bit 的 long 型的数字作为全局惟一 id。在分布式系统中的利用非常宽泛,且 ID 引入了工夫戳,为什么叫雪花算法呢?家喻户晓世界上没有一对雷同的雪花。雪花算法基本上放弃自增的。

这 64 个 bit 中,其中 1 个 bit 是不必的,而后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。举例如上图:

  1. 第一个局部是 1 个 bit:0,这个是无意义的。因为二进制里第一个 bit 位如果是 1,那么都是正数,然而咱们生成的 id 都是负数,所以第一个 bit 对立都是 0。
  2. 第二个局部是 41 个 bit:示意的是工夫戳。单位是毫秒。41 bit 能够示意的数字多达 2^41 – 1,也就是能够标识 2 ^ 41 – 1 个毫秒值,换算成年就是示意 69 年的工夫。
  3. 第三个局部是 10 个 bit:5 个 bit 代表机房,意思就是最多代表 2^5 个机房(32 个机房)。另 5 个 bit 代表机器 id,意思就是每个机房里能够代表 2^5 个机器(32 台机器)
  4. 第四个局部是 12 个 bit:示意自增序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号。12bit 能够代表的最大正整数是 2^12-1=4096,也就是说能够用这个 12bit 代表的数字来辨别同一个毫秒内的 4096 个不同的 id。

总结的来说,就是用一个 64 bit 位来设置不同的标记位,辨别每一个 id。

SnowFlake 算法的实现代码 https://github.com/souyunku/S…

长处

  1. 高性能,本地通过位运算生成,效率快
  2. 高可用,本地生成无节点宕机状况产生
  3. 容量大,每秒中能生成数百万的自增 ID
  4. ID 自增:存入数据库中,索引效率高

毛病

  1. 依赖与零碎工夫的一致性,如果零碎工夫被回调,或者扭转可能会造成 id 抵触或者反复。

理论中咱们的机房并没有那么多,咱们能够改良改算法,将 10bit 的机器 id 优化成业务 ID 或者业务零碎实例数

前面讲讲业内一些公司基于以上几种最根本的 ID 生成形式,进行的局部优化。

百度 uid-generator

uid-generator 是由百度技术部开发,基于 Snowflake 算法实现,与原始 snowflake 算法不同在于,uid-generator 反对自定义工夫戳、workId 和 序列号等各局部的位数。提供了两种形式,看上面剖析

DefaultUidGenerator

DefaultUidGenerator 采纳用户自定义 workId 的生成策略。大抵原理如下:
须要新增一个 WORKER_NODE 表,当利用启动时会向数据库表中插入一条数据,插入胜利后返回的自增 ID 就是 workId。
对于时钟回拨的问题,DefaultUidGenerator 采纳了比较简单粗犷的形式,间接抛出谬误

由上图可知,UidGenerator 的工夫局部只有 28 位,这就意味着 UidGenerator 默认只能接受 8.5 年(2^28-1/86400/365)。当然,依据你业务的需要,UidGenerator 能够适当调整 delta seconds、worker node id 和 sequence 占用位数。

CachedUidGenerator

CachedUidGenerator 是 UidGenerator 的重要改良实现,workerId 的获取形式也和 DefaultUidGenerator 是一样。外围优化的两个点是:

  1. 利用 RingBuffer 数据结构事后生成若干个分布式 ID 并保留。
  2. 通过工夫值递增失去工夫值(lastSecond.incrementAndGet()),而不是 System.currentTimeMillis()这种形式,保障了工夫回拨的问题。

留神的是,CachedUidGenerator 分布式 ID 中的工夫信息可能并不是这个 ID 真正产生的工夫点。

github 地址:https://github.com/baidu/uid-…

美团(Leaf)

Leaf 反对号段模式和雪花算法模式,能够切换应用。

号段模式

思维和咱们下面讲的统一,它存储采纳数据库。

雪花算法模式

Leaf 的 snowflake 模式依赖于 ZooKeeper,不同于原始 snowflake 算法是在 workId 的生成上。Leaf 中 workId 是基于 ZooKeeper 的程序 Id 来生成的,每个利用在应用 Leaf-snowflake 时,启动时都会都在 Zookeeper 中生成一个程序 Id,相当于一台机器对应一个程序节点,也就是一个 workId。对于工夫回拨的问题,美团采取的策略是期待 5ms 后从新获取,如果发现工夫还未追上,那么进行告警。

github 地址:https://github.com/Meituan-Di…

参考文献:https://mp.weixin.qq.com/s/Sr…
http://www.360doc.com/content…
公众号:sowhat1412

正文完
 0