spring boot / cloud (十六) 分布式ID生成服务
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-发号流程示意

相关源码可在本文末尾的配套代码仓库中获得,工程是 : 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,以及如何做到高可用的设计和思路,环境大家提出意见和建议
代码仓库 (博客配套代码)
想获得最快更新,请关注公众号

spring boot / cloud (十六) 分布式ID生成服务的更多相关文章
- spring boot / cloud (十五) 分布式调度中心进阶
spring boot / cloud (十五) 分布式调度中心进阶 在<spring boot / cloud (十) 使用quartz搭建调度中心>这篇文章中介绍了如何在spring ...
- spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法
spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法 前言 本篇接着<spring boot / cloud ...
- spring boot / cloud (十八) 使用docker快速搭建本地环境
spring boot / cloud (十八) 使用docker快速搭建本地环境 在平时的开发中工作中,环境的搭建其实一直都是一个很麻烦的事情 特别是现在,系统越来越复杂,所需要连接的一些中间件也越 ...
- spring boot / cloud (十九) 并发消费消息,如何保证入库的数据是最新的?
spring boot / cloud (十九) 并发消费消息,如何保证入库的数据是最新的? 消息中间件在解决异步处理,模块间解耦和,和高流量场景的削峰,等情况下有着很广泛的应用 . 本文将跟大家一起 ...
- spring boot / cloud (十二) 异常统一处理进阶
spring boot / cloud (十二) 异常统一处理进阶 前言 在spring boot / cloud (二) 规范响应格式以及统一异常处理这篇博客中已经提到了使用@ExceptionHa ...
- 分布式ID生成服务,真的有必要搞一个
目录 阐述背景 Leaf snowflake 模式介绍 Leaf segment 模式介绍 Leaf 改造支持RPC 阐述背景 不吹嘘,不夸张,项目中用到ID生成的场景确实挺多.比如业务要做幂等的时候 ...
- Leaf:美团分布式ID生成服务开源
Leaf是美团基础研发平台推出的一个分布式ID生成服务,名字取自德国哲学家.数学家莱布尼茨的一句话:“There are no two identical leaves in the world.”L ...
- 9种分布式ID生成之 美团(Leaf)实战
整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 更多优选 一口气说出 9种 分布式ID生成方式,面试官有点懵了 ...
- spring boot / cloud (六) 开启CORS跨域访问
spring boot / cloud (六) 开启CORS跨域访问 前言 什么是CORS? Cross-origin resource sharing(跨域资源共享),是一个W3C标准,它允许你向一 ...
随机推荐
- 用css3的cursor:zoom-in/zoom-out实现微博看图片放大镜效果
1.前言 CSS3的出现解决了很多让人头疼的问题,至少我想很多童鞋都这样认为.css3的cursor属性大家用的应该是非常的多的,我想用的比较多的像cursor:pointer;cursor:help ...
- echarts_部分图表配置_dataZoom精确控制显示数据数量
echarts为我们提供了dataZoom组件,当数据过多时就有了它的用武之地,业务场景:数据返回100调可是为了前端显示效果默认只显示20条,其他数据由dataZoom控制显示隐藏: functio ...
- 机器学习实战 logistic回归 python代码
# -*- coding: utf-8 -*- """ Created on Sun Aug 06 15:57:18 2017 @author: mdz "&q ...
- grunt 的安装和简单使用
安装Grunt命令行 npm install -g grunt-cli 创建package.json,如果有package.json包,可以直接npm install加载依赖组件 npm init 安 ...
- 微信开发中遇到的问题,关于cdnmidimgurl 图片获取
访问了微信服务器 返回的数据: [{MsgId=3349810483943419227, FromUserName=@@855c3ada0e9c387cfbcff93e9a1a639f024bcfd1 ...
- POJ 1182 食物链 -- 解题报告
食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 70529 Accepted: 20875 Description ...
- D重叠面积
Description zjahstu是个很厚道的ACMer,O(∩_∩)O~..特为大家准备水题一道.. 题目很简单,两个矩形,告诉你矩形1,矩形2的面积和他们的总面积,请你求两矩形重叠部分的面积. ...
- python基础===Python 代码优化常见技巧
Python 代码优化常见技巧 代码优化能够让程序运行更快,它是在不改变程序运行结果的情况下使得程序的运行效率更高,根据 80/20 原则,实现程序的重构.优化.扩展以及文档相关的事情通常需要消耗 8 ...
- Markdown简明教程
一.Markdown到底是什么? Markdown是一种轻量级的标记语言,它使用很少量的符号控制文字的样式和排版,简单易学,使你更专注于文字. 二.Markdown的使用 接下来让我们一起来看下Mar ...
- 使用proxool连接池配置教程
proxool连接池的优点: 1.透明度:透明地将连接池添加到现有的JDBC驱动程序. 2.开源:我们的许可证允许您灵活地将其用于商业和其他开源产品. 3.标准:符合J2SE API,使您有信心开发标 ...