分布式环境下创建唯一ID

来源:互联网 发布:删除windows一键还原 编辑:程序博客网 时间:2024/06/05 15:55

在我目前的系统中,有多个系统会尝试写入数据库。要求库中表的第一个字段是全局唯一的ID(跨表)。

为了实现该要求,想了如下的办法:

1. 自行设计唯一规则;

2. 利用数据库自增;

3. 利用外部统一的ID生成器。


1先不谈。后面详解。

2. 数据库自增在跨表时就无法用了,但是可以单独创建一张表来计数,每次去取LAST_INSERT值。利用了MySQL的replace info。当然也可以自己实现存储过程,会比较复杂。

CREATE TABLE `TicketsTable` (  `id` bigint(20) unsigned NOT NULL auto_increment,  `stub` char(1) NOT NULL default '',  PRIMARY KEY  (`id`),  UNIQUE KEY `stub` (`stub`)) 

向里边插入一条记录后大致是这样:


+-------------------+------+| id | stub |+-------------------+------+| 72157623227190423 | a |+-------------------+------+


当需要一个64Bits ID的时候,执行如下SQL 语句:

REPLACE INTO Tickets64 (stub) VALUES ('a');SELECT LAST_INSERT_ID();


存储该表的Server如果挂掉,计数就不可用了。所以为了防止这个Ticket Server单点故障,可以设置两个Ticket Server实例。其中一个产生奇数ID,另一个产生偶数ID。

TicketServer1:auto-increment-increment = 2auto-increment-offset = 1TicketServer2:auto-increment-increment = 2auto-increment-offset = 2

应用交替请求两个Server,这样不仅压力减小一半,故障风险也降低一半。不过这里也有个问题,就是当一台机器故障时,另一台正常机器产生的ID将会领先故障机器一截,可能会造成不再是时间上有序的ID。

Flickr就是使用这种方法,按照他们的说法,这并不影响他们的应用。


3. 这个外部计数器,可以有很多种实现方式:

a.MongoDB

MongoDB中每一条记录都有一个’id’字段用来唯一标示本记录。如果用户插入数据时没有显示提供’id’字段,那么系统会自动生成一个。ObjectID一共12Bytes,设计的时候充分考虑了分布式环境下使用的情况,所以能保证在一个分布式MongoDB集群中唯一。ObjectID格式如下:

0 4 7 9 12+--------+------+----+------+|time |pc |pid |inc |+--------+------+----+------+

前四个字节是Unix Timestamp。
接着三个字节是当前机器“hostname/mac地址/虚拟编号”其中之一的MD5结果的前3个字节。
接着两个字节是当前进程的PID。
最后三个字节是累加计数器或是一个随机数(只有当不支持累加计数器时才用随机数)。
最后生成的仍然是一个用16进制表示的串,如47cc67093475061e3d95369d。这里MongoDB的ObjectID相对UUID有个很大的优点就是ObjectID是时间上有序的。另外还有ObjectID本身也包含了很多其它有用的信息,通过直接解码ObjectID即可直接获得这些信息。


b. Snowflake

Snowflake是twitter开源的一款独立的适用于分布式环境的ID生成服务器。生成的ID是64Bits,同时满足高性能(>10K ids/s),低延迟(<2ms)和高可用。与MongoDB ObjectID类似这里生成的ID也是时间上有序的。编码方式也和ObjectID类似,如下:

0 41 51 64+-----------+------+------+|time |pc |inc |+-----------+------+------+

前41bits是以微秒为单位的timestamp。
接着10bits是事先配置好的机器ID。
最后12bits是累加计数器。


在我的项目里,上面两种都不太方便。只能自定义规则。

考虑到MySQL的bigint型最大是64bit长,所以,规则也只能定到64位。

这里很自然就想到了GUID和UUID,但是长度太长了,128bit。必须得压缩。可以用base64,但是MurmurHash效率更高。

MurmurHash可以产生32位、64位或128位的定长hash,理论上只要key不同,碰撞几率非常小。

结果,在我用GUID做key去hash,发现32位碰撞的很厉害,64位100万次测试没有碰撞,128位就更不用说了。

所以,如果只是要64位唯一ID, GUID+Murmurhash就OK了。


但是,这里已然有个问题,hash后的ID不具有递增性,在数据批量处理时会很不方便。所以,在考虑用前32位时间戳,后32位唯一值的方式来实现,但是到现在没想到很好的组合,找了几个都有碰撞。待继续研究处理。

两篇很好的参考文章:

http://blog.ddup.us/?p=302

http://www.cnblogs.com/heyuquan/p/3261250.html

0 0