一、为什么需要分布式ID
1、跨机房部署
如果数据库是跨机房部署,分布式ID是必须的,不然后续做数据分析和统计、跨机房路由会踩大坑。
2、海量数据
如果数据量可能会超出数据库自增ID类型最大值, 分布式ID也是必然面对的。
二、分布式ID的需求有哪些
先看下功能性需求
1、全局唯一
即不管是哪个机房生成的,全局必须唯一,不能和其它机房产生的值冲突
2、单调递增
保证下一个ID一定大于上一个ID
3、具有一定的安全性
像订单号这种场景,不能让人一看最新的ID就知道你每天的订单量了
非功能性要求:
1、高可用
这个一个基础的功能,如果挂掉,会影响很多业务
2、高吞吐量
上面的功能性需求看每家公司自己的业务场景。
三、常用算法有
1、snowflake(雪花)算法
生成一个64bit的数字,数字被划分成多个段:时间戳、机器编码、序号。
优点:
缺点:
2、基于数据库
一般基于数据库,充分利用MySQL自增ID的机制。
3、UUID
UUID是Universally Unique Identifier的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符,UUID是16字节128位长的数字,通常以36字节的字符串表示。
优点:
缺点:
这种方案一般用的比较少,除非不用存储在数据库中。
四、实现方案
上面讲了大概的理论,我们看下目前比较著名的实现方案。
4.1、美团的Leaf-Segment
实现了Leaf-segment和Leaf-snowflake方案。
4.1.1 Leaf-segment方案
以MySQL举例,利用给字段设置auto_increment和auto_increment_offset来保证ID自增,每次业务使用下列SQL读写MySQL得到ID号。
表结构如下:
Create Table Tickets64 (
id bigint(20) unsigned not null auto_increment,
stub char(1),
Primary KEY(id),
UNIQUE KEY ix_stub(stub)
)Engine = InnoDB;
如果业务比较多,可以使用多张表。
刚开始使用如下:
begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;
这个方案的缺点如下:
1)、每次都要操作1次数据库,性能不高。
2)强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。
经过改进,方案如下:
数据库设计如下
+-------------+--------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+-------------------+-----------------------------+
| biz_tag | varchar(128) | NO | PRI | | |
| max_id | bigint(20) | NO | | 1 | |
| step | int(11) | NO | | NULL | |
| desc | varchar(256) | YES | | NULL | |
| update_time | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------------+--------------+------+-----+-------------------+-----------------------------+
重要字段说明:biz_tag用来区分业务,max_id表示该biz_tag目前所被分配的ID号段的最大值,step表示每次分配的号段长度。
部署架构
1、增加一层proxy
服务不再直连mysql,而是改为连proxy server,这样就可以做隔离了,后续存储改为其它数据库,系统升级也比较方便。
2、增加步长
每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。只需要把step设置得足够大,比如1000。那么只有当1000个号被消耗完了之后才会去重新读写一次数据库。读写数据库的频率从1减小到了1/step。
4.1.2 Leaf-snowflake方案
Leaf-segment方案可以生成趋势递增的ID,同时ID号是可计算的,不适用于订单ID生成场景,比如竞对在两天中午12点分别下单,通过订单id号相减就能大致计算出公司一天的订单量,这个是不能忍受的。面对这一问题,美团提供了 Leaf-snowflake方案。
启动步骤如下:
时钟问题解决方案:
4.2 百度的uid-generator
github地址:https://github.com/baidu/uid-generator
分配方式:
worder id由数据库来保存,个人觉得会是一个瓶颈,并且用完即弃,如果机器经常启动,感觉也会很快用完。
单机并发只有8192,看具体业务场景,当然可以再扩充,可以让worder id变短些。
4.3 微信的seqsvr
适用于面向每个用户的场景,像用户数据同步等。
实现思路是:预分配+分号段共享存储+存储和缓存分离
在容灾方面,先是用主从架构
后来再采用动态路由表的方案来解决配置不一致的问题,
细节就不在这里聊了,有兴趣的同学可以百度下:微信序列号生成器架构设计及演变