高并发分布式下生成全局ID的几种方法

文章目录

  • 利用全球唯一UUID生成订单号码
  • 基于数据库实现全局ID
  • 基于redis生成全局ID策略
  • 号段模式
  • 雪花算法

利用全球唯一UUID生成订单号码

**推荐理由: 简单方便,网上虽然说UUID出现了重复的事件,但是我实际测试,并没有发现问题的复现,所以他作为一个国际化的ID生成算法,还是很不错的,生成的ID的性能很好,缺点就是生成的UUID的为字符串,无法实现自增,同时比较暂内存空间
**

UUID的组成部分:当前日期+时钟序列+随机数+全局唯一的IEE机器识别号
全局唯一的IEE机器识别号:如果有网卡,从网卡MAC地址获取,没有网卡从其他方式进行获取

用法
高并发分布式下生成全局ID的几种方法_第1张图片
前面已经说过UUID的组成部分开头不是当前日期吗?为什么这里还有字母呢? 其实他是ASC码进行转换的

UUID的适用场景: 生成token的场景比较多 去掉"-"符号

基于数据库实现全局ID

**如果是2个数据库 A,B 在高并发的环境下 如果按照主键的默认的自增策略的话, 因为主键自增唯一的特性,可能会在并发的情况下,创建重复的ID与主键的特性违背,那么如何避免这个问题呢?
答案:此时我们可以给每个数据库设置不同的步长(每次增加多少), 比如A 每次增加的2 起始值为0, B 每次增加2 起始值为 1 每次增加的2 ,那么A的数据库就是 13 5 7 … B的数据是2,4,6,8 这样就不会导致两者之间的冲突问题

那么如何设置步长呢?
查看数据库的步长**

    SHOW VARIABLES LIKE 'auto_inc%'

高并发分布式下生成全局ID的几种方法_第2张图片
修改自增的步长

SET @@auto_increment_increment=10

修改起始值

SET @@auto_increment_offset=5;

基于redis生成全局ID策略

基于Redis生成生成全局id策略 因为Redis是单线的,天生保证原子性**,可以使用Redis的原子操作 INCR和INCRBY来实现
优点: 不依赖于数据库,灵活方便,且性能优于数据库。 数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点: 如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。 需要编码和配置的工作量比较大。
注意:在Redis集群情况下,同样和Redis一样需要设置不同的增长步长,同时key一定要设置有效期 可以使用Redis集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为: A:1,6,11,16,21 B:2,7,12,17,22 C:3,8,13,18,23 D:4,9,14,19,24 E:5,10,15,20,25,
但是redis 需要进行连接,所以连接redis需要占用一定的宽带
获取值得方法为**

   RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        long l = redisAtomicLong.incrementAndGet();

在生成订单号码的时候,会有一个补0的问题就说一个ID 最后一位是五位数,位数不够0 来凑,那么如何实现呢
15151515151515-00001

具体做法为

String orderId = prefix() + "-" + String.format("%1$05d", andIncrement);

其中 5 d中的 5 是指的5位,根据不同的需求可以具体修改
因为redis是单线程的为了提高效率可以做集群,但是 集群也会发生Id重复的问题,前面虽然说得是他是单线程不会有安全的问题,但是那也是对于一台机器而言.所以对于这个问题,我们也可以设置步长解决Id重复的问题

   //起始值
        redisAtomicLong.set(10);
        //设置步长
        redisAtomicLong.addAndGet(10);

以上基本上就已经解决了大部分的问题,但同事还需要设置key的有效期

  redisAtomicLong.expire(24, TimeUnit.HOURS);

号段模式

我们可以使用号段的方式来获取自增ID,号段可以理解成批量获取,比如DistributIdService从数据库获取ID时,如果能批量获取多个ID并缓存在本地的话,那样将大大提供业务应用获取ID的效率。
比如DistributIdService每次从数据库获取ID时,就获取一个号段,比如(1,1000],这个范围表示了1000个ID,业务应用在请求DistributIdService提供ID时,DistributIdService只需要在本地从1开始自增并返回即可,而不需要每次都请求数据库,一直到本地自增到1000时,也就是当前号段已经被用完时,才去数据库重新获取下一号段。

所以,我们需要对数据库表进行改动,如下

CREATE TABLE id_generator (
  id int(10) NOT NULL,
  current_max_id bigint(20) NOT NULL COMMENT '当前最大id',
  increment_step int(10) NOT NULL COMMENT '号段的长度',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这个数据库表用来记录自增步长以及当前自增ID的最大值(也就是当前已经被申请的号段的最后一个值),因为自增逻辑被移到DistributIdService中去了,所以数据库不需要这部分逻辑了。 这种方案不再强依赖数据库,就算数据库不可用,那么DistributIdService也能继续支撑一段时间。但是如果DistributIdService重启,会丢失一段ID,导致ID空洞。 为了提高

DistributIdService的高可用,需要做一个集群,业务在请求DistributIdService集群获取ID时,会随机的选择某一个DistributIdService节点进行获取,对每一个DistributIdService节点来说,数据库连接的是同一个数据库,
那么可能会产生多个DistributIdService节点同时请求数据库获取号段,那么这个时候需要利用乐观锁来进行控制,比如在数据库表中增加一个version字段,在获取号段时使用如下SQL

update id_generator set current_max_id=#{newMaxId}, version=version+1 where version = #{version}

因为newMaxId是DistributIdService中根据oldMaxId+步长算出来的,只要上面的update更新成功了就表示号段获取成功了。

为了提供数据库层的高可用,需要对数据库使用多主模式进行部署,对于每个数据库来说要保证生成的号段不重复,这就需要利用最开始的思路,再在刚刚的数据库表中增加起始值和步长,比如如果现在是两台Mysql,那么mysql1将生成号段(1,1001],自增的时候序列为1,3,4,5,7…mysql1将生成号段(2,1002],自增的时候序列为2,4,6,8,10…

在TinyId中还增加了一步来提高效率,在上面的实现中,ID自增的逻辑是在DistributIdService中实现的,而实际上可以把自增的逻辑转移到业务应用本地,这样对于业务应用来说只需要获取号段,每次自增时不再需要请求调用DistributIdService了。

雪花算法

Twitter的snowflake(雪花)算法 snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。
其核心思想是: 高位随机+毫秒数+机器码(数据中心+机器id)+10位的流水号码

Github地址: https://github.com/twitter-archive/snowflake
加粗样式
这个性能最高,本打算拉下来讲讲.没想到浏览器一直加载,剩下的就交给大家去探索吧,加油! 0.0

你可能感兴趣的:(java)