分布式Id教程

转自:https://baijiahao.baidu.com/s?id=1584913615817222458&wfr=spider&for=pcmysql

一,题记git

全部的业务系统,都有生成ID的需求,如订单id,商品id,文章ID等。这个ID会是数据库中的惟一主键,在它上面会创建汇集索引!github

ID生成的核心需求有两点:web

全局惟一算法

趋势有序sql

二,为何要全局惟一?数据库

著名的例子就是身份证号码,身份证号码确实是对人惟一的,然而一我的是能够办理多个身份证的,例如你身份证丢了,又从新补办了一张,号码不变。安全

问题来了,由于系统是按照身份证号码作惟一主键的。此时,若是身份证是被盗的状况下,你是没有办法在系统里面注销的,由于新旧2个身份证的“主键”都是身份证号码。服务器

也就是说,旧的身份证仍然逍遥在外,彻底有效。这个时候,还好有一个身份证有效时间的东西,只有靠身份证有效期来辨识了。不过,这就是如今这么多银行,电信诈骗的由来,捡到一张身份证,去不少银行,手机,酒店均可以使用!身份证缺少注销机制!数据结构

因此,经验告诉咱们。不要相信本身的直觉,业务上所谓的惟一每每都是不靠谱的,经不起时间的考研的。因此须要单独设置一个和业务无关的主键,专业术语叫作代理主键(surrogate key)。

这也是为何数据库设计范式,惟一主键是第一范式!

三,为何要趋势有序

以mysql为例,InnoDB引擎表是基于B+树的索引组织表(IOT);每一个表都须要有一个汇集索引(clustered index);全部的行记录都存储在B+树的叶子节点(leaf pages of the tree);基于汇集索引的增、删、改、查的效率相对是最高的;以下图:

 

若是咱们定义了主键(PRIMARY KEY),那么InnoDB会选择其做为汇集索引;

若是没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的惟一索引做为主键索引;

若是也没有这样的惟一索引,则InnoDB会选择内置6字节长的ROWID做为隐含的汇集索引(ROWID随着行记录的写入而主键递增,这个ROWID不像ORACLE的ROWID那样可引用,是隐含的)。

综上总结,若是InnoDB表的数据写入顺序能和B+树索引的叶子节点顺序一致的话,这时候存取效率是最高的,也就是下面这几种状况的存取效率最高

使用自增列(INT/BIGINT类型)作主键,这时候写入顺序是自增的,和B+数叶子节点分裂顺序一致;

该表不指定自增列作主键,同时也没有能够被选为主键的惟一索引(上面的条件),这时候InnoDB会选择内置的ROWID做为主键,写入顺序和ROWID增加顺序一致;

除此之外,若是一个InnoDB表又没有显示主键,又有能够被选择为主键的惟一索引,但该惟一索引可能不是递增关系时(例如字符串、UUID、多字段联合惟一索引的状况),该表的存取效率就会比较差。)

这就是为何咱们的分布式ID必定要是趋势递增的!那么在开发当中,面对这种分布式ID需求,常见的处理方案有哪些呢?

 

四,数据库自增加序列或字段

最多见的方式。利用数据库,全数据库惟一。

优势:

1)简单,代码方便,性能能够接受。

2)数字ID自然排序,对分页或者须要排序的结果颇有帮助。

缺点:

1)不一样数据库语法和实现不一样,数据库迁移的时候或多数据库版本支持的时候须要处理。

2)在单个数据库或读写分离或一主多从的状况下,只有一个主库能够生成。有单点故障的风险。

3)在性能达不到要求的状况下,比较难于扩展。

4)若是碰见多个系统须要合并或者涉及到数据迁移会至关痛苦。

5)分表分库的时候会有麻烦。

优化方案:

1)针对主库单点,若是有多个Master库,则每一个Master库设置的起始数字不同,步长同样,能够是Master的个数。好比:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。这样就能够有效生成集群中的惟一ID,也能够大大下降ID生成数据库操做的负载。

五,UUID

常见的方式。能够利用数据库也能够利用程序生成,通常来讲全球惟一。

优势:

1)简单,代码方便。

2)生成ID性能很是好,基本不会有性能问题。

3)全球惟一,在碰见数据迁移,系统数据合并,或者数据库变动等状况下,能够从容应对。

缺点:

1)没有排序,没法保证趋势递增。

2)UUID每每是使用字符串存储,查询的效率比较低。

3)存储空间比较大,若是是海量数据库,就须要考虑存储量的问题。

4)传输数据量大

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)须要编码和配置的工做量比较大。

七,twitter

twitter在把存储系统从MySQL迁移到Cassandra的过程当中因为Cassandra没有顺序ID生成机制,因而本身开发了一套全局惟一ID生成服务:Snowflake。

1 41位的时间序列(精确到毫秒,41位的长度可使用69年)

2 10位的机器标识(10位的长度最多支持部署1024个节点)

3 12位的计数顺序号(12位的计数顺序号支持每一个节点每毫秒产生4096个ID序号) 最高位是符号位,始终为0。

优势:

高性能,低延迟;独立的应用;

按时间有序。

缺点:

须要独立的开发和部署。

强依赖时钟,若是主机时间回拨,则会形成重复ID,会产生

ID虽然有序,可是不连续

原理

 

八,MongoDB的ObjectId

MongoDB的ObjectId和snowflake算法相似。它设计成轻量型的,不一样的机器都能用全局惟一的同种方法方便地生成它。MongoDB 从一开始就设计用来做为分布式数据库,处理多个节点是一个核心要求。使其在分片环境中要容易生成得多。

ObjectId使用12字节的存储空间,其生成方式以下:

|0|1|2|3|4|5|6 |7|8|9|10|11|

|时间戳 |机器ID|PID|计数器 |

前四个字节时间戳是从标准纪元开始的时间戳,单位为秒,有以下特性:

1 时间戳与后边5个字节一块,保证秒级别的惟一性;

2 保证插入顺序大体按时间排序;

3 隐含了文档建立时间;

4 时间戳的实际值并不重要,不须要对服务器之间的时间进行同步(由于加上机器ID和进程ID已保证此值惟一,惟一性是ObjectId的最终诉求)。

机器ID是服务器主机标识,一般是机器主机名的散列值。

同一台机器上能够运行多个mongod实例,所以也须要加入进程标识符PID。

前9个字节保证了同一秒钟不一样机器不一样进程产生的ObjectId的惟一性。后三个字节是一个自动增长的计数器(一个mongod进程须要一个全局的计数器),保证同一秒的ObjectId是惟一的。同一秒钟最多容许每一个进程拥有(256^3 = 16777216)个不一样的ObjectId。

总结一下:时间戳保证秒级惟一,机器ID保证设计时考虑分布式,避免时钟同步,PID保证同一台服务器运行多个mongod实例时的惟一性,最后的计数器保证同一秒内的惟一性(选用几个字节既要考虑存储的经济性,也要考虑并发性能的上限)。

"_id"既能够在服务器端生成也能够在客户端生成,在客户端生成能够下降服务器端的压力。

九,类snowflake算法

国内有不少厂家基于snowflake算法进行了国产化,例如

百度的uid-generator:

https://github.com/baidu/uid-generator

美团Leaf:

https://github.com/zhuzhong/idleaf

基本是对snowflake的进一步优化,好比解决时钟 回拨问题!

十,总结

整体而言,分布式惟一ID须要知足如下条件:

高可用性:不能有单点故障。

全局惟一性:不能出现重复的ID号,既然是惟一标识,这是最基本的要求。

趋势递增:在MySQL InnoDB引擎中使用的是汇集索引,因为多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面咱们应该尽可能使用有序的主键保证写入性能。

时间有序:以时间为序,或者ID里包含时间。这样一是能够少一个索引,二是冷热数据容易分离。

分片支持:能够控制ShardingId。好比某一个用户的文章要放在同一个分片内,这样查询效率高,修改也容易。

单调递增:保证下一个ID必定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。

长度适中:不要太长,最好64bit。使用long比较好操做,若是是96bit,那就要各类移位至关的不方便,还有可能有些组件不能支持这么大的ID。

信息安全:若是ID是连续的,恶意用户的拔取工做就很是容易作了,直接按照顺序下载指定URL便可;若是是订单号就更危险了,竞争对手能够直接知道咱们一天的单量。因此在一些应用场景下,会须要ID无规则、不规则。

欢迎关注

相关文章
相关标签/搜索