`
uule
  • 浏览: 6305478 次
  • 性别: Icon_minigender_1
  • 来自: 一片神奇的土地
社区版块
存档分类
最新评论

分布式ID的实现方式

阅读更多

分布式ID方案有哪些以及各自的优劣势

分布式ID生成器解决方案-CSDN

分布式ID解决方案-头条

分布式系统唯一ID生成方案汇总-代码

 

背景

在分布式系统中,经常需要对大量的数据、消息、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"

 

 

...

  • 大小: 46.7 KB
  • 大小: 61.4 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics