在复杂分布式系统中,每每须要对大量的数据和消息进行惟一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增加,对数据分库分表后须要有一个惟一ID来标识一条数据或消息,数据库的自增ID显然不能知足需求;特别一点的如订单、骑手、优惠券也都须要有惟一ID作标识。此时一个可以生成全局惟一ID的系统是很是必要的。归纳下来,那业务系统对ID号的要求有哪些呢?html
上述123对应三类不一样的场景,3和4需求仍是互斥的,没法使用同一个方案知足。java
同时除了对ID号码自身的要求,业务还对ID号生成系统的可用性要求极高,想象一下,若是ID生成系统瘫痪,整个美团点评支付、优惠券发券、骑手派单等关键动做都没法执行,这就会带来一场灾难。node
由此总结下一个ID生成系统应该作到以下几点:mysql
UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:550e8400-e29b-41d4-a716-446655440000
,到目前为止业界一共有5种方式生成UUID,详情见IETF发布的UUID规范 A Universally Unique IDentifier (UUID) URN Namespace。git
优势:github
缺点:算法
ID做为主键时在特定的环境会存在一些问题,好比作DB主键的场景下,UUID就很是不适用:sql
① MySQL官方有明确的建议主键要尽可能越短越好[4],36个字符长度的UUID不符合要求。mongodb
All indexes other than the clustered index are known as secondary indexes. In InnoDB, each record in a secondary index contains the primary key columns for the row, as well as the columns specified for the secondary index. InnoDB uses this primary key value to search for the row in the clustered index. If the primary key is long, the secondary indexes use more space, so it is advantageous to have a short primary key.数据库
② 对MySQL索引不利:若是做为数据库主键,在InnoDB引擎下,UUID的无序性可能会引发数据位置频繁变更,严重影响性能。
这种方案大体来讲是一种以划分命名空间(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相似方法,经过“时间+机器码+pid+inc”共12个字节,经过4+3+2+3的方式最终标识成一个24长度的十六进制字符。
以MySQL举例,利用给字段设置auto_increment_increment
和auto_increment_offset
来保证ID自增,每次业务使用下列SQL读写MySQL获得ID号。
begin; REPLACE INTO Tickets64 (stub) VALUES ('a'); SELECT LAST_INSERT_ID(); commit;
这种方案的优缺点以下:
优势:
缺点:
对于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那么整个架构就变成了以下图所示:
这种架构貌似可以知足性能的需求,但有如下几个缺点:
Leaf这个名字是来自德国哲学家、数学家莱布尼茨的一句话:
There are no two identical leaves in the world
"世界上没有两片相同的树叶"
综合对比上述几种方案,每种方案都不彻底符合咱们的要求。因此Leaf分别在上述第二种和第三种方案上作了相应的优化,实现了Leaf-segment和Leaf-snowflake方案。
第一种Leaf-segment方案,在使用数据库的方案上,作了以下改变:
数据库表设计以下:
+-------------+--------------+------+-----+-------------------+-----------------------------+ | 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 | +-------------+--------------+------+-----+-------------------+-----------------------------+
重要字段说明:biz_tag用来区分业务,max_id表示该biz_tag目前所被分配的ID号段的最大值,step表示每次分配的号段长度。原来获取ID每次都须要写数据库,如今只须要把step设置得足够大,好比1000。那么只有当1000个号被消耗完了以后才会去从新读写一次数据库。读写数据库的频率从1减少到了1/step,大体架构以下图所示:
test_tag在第一台Leaf机器上是1~1000的号段,当这个号段用完时,会去加载另外一个长度为step=1000的号段,假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是3001~4000。同时数据库对应的biz_tag这条数据的max_id会从3000被更新成4000,更新号段的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
这种模式有如下优缺点:
优势:
缺点:
对于第二个缺点,Leaf-segment作了一些优化,简单的说就是:
Leaf 取号段的时机是在号段消耗完的时候进行的,也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间,而且在这期间进来的请求也会由于DB号段没有取回来,致使线程阻塞。若是请求DB的网络和DB的性能稳定,这种状况对系统的影响是不大的,可是假如取DB的时候网络发生抖动,或者DB发生慢查询就会致使整个系统的响应时间变慢。
为此,咱们但愿DB取号段的过程可以作到无阻塞,不须要在DB取号段的时候阻塞请求线程,即当号段消费到某个点时就异步的把下一个号段加载到内存中。而不须要等到号段用尽的时候才去更新号段。这样作就能够很大程度上的下降系统的TP999指标。详细实现以下图所示:
采用双buffer的方式,Leaf服务内部有两个号段缓存区segment。当前号段已下发10%时,若是下一个号段未更新,则另启一个更新线程去更新下一个号段。当前号段所有下发完后,若是下个号段准备好了则切换到下个号段为当前segment接着下发,循环往复。
每一个biz-tag都有消费速度监控,一般推荐segment长度设置为服务高峰期发号QPS的600倍(10分钟),这样即便DB宕机,Leaf仍能持续发号10-20分钟不受影响。
每次请求来临时都会判断下个号段的状态,从而更新此号段,因此偶尔的网络抖动不会影响下个号段的更新。
对于第三点“DB可用性”问题,咱们目前采用一主两从的方式,同时分机房部署,Master和Slave之间采用半同步方式[5]同步数据。同时使用公司Atlas数据库中间件(已开源,更名为DBProxy)作主从切换。固然这种方案在一些状况会退化成异步模式,甚至在很是极端状况下仍然会形成数据不一致的状况,可是出现的几率很是小。若是你的系统要保证100%的数据强一致,能够选择使用“类Paxos算法”实现的强一致MySQL方案,如MySQL 5.7前段时间刚刚GA的MySQL Group Replication。可是运维成本和精力都会相应的增长,根据实际状况选型便可。
同时Leaf服务分IDC部署,内部的服务化框架是“MTthrift RPC”。服务调用的时候,根据负载均衡算法会优先调用同机房的Leaf服务。在该IDC内Leaf服务不可用的时候才会选择其余机房的Leaf服务。同时服务治理平台OCTO还提供了针对服务的过载保护、一键截流、动态流量分配等对服务的保护措施。
Leaf-segment方案能够生成趋势递增的ID,同时ID号是可计算的,不适用于订单ID生成场景,好比竞对在两天中午12点分别下单,经过订单id号相减就能大体计算出公司一天的订单量,这个是不能忍受的。面对这一问题,咱们提供了 Leaf-snowflake方案。
Leaf-snowflake方案彻底沿用snowflake方案的bit位设计,便是“1+41+10+12”的方式组装ID号。对于workerID的分配,当服务集群数量较小的状况下,彻底能够手动配置。Leaf服务规模较大,动手配置成本过高。因此使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。Leaf-snowflake是按照下面几个步骤启动的:
除了每次会去ZK拿数据之外,也会在本机文件系统上缓存一个workerID文件。当ZooKeeper出现问题,刚好机器出现问题须要重启时,能保证服务可以正常启动。这样作到了对三方组件的弱依赖。必定程度上提升了SLA
由于这种方案依赖时间,若是机器的时钟发生了回拨,那么就会有可能生成重复的ID号,须要解决时钟回退的问题。
参见上图整个启动流程图,服务启动时首先检查本身是否写过ZooKeeper leaf_forever节点:
因为强依赖时钟,对时间的要求比较敏感,在机器工做时NTP同步也会形成秒级别的回退,建议能够直接关闭NTP同步。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE,等时钟追上便可。或者作一层重试,而后上报报警系统,更或者是发现有时钟回拨以后自动摘除自己节点并报警,以下:
//发生了回拨,此刻时间小于上次发号时间 if (timestamp < lastTimestamp) { long offset = lastTimestamp - timestamp; if (offset <= 5) { try { //时间误差大小小于5ms,则等待两倍时间 wait(offset << 1);//wait timestamp = timeGen(); if (timestamp < lastTimestamp) { //仍是小于,抛异常并上报 throwClockBackwardsEx(timestamp); } } catch (InterruptedException e) { throw e; } } else { //throw throwClockBackwardsEx(timestamp); } } //分配ID
从上线状况来看,在2017年闰秒出现那一次出现过部分机器回拨,因为Leaf-snowflake的策略保证,成功避免了对业务形成的影响。
Leaf在美团点评公司内部服务包含金融、支付交易、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。目前Leaf的性能在4C8G的机器上QPS能压测到近5w/s,TP999 1ms,已经可以知足大部分的业务的需求。天天提供亿数量级的调用量,做为公司内部公共的基础技术设施,必须保证高SLA和高性能的服务,咱们目前还仅仅达到了及格线,还有不少提升的空间。
照东,美团点评基础架构团队成员,主要参与美团大型分布式链路跟踪系统Mtrace和美团点评分布式ID生成系统Leaf的开发工做。曾就任于阿里巴巴,2016年7月加入美团。
最后作一个招聘广告:若是你对大规模分布式环境下的服务治理、分布式会话链追踪等系统感兴趣,诚挚欢迎投递简历至:zhangjinlu#meituan.com。
原文连接:https://tech.meituan.com/MT_Leaf.html