文档章节

spring boot / cloud (十六) 分布式ID生成服务

wangkang80
 wangkang80
发布于 2017/09/09 13:51
字数 2127
阅读 1571
收藏 43

spring boot / cloud (十六) 分布式ID生成服务

在几乎所有的分布式系统或者采用了分库/分表设计的系统中,几乎都会需要生成数据的唯一标识ID的需求,

常规做法,是使用数据库中的自动增长列来做系统主键,但是这样的做法无法保证ID全局唯一.

那么一个分布式ID生成器应该满足那些需求呢 :

  • 全局唯一性

  • 趋势递增

  • 能够融入分库基因

本文将基于snowflake的算法来进行以下的讨论,当然,分布式ID的生成方案有很多,

不过在本文并不会分散开来讨论/比对,因为网上相关的文章实在太多,如果有需要了解的同学,请自行百度.

同时,也不会讨论snowflake算法,同样也是因为网上相关的文章实在太多,如果有需要了解的同学,请自行百度.

本文期望解决什么问题?

先看两段代码:

public void id() {
     Map<Long, Long> map = new HashMap<>();
     int maxCount = 100;
     IdWorker idWorker = new IdWorker(1, 1);
     for (int i = 0; i < maxCount; i++) {
         long id = idWorker.nextId();
         map.put(id, id);
     }
     log.info("{} , {}", maxCount, map.size());
 }

输出为 : 100 , 100

public void id() {
     Map<Long, Long> map = new HashMap<>();
     int maxCount = 100;
     for (int i = 0; i < maxCount; i++) {
         IdWorker idWorker = new IdWorker(1, 1);
         long id = idWorker.nextId();
         map.put(id, id);
     }
     log.info("{} , {}", maxCount, map.size());
 }

输出为 : 100 , 10

这两段代码的区别,相信大家一眼就能看出,但是那为什么会出现这样的情况呢?

了解snowflake的同学也都知道,这个算法是基于时间的,如下组成 :

0 | 时间(41位) | 数据中心ID(5位) | 机器ID(5位) | 序号(12位)

而生成ID的算法逻辑,简单点说,在相同数据中心ID机器ID的情况下,如果时间的毫秒数是一致的,那么就通过递增序列号来保证ID不重复.

也就是说在1毫秒内最大生成的ID个数是二进制12bit的最大值,也就是4096(0-4095)个

那么如果序列号超过了这个最大值,则会将程序阻塞到下一毫秒,然后序列号归零,继续生成ID.

好知道了生成ID的逻辑后,上面两个程序判断的现象也就不难解释了.

程序一 : 没有重复,是因为在整个循环中,ID生成器只实例化过一次,在循环的过程中,能正常的递增序列号,所以不会有重复的ID出现

程序二 : 有重复,是因为ID生成器是在循环中循环实例化的,每次生成ID的时候序列号都是0,但是程序执行很快,得到的时间毫秒数又是一样的,那么,就必然会有重复值了.

所以从以上的程序片段和分析中可以得出一个结论 : 要想snowflake生成全局唯一的ID,那么ID生成器必须也是全局单例的

那申明一个全局静态的ID生成器不就行了?

两个点要主注意一下 :

  • 分布式系统下全局静态变量也是多份的,因为系统可能运行在不同的JVM下,并不能保证变量的全局单例

  • 前面提到了在同一毫秒下,最多只能生成4096个ID,对于那些并发量很大是系统来说,显然是不够的, 那么这个时候就是通过datacenterId和workerId来做区分,这两个ID,分别是5bit,共10bit,最大值是1024(0-1023)个, 在这种情况下,snowflake一毫秒理论上最大能够生成的ID数量是约42W个,这是一个非常大的基数了,理论上能够满足绝大多数系统的并发量

所以得出一个结论 : snowflake可以通过datacenterId和workerId来区分ID的归属(可以是业务线,可以是机房,等等,按需定义)来达到更大的ID生成数量

那么有那些方法来分配atacenterId和workerId呢?

  • 写死 : 正如上面说的一样,单机部署,然后写死两个值

  • 读配置文件 : 将值放在配置中心,应用启动的时候读取,然后初始化

  • 动态分配 : 本文主旨

所以本文主要讨论的是如何动态分配snowflake的datacenterId和workerId,以及如何做到高可用

所以大家先看一下架构图 :

分布式ID-逻辑架构示意

分布式ID-逻辑架构示意

分布式ID-发号流程示意

分布式ID-发号流程示意

相关源码可在本文末尾的配套代码仓库中获得,工程是 : udf-starter-id

架构设计

构建独立的ID生成服务,提供如下服务:

#生成分布式ID(按时间戳区分datacenterId和workerId)
/service/id

#生成分布式ID(按dwId[0-1023])
/service/id/{dwId}

#生成分布式ID(按datacenterId[0-31]和workerId[0-31])
/service/id/{datacenterId}/{workerId}

#批量生成分布式ID(按时间戳区分datacenterId和workerId)
/service/id/batch/{count}

#批量生成分布式ID(按dwId[0-1023])
/service/id/batch/{dwId}/{count}

#批量生成分布式ID(按datacenterId[0-31]和workerId[0-31])
/service/id/batch/{datacenterId}/{workerId}/{count}

融入分库基因

在提供出来的rest服务中,提供了datacenterId和workerId的参数(dwId就是两者的融合,10bit),

总共预留了10个bit的空余来支持分库分表,最大支持1024个节点.

反解析分布式ID

snowflake生成的ID是可以被反解析的,这样更进一步的支持了分库的相关炒作,相关实现如下 :

 Id reverseId = new Id();
reverseId.setSequence((id) & ~(-1L << 12)); // sequence
reverseId.setDwId((id >> (12)) & ~(-1L << (10))); // dwId
reverseId.setWorkerId((id >> 12) & ~(-1L << 5)); // workerId
reverseId.setDatacenterId((id >> 17) & ~(-1L << 5)); // datacenterId
reverseId.setTimestamp((id >> 22) + TWEPOCH); // timestamp
return reverseId;

集群部署 和 懒实例化ID生成器

本方案是可以支持ID生成服务有多个实例,最多1024个,能并且能保证每个实例内,相同datacenterId和workerId的ID生成器只有一个,做到全局单例.

主要是通过redis原子锁的来实现的.详情可看上面的流程图,主要分为本地ID生成跨实例ID生成两种模式 :

本地生成

这种情况比较简单,就是生成ID的请求刚刚落到ID生成器所在的实例上,然后就可以直接拿到ID生成器,然后生成ID.

跨实例ID生成

这种情况简单点说就是,比如你要生成3-3的ID,这个ID生成器在实例A上,但是负载均衡器将请求发到实例B上去了,

这个时候实例B上并没有对应的ID生成器,这个时候,就会从缓存中拿到对应的缓存值,拿到用用这个ID生成器的HOST和PORT,

然后在做一个RMS请求,调用远程的rest服务,生成ID,然后返回

高可用 和 故障转移

上面提到了,ID生成器现在是全网单例的了,那么其中一个节点有故障,挂掉了怎么办呢?

跨实例ID生成的场景下,会有RMS请求失败的情况,远程节点有可能会故障,这个时候,一旦RMS请求失败,则会触发故障转移,

具体操作就是将redis中的对应缓存删除掉,然后走一个实例化ID生成器的流程,这个时候,当前处理请求的节点就会将故障节点拥有的ID生成器转移过来,转为本地生成模式,从而做到的故障转移

性能

如果是本地ID生成的话,那基本没有性能损耗,直接操作本地变量.

跨实例ID生成的情况会多出来一个RMS请求的耗时,但是一次ID生成的请求最多触发一次RMS请求,消耗是可控的

在有节点故障的时候,触发故障转移会额外的产生一次ID实例化的流程,会造成轻微波动,但紧当前的这一次请求,下次的请求就会转为本地ID生成的模式

结束

今天跟大家分享了如何动态分配snowflake的datacenterId和workerId,以及如何做到高可用的设计和思路,环境大家提出意见和建议

代码仓库 (博客配套代码)


想获得最快更新,请关注公众号

想获得最快更新,请关注公众号

© 著作权归作者所有

共有 人打赏支持
wangkang80
粉丝 361
博文 22
码字总数 34117
作品 3
浦东
高级程序员
私信 提问
加载中

评论(2)

wangkang80
wangkang80

引用来自“冷冷gg”的评论

赞~!
回赞!
冷冷gg
冷冷gg
赞~!
【小马哥】Spring Cloud系列讲座

这里为大家推荐一个不错的Spring Cloud系列讲座,讲师介绍如下: 小马哥,阿里巴巴技术专家,从事十余年Java EE 开发,国内微服务技术讲师。目前主要负责微服务技术推广、架构设计、基础设施...

杜琪
2018/03/02
0
0
【小马哥】Spring Boot系列讲座

这里为大家推荐一个不错的Spring Boot系列讲座,讲师介绍如下: 小马哥,阿里巴巴技术专家,从事十余年Java EE 开发,国内微服务技术讲师。目前主要负责微服务技术推广、架构设计、基础设施、...

杜琪
2018/03/02
0
0
Spring Boot 介绍说明---来源于网络,日常学习笔记

Spring Boot是Spring旗下众多的子项目之一,其理念是约定优于配置,它通过实现了自动配置(大多数用户平时习惯设置的配置作为默认配置)的功能来为用户快速构建出标准化的应用。Spring Boot...

舒文joven
2018/08/28
0
0
热门技术从零开始学Spring Cloud视频教程发布

使用技术 (1)spring boot,使用版本:1.5.8 (2)spring cloud ,使用版本:Dalston.SR4 (3)Netflix Eureka (4)Netflix Ribbon (5)Feign (6)Netflix Hystrix (7)Spring Clou Con...

小红牛
2018/08/04
0
0
恒宇少年/spring-boot-chapter

简书整套文档以及源码解析 专题 专题名称 专题描述 001 Spring Boot 核心技术 讲解SpringBoot一些企业级层面的核心组件 002 Spring Cloud 核心技术 对Spring Cloud核心技术全面讲解 003 Quer...

恒宇少年
2018/04/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

[git/tower]SSL certificate problem: Invalid certificate chain

fatal: unable to access 'https://xxx@130.51.23.250/baseline/mobile-framework/login-service.git/': SSL certificate problem: Invalid certificate chain 解决: git config --global ......

Danni3
35分钟前
1
0
ADI推出AD9528 JESD204B时钟和SYSREF发生器

1:根据ADI官网上对9361的介绍,其中还提到了与9361相配套的电源,时钟,LNA,PA等等功能部分需要的芯片,具体网页:https://www.analog.com/en/products/ad9361.html 2:MATLAB Filter Design...

whoisliang
48分钟前
2
0
Java springcloud B2B2C o2o多用户商城 springcloud架构-docker-feign配置(五)

简介 上一节我们讨论了怎么用feign声明式调用cloud的生产者,这节我们讨论一下feign配置,通过编写配置类,我们可以自定义feign的日志级别,日志扫描目录,可以通过feign调用服务在eureka上的...

sccspuercode
54分钟前
5
0
长连接的心跳及重连设计

前言 说道“心跳”这个词大家都不陌生,当然不是指男女之间的心跳,而是和长连接相关的。 顾名思义就是证明是否还活着的依据。 什么场景下需要心跳呢? 目前我们接触到的大多是一些基于长连接...

crossoverJie
55分钟前
10
0
OSChina 周三乱弹 —— 风扇写着先生请自爱

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @蚂蚁哈哈哈 :分享陈奕迅的单曲《落花流水》 《落花流水》- 陈奕迅 手机党少年们想听歌,请使劲儿戳(这里) @车谷 :我发现每天上班都好困 ...

小小编辑
今天
1K
16

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部