在分布式系统中,生成全局惟一ID,有不少种方案,可是在这多种方案中,每种方案都有有缺点,下面咱们之针对经过经常使用数据库来生成分布式ID的方案,其它方法会在其它文中讨论:mysql
这里咱们讨论mysql生成ID。由于MySQL自己能够auto_increment和auto_increment_offset来保证ID自增,很天然地,咱们会想到借助这个特性来实现这个功能。算法
全局ID生成方案里采用了MySQL自增加ID的机制(auto_increment + replace into + MyISAM)。一个生成64位ID方案具体实现是这样的:
先建立单独的数据库(eg:ticket),而后建立一个表:sql
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
表建立以后咱们要设置一个初始值,好比100000,执行SELECT * from Tickets64,查询结果就是这样的:mongodb
每当咱们的应用须要ID的时候就会作以下操做,调用以下存储过程:数据库
begin; REPLACE INTO Tickets64 (stub) VALUES ('a'); SELECT LAST_INSERT_ID(); commit;
架构如图:服务器
这样咱们就能拿到不断增加且不重复的ID了。 网络
这种方案的优缺点以下:架构
优势:并发
缺点:分布式
对于MySQL性能问题,可用以下方案解决:在分布式系统中咱们能够多部署几台机器,每台机器设置不一样的初始值,且步长和机器数相等。好比有两台机器。设置步长step为2,TicketServer1的初始值为1(1,3,5,7,9,11...)、TicketServer2的初始值为2(2,4,6,8,10...)。这是Flickr团队在2010年撰文介绍的一种主键生成策略(Ticket Servers: Distributed Unique Primary Keys on the Cheap )。以下所示,为了实现上述方案分别设置两台机器对应的参数,TicketServer1从1开始发号,TicketServer2从2开始发号,两台机器每次发号以后都递增2。
TicketServer1: auto-increment-increment = 2 auto-increment-offset = 1 TicketServer2: auto-increment-increment = 2 auto-increment-offset = 2
假设咱们要部署N台机器,步长需设置为N,每台的初始值依次为0,1,2...N-1那么整个架构就变成了以下图所示:
这种架构貌似可以知足性能的需求,但有如下几个缺点:
这种方案大体来讲是一种以划分命名空间(UUID也算,因为比较常见,因此单独分析)来生成ID的一种算法,这种方案把64-bit分别划分红多段,分开来标示机器、时间等,好比在snowflake中的64-bit分别表示以下图(图片来自网络)所示:
41-bit的时间能够表示(1L<<41)/(1000L*3600*24*365)=69年的时间,10-bit机器能够分别表示1024台机器。若是咱们对IDC划分有需求,还能够将10-bit分5-bit给IDC,分5-bit给工做机器。这样就能够表示32个IDC,每一个IDC下能够有32台机器,能够根据自身需求定义。12个自增序列号能够表示2^12个ID,理论上snowflake方案的QPS约为409.6w/s,这种分配方式能够保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不一样的。
这种方式的优缺点是:
优势:
缺点:
MongoDB官方文档 ObjectID能够算做是和snowflake相似方法:
为了考虑分布式,“_id”要求不一样的机器都能用全局惟一的同种方法方便的生成它。所以不能使用自增主键(须要多台服务器进行同步,既费时又费力),
所以选用了生成ObjectId对象的方法。
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"既能够在服务器端生成也能够在客户端生成,在客户端生成能够下降服务器端的压力。
当使用数据库来生成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)须要编码和配置的工做量比较大。