如何设计一个分布式ID生成器保证ID按时间有序?
很多业务有生成唯一 ID 并作为数据库主键的需求。数据库会在这个字段上建立聚集索引(参考 MySQL InnoDB),即该字段会影响各条数据再物理存储上的顺序。 ID还要尽可能短,节省内存,让数据库索引效率更高。基本上64位整数能够满足绝大多数的场景,但是如果能做到比64位更短那就更好了。需要根据具体业务进行分析,预估出ID的最大值,这个最大值通常比64位整数的上限小很多,于是我们可以用更少的bit表示这个ID。 查询的时候,往往有分页或者排序的需求,所以需要给每条数据添加一个时间字段,并在其上建立普通索引(Secondary Index)。但是普通索引的访问效率比聚集索引慢,如果能够让ID按照时间粗略有序,则可以省去这个时间字段。为什么不是按照时间精确有序呢?因为按照时间精确有序是做不到的,除非用一个单机算法,在分布式场景下做到精确有序性能一般很差。 这就引出了 ID 生成的三大核心需求: 全局唯一 按照时间粗略有序 尽可能短 下面介绍一些常用的生成 ID 的方法。 UUID UUID 是一类算法的统称,具体有不同的实现。UUID 的优点是每台机器可以独立产生 ID,理论上保证不会重复,所以天然是分布式的;缺点是生成的 ID 太长,不仅占用内存,而且索引查询效率低。 MongoDB 的 ObjectId 使用的就是 UUID 算法。生成的 ObjectId 占 12 个字节,由以下几个部分组成, 4 个字节表示的 Unix timestamp 3 个字节表示的机器的 ID 2 个字节表示的进程 ID 3 个字节表示的计数器 使用数据库 可以使用数据库中的自增主键来生成ID。将ID生成的过程交给数据库管理,每个节点向数据库插入记录时,数据库会自动分配一个唯一的ID。通过使用数据库的自动递增功能,可以保证ID的唯一性和粗略有序性。 在分布式环境下,可以使用多台数据库协同工作生成 ID。假设用 8 台MySQL服务器协同工作,第一台 MySQL 初始值是 1,每次自增 8,第二台 MySQL 初始值是 2,每次自增 8,依次类推。在数据库前面添加一个负载均衡,每来一个请求,由负载均衡随机地将请求发给 8 台 MySQL 中的任意一个,然后返回一个ID。 Flickr就是这么做的,仅仅使用了两台 MySQL 服务器。可见这个方法虽然简单无脑,但是性能足够好。不过要注意,在 MySQL 中,不需要把所有 ID 都存下来,每台机器只需要存一个 MAX_ID 就可以了。这需要用到 MySQL 的一个 REPLACE INTO 特性。 Flickr 的实现方式如下。 Tickets64 表结构如下: 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=InnoDB SELECT * from Tickets64 返回一行,如下所示:...