Leaf : 美团分布式ID生成服务
There are no two identical leaves in the world.(世界上没有两片相同的树叶。) — 莱布尼茨
现有分布式ID生成方案
在探究美团的 Leaf 服务之前,我们不妨先了解下市场上现有的几种分布式 Id 生成方案。
- UUID
- 数据库自增ID
- 号段模式
- Redis
- 雪花算法(SnowFlake)
- 滴滴出品(TinyID)
- 百度(Uidgenerator)
- 美团(Leaf)
UUID
在 Java 中,如果你想在分布式系统中获得一个具有唯一性的ID,UUID 一定是首先被想起的几种方案之一,毕竟它有着全球唯一的特性,实现起来也十分简单。
public static void main(String[] args) {
String uuid = UUID.randomUUID().toString().replaceAll("-","");
System.out.println(uuid);
}
UUID 在你的项目中使用简单到只需要一行代码,输出结果则为一串无序且唯一的64位字符c2b8c2b9e46c47e3b30dca3b0d447718
。
如果我们仅仅想要的是一个全球唯一的 ID 号,那 UUID 的确是一个十分不错的解决方案。
但在实际生产环境中,UUID 作为 ID 最终是要被存入数据库之中并作为主键
的。但 UUID 64位的臃肿身材注定使得其在数据库中的存储性能差查询也会相当耗时。
另一个问题则是 UUID 其完全无序的生成策略导致其若作为流水号无法看出其与业务的联系,其不包含时间戳、机器ID,更无法满足趋势递增性的特点。注定其不太适合作为分布式ID
的生成方案。
优点:
- 生成足够简单,在本地生成没有网络消耗也没有中心化导致的单点故障问题,且具有唯一性
缺点:
- 无序的字符串,不具备趋势递增的特性
- 没有具体的业务含义
- 长度过长,存储查询对于MySQL数据库性能消耗大,MySQL官方也明确建议主键长度尽量越短越好,作为数据库主键
UUID
的无序性会导致数据位置频繁变动,严重影响性能。
数据库自增ID
基于数据库的auto_increment
自增ID完全可以当分布式ID
,具体实现:需要一个单独的MySQL实例用来生成ID,建表结构如下:
CREATE DATABASE `SEQ_ID`;
CREATE TABLE SEQID.SEQUENCE_ID (
id bigint(20) unsigned NOT NULL auto_increment,
value char(10) NOT NULL default '',
PRIMARY KEY (id),
) ENGINE=MyISAM;
insert into SEQUENCE_ID(value) VALUES ('values');
当我们需要一个ID时,向表中插入一条记录返回主键ID
,但这种方式有一个比较致命的缺点,就是当QPS激增时,MySQL 数据库可能由于自身性能的瓶颈限制(500-700并发量/秒)。且由于 MySQL 作为 ID
生成服务,一旦 MySQL 宕机出现单点故障,将可能导致系统后续所有服务不可用,风险较大。
优点:
- 实现简单成本低,ID 单调递增,数值类型查询性能高
缺点:
- 单例 DB 存在宕机风险,无法应对高并发场景
数据库集群模式
在对上述方案进行优化,对数据库进行集群化部署方式拓展,使用主主模式集群。同样为了防止多主数据库进行数据同步时出现主键冲突,我们需要预先设置 ID 的起始值与步长。
MySQL配置:
set @@auto_increment_offset = start; -- start 起始值:数据库序号
set @@auto_increment_increment = step; -- step 步长:maste