Leaf是美团基础研发平台推出的一个分布式ID生成服务,名字取自德国哲学家、数学家莱布尼茨的一句话:“There are no two identical leaves in the world.”Leaf具有高可靠、低延迟、全局惟一等特色。目前已经普遍应用于美团金融、美团外卖、美团酒旅等多个部门。具体的技术细节。近日,Leaf项目已经在Github上开源:https://github.com/Meituan-Dianping/Leaf,但愿能和更多的技术同行一块儿交流、共建。html
Leaf在设计之初就秉承着几点要求:mysql
Leaf第一个版本采用了预分发的方式生成ID,便可以在DB之上挂N个Server,每一个Server启动时,都会去DB拿固定长度的ID List。这样就作到了彻底基于分布式的架构,同时由于ID是由内存分发,因此也能够作到很高效。接下来是数据持久化问题,Leaf每次去DB拿固定长度的ID List,而后把最大的ID持久化下来,也就是并不是每一个ID都作持久化,仅仅持久化一批ID中最大的那一个。这个方式有点像游戏里的按期存档功能,只不过存档的是将来某个时间下发给用户的ID,这样极大地减轻了DB持久化的压力。git
整个服务的具体处理过程以下:github
用户经过Round-robin的方式调用Leaf Server的各个服务,因此某一个Client获取到的ID序列多是:1,1001,2001,2,1002,2002……也多是:1,2,1001,2001,2002,2003,3,4……当某个Leaf Server号段用完以后,下一次请求就会从DB中加载新的号段,这样保证了每次加载的号段是递增的。算法
Leaf数据库中的号段表格式以下:sql
+-------------+--------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+-------------------+-----------------------------+
| biz_tag | varchar(128) | NO | PRI | | |
| max_id | bigint(20) | NO | | 1 | |
| step | int(11) | NO | | NULL | |
| desc | varchar(256) | YES | | NULL | |
| update_time | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------------+--------------+------+-----+-------------------+-----------------------------+
复制代码
Leaf Server加载号段的SQL语句以下:数据库
Begin
UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
SELECT tag, max_id, step FROM table WHERE biz_tag=xxx
Commit
复制代码
总体上,V1版本实现比较简单,主要是为了尽快解决业务层DB压力的问题,而快速迭代出的一个版本。于是在生产环境中,也发现了些问题。好比:缓存
为了解决这两个问题,Leaf采用了异步更新的策略,同时经过双Buffer的方式,保证不管什么时候DB出现问题,都能有一个Buffer的号段能够正常对外提供服务,只要DB在一个Buffer的下发的周期内恢复,就不会影响整个Leaf的可用性。bash
这个版本代码在线上稳定运行了半年左右,Leaf又遇到了新的问题:架构
假设服务QPS为Q,号段长度为L,号段更新周期为T,那么Q * T = L。最开始L长度是固定的,致使随着Q的增加,T会愈来愈小。可是Leaf本质的需求是但愿T是固定的。那么若是L能够和Q正相关的话,T就能够趋近一个定值了。因此Leaf每次更新号段的时候,根据上一次更新号段的周期T和号段长度step,来决定下一次的号段长度nextStep:
至此,知足了号段消耗稳定趋于某个时间区间的需求。固然,面对瞬时流量几10、几百倍的暴增,该种方案仍不能知足能够容忍数据库在一段时间不可用、系统仍能稳定运行的需求。由于本质上来说,Leaf虽然在DB层作了些容错方案,可是号段方式的ID下发,最终仍是须要强依赖DB。
在MySQL这一层,Leaf目前采起了半同步的方式同步数据,经过公司DB中间件Zebra加MHA作的主从切换。将来追求彻底的强一致,会考虑切换到MySQL Group Replication。
现阶段因为公司数据库强一致的特性还在演进中,Leaf采用了一个临时方案来保证机房断网场景下的数据一致性:
针对服务自身的监控,Leaf提供了Web层的内存数据映射界面,能够实时看到全部号段的下发状态。好比每一个号段双buffer的使用状况,当前ID下发到了哪一个位置等信息均可以在Web界面上查看。
Snowflake,Twitter开源的一种分布式ID生成算法。基于64位数实现,下图为Snowflake算法的ID构成图。
这样经过时间+机器号+自增ID的组合来实现了彻底分布式的ID下发。
在这里,Leaf提供了Java版本的实现,同时对Zookeeper生成机器号作了弱依赖处理,即便Zookeeper有问题,也不会影响服务。Leaf在第一次从Zookeeper拿取workerID后,会在本机文件系统上缓存一个workerID文件。即便ZooKeeper出现问题,同时刚好机器也在重启,也能保证服务的正常运行。这样作到了对第三方组件的弱依赖,必定程度上提升了SLA。
分布式ID生成的方案有不少种,Leaf开源版本提供了两种ID的生成方式:
读者能够按需选择适合自身业务场景的ID下发方式。但愿美团的方案能给予你们一些帮助,同时也但愿各位可以一块儿交流、共建。
Leaf项目Github地址:https://github.com/Meituan-Dianping/Leaf 。
若有任何疑问和问题,欢迎留言~