背景
在分布式系统中,经常需要对大量的数据、消息、http请求等进行唯一标识,例如:在分布式系统之间http请求需要唯一标识,调用链路分析的时候需要使用这个唯一标识。这个时候数据库自增主键已经不能满足需求,需要一个能够生成全局唯一ID的系统,这个系统需要满足以下需求:
全局唯一:不能出现重复ID。
高可用:ID生成系统是基础系统,被许多关键系统调用,一旦宕机,会造成严重影响。
经典方案介绍
1. UUID
UUID是Universally Unique Identifier的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符,UUID是16字节128位长的数字,通常以36字节的字符串表示,比如:3F2504E0-4F89-11D3-9A0C-0305E82C3301。
UUID经由一定的算法机器生成,为了保证UUID的唯一性,规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,以及从这些元素生成UUID的算法。UUID的复杂特性在保证了其唯一性的同时,意味着只能由计算机生成。
优点:
本地生成ID,不需要进行远程调用,时延低,性能高。
缺点:
UUID过长,16字节128位,通常以36长度的字符串表示,很多场景不适用,比如用UUID做数据库索引字段。
没有排序,无法保证趋势递增。
2. Flicker方案(自增长机制)
这个方案是由Flickr团队提出,设计单独的库表,单独提供产生全局ID的服务,主要思路采用了MySQL自增长ID的机制(auto_increment + replace into)
CREATE TABLE Tickets64 ( id bigint(20) unsigned NOT NULL auto_increment, stub char(1) NOT NULL default '', PRIMARY KEY (id), UNIQUE KEY stub (stub) )ENGINE=MyISAM;
#每次业务使用下列SQL读写MySQL得到ID号
REPLACE INTO Tickets64 (stub) VALUES ('a'); SELECT LAST_INSERT_ID();
replace into 跟 insert 功能类似,不同点在于:replace into 首先尝试插入数据到表中,如果发现表中已经有此行数据(根据主键或者唯-索引判断)则先删除此行数据,然后插入新的数据, 否则直接插入新数据。
为了避免单点故障,最少需要两个数据库实例,通过区分auto_increment的起始值和步长来生成奇偶数的ID。
Server1:
auto-increment-increment = 2
auto-increment-offset = 1
Server2:
auto-increment-increment = 2
auto-increment-offset = 2
优点:
充分借助数据库的自增ID机制,可靠性高,生成有序的ID。
缺点:
ID生成性能依赖单台数据库读写性能。
依赖数据库,当数据库异常时整个系统不可用。
对于依赖MySql性能问题,可用如下方案解决:
在分布式环境中我们可以部署多台,每台设置不同的初始值,并且步长为机器台数,比如部署N台,每台的初始值就为0,1,2,3…N-1,步长为N。
以上方案虽然解决了性能问题,但是也存在很大的局限性:
系统扩容困难:系统定义好步长之后,增加机器之后调整步长困难。
数据库压力大:每次获取一个ID都必须读写一次数据库。
3. 阿里-TDDL序列生成方式(取出一定数量放内存中)
TDDL是阿里的分库分表中间件,它里面包含了全局数据库ID的生成方式,主要思路:
使用数据库同步ID信息。
每次批量取一定数量的可用ID在内存中,使用完后,再请求数据库重新获取下一批可用ID,每次获取的可用ID数量由步长控制,实际业务中可根据使用速度进行配置。
每个业务可以给自己的序列起个唯一的名字,隔离各个业务系统的ID。
数据表设计:
seqName varchar(100) 序列名称,主键 cur_value bigint(20) 当前值 step int 步长,根据实际情况设置
优点:
- 相比flicker方案,大大降低数据库写压力,数据库不再是性能瓶颈。
- 相比flicker方案,生成ID性能大幅度提高,因为获取一个可用号段后在内存中直接分配,相对于每次读取数据库性能提高了几个量级。
- 不同业务不同的ID需求可以用seqName字段区分,每个seqName的ID获取相互隔离,互不影响。
缺点:
强依赖数据库,当数据库异常时整个系统不可用。
4. twitter-snowflake方案
snowflake是twitter开源的分布式ID生成系统。 Twitter每秒有数十万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统中不同机器产生的id必须不同。
这种方案生成一个64bit的数字,64bit被划分成多个段,分别表示时间戳、机器编码、序号。
ID为64bit 的long 数字,由三部分组成:
41位的时间序列(精确到毫秒,41位的长度可以使用69年)。
10位的机器标识(10位的长度最多支持部署1024个节点)。
12位的计数顺序号(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)。
优点:
时间戳在高位,自增序列在低位,整个ID是趋势递增的,按照时间有序。
性能高,每秒可生成几百万ID。
可以根据自身业务需求灵活调整bit位划分,满足不同需求。
缺点:
强依赖时钟,如果主机时间回拨,则会造成重复ID,会产生
ID虽然有序,但是不连续
在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况。
5、Redis生成ID
当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现。
可以使用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
这个,随便负载到哪个机确定好,未来很难做修改。但是3-5台服务器基本能够满足器上,都可以获得不同的ID。但是步长和初始值一定需要事先需要了。使用Redis集群也可以方式单点故障的问题。
另外,比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。
优点:
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:
1)如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。
2)需要编码和配置的工作量比较大。
Redis Incr 命令将 key 中储存的数字值增一。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
实例
redis> SET page_view 20 OK redis> INCR page_view (integer) 21 redis> GET page_view # 数字值在 Redis 中以字符串的形式保存 "21"
Redis Incrby 命令将 key 中储存的数字加上指定的增量值。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
实例
# key 存在且是数字值 redis> SET rank 50 OK redis> INCRBY rank 20 (integer) 70 redis> GET rank "70"
...
相关推荐
项目中的代码截取自mybatis-plus-3.5项目源码中分布式ID的实现,主要是内容是Mybatis-plus项目中IdentifierGenerator接口的两个实现类DefaultIdentifierGenerator和ImadcnIdentifierGenerator的源码,还有IdWorker工具...
分布式ID生成,主键生成,Java实现的snowflake算法
Vesta,uidgennator等分布式id生成方案 UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器。UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于docker等虚拟化环境...
分布式ID雪花算法的实现
用 gRPC Protobuf Go 在分布式系统中实现连续递增 ID
分布式id生成详解,各种分布式ID的实现方式,例如百度 UidGenerator 算法,美团 Leaf 算法,大家可以参考下
Golang Mysql实现的分布式ID生成服务
介绍分布式事务的定义、原则和实现原则,介绍使用Spring框架实现分布式事务的几种方式,包括使用JTA、Spring事务同步、链式事务等,并通过实战介绍其实现。除此以外还介绍了一些分布式事务相关的技术,如幂等性、...
引用步骤: 1.基于SpringBoot自动装配,引用jar包即可,坐标如下: ... <artifactId>feignextend ...[%date{yyyy-MM-dd HH:mm:ss.SSS}] [%X{traceId}] [%thread] %-5level %logger{80} %line - %msg%n</Pattern>
在大数据量的时候,会涉及分库分表,使用自增ID可能会导致ID重复,使用UUID是无序的,在创建主键索引的时候会频繁的修改索引树内的索引位置,让索引更新的效率很低等问题。索引此时就引入了雪花ID,它既能保证ID的...
d、分布式全局ID生成器,ID生成非绝对递增有序,是趋向有序,这一点如果能接受,可以直接copy使用 2、事务回滚机制说明 a、每个消费端的事务处理都由本地事务负责 b、基于下单队列消费端临时表,查询红包、...
1.这么多种分布式ID生成方式,应该选择哪种呢? 2.雪花算法底层实现原理是什么? 4、分库分表 1.当数据量大了之后,我们应该如何选择分库分表的解决方案? 2.做分库分表,是应该垂直切分还是水平切分? 带着这些...
借鉴snowflake的思想,结合各公司的业务逻辑和并发量,可以实现自己的分布式ID生成算法
6.高并发分布式全局ID生成雪花算法 7.分布式Session使用redis实现 8.分布式服务追踪与调用链Sleuth+ZipKin C.项目运营与部署环境 1.分布式设施环境,统一采用docker安装 2.使用jenkins+docker实现自动部署 3.微服务...
分布式ID生成器 VESTA-jar+源码 Vesta是一款通用的ID产生器,互联网俗称统一发号器,它具有全局唯一、粗略有序、可反解和可制造等特性,它支持三种发布模式:嵌入发布模式、中心服务器发布模式、REST发布模式,根据...
相信绝大部分开发者在刚入行的时候选择的都是数据库的自增id,因为这是一种非常简单的方式,数据库里配置下就行了。都很明显。 优点如下: 无需编码,数据库自动生成,速度快,按序存放。 数字格式,占用空间小。 ...
分布式ID生成,主键生成策略,snowflake算法,采用java实现。
We have retired the initial release of Snowflake and working on open sourcing the next version based on Twitter-server, in a form that can run anywhere without requiring Twitter's own infrastructure ...
gid是使用golang开发的生成分布式Id系统,基于数据库号段算法实现 HTTP,GRPC对外服务 表现 id从内存生成,如果(step)步长设置的足够大,qps可达到千万+ 可用 id分配依赖mysql,当mysql不可用的,如果内存上还有的...
基于java的分布式主键发生器,使用ice通讯,实现从单服务器到多层的大规模主键发生器,可以保持ID的唯一性.