默认
打赏 发表评论 13
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
IM消息ID技术专题(四):深度解密美团的分布式ID生成算法
阅读(118521) | 评论(13 收藏10 淘帖2 3
微信扫一扫关注!

本文来自美团技术团队“照东”的分享,原题《Leaf——美团点评分布式ID生成系统》,即时通讯网收录时有勘误、修订并重新排版,感谢原作者的分享。


1、引言


鉴于IM系统中聊天消息ID生成算法和生成策略的重要性(因为某种意义上来说:聊天消息ID的优劣决定了IM应用层某些功能实现的难易度),所以即时通讯网近期正在着重整理有关IM中的聊天消息ID算法方面的文章,包括微信团队的这篇《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》,以及融云分享的《融云技术分享:解密融云IM产品的聊天消息ID生成策略》一文。

本文分享了美团系统中正在使用的两种ID生成算法:

  • 1)Leaf-segment方案:可生成全局唯一、全局有序的ID;
  • 2)Leaf-snowflake方案:可生成全局唯一、局部有序的ID。

对于美团的Leaf-segment这个ID生成方案,因为生成的ID全局唯一、全局有序,所以非常适合IM这种应用场景,这也是即时通讯网整理并分享给社区的原因。

友情提示:IM系统中的消息ID不同于电商等传统信息系统,IM中的消息ID通常较少用于服务端架构中的检索目的(例外是:消息撤回等使用频率较低的功能中会用到),所以服务端架构中的ID查询性能上可以不必追求极致(必竟IM消息对应于人的自然沟通,通常都是以时间为检索条件,比如离线消息拉取、群消息拉取、漫游消息拉取等),所以在学习诸如美团的ID生成算法时,没有必要生搬硬套,适度借鉴,按照IM系统的特性进行融会贯通地设计才是最佳实践

免责申明:本文来自美团官方技术团队的分享,仅用于技术交流学习和研究目的,请勿用于非法用途,文中如涉及商业秘密,请告之我处理!

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_cccc.jpg

2、关于作者


照东:美团点评基础架构团队成员,主要参与美团大型分布式链路跟踪系统Mtrace和美团点评分布式ID生成系统Leaf的开发工作。曾就职于阿里巴巴,2016年7月加入美团。

3、专题目录


本文是“IM消息ID技术专题”系列文章的第4篇,专题总目录如下:


4、正文概述


在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求;特别一点的如订单、骑手、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。

概括下来,那业务系统对ID号的要求有哪些呢?

  • 1)全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求;
  • 2)趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能;
  • 3)单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM聊天中的增量消息、排序等特殊需求;
  • 4)信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。

上述123对应三类不同的场景,3和4需求还是互斥的,无法使用同一个方案满足。

同时除了对ID号码自身的要求,业务还对ID号生成系统的可用性要求极高,想象一下,如果ID生成系统瘫痪,整个美团点评支付、优惠券发券、骑手派单等关键动作都无法执行,这就会带来一场灾难。

由此总结下一个分布式ID生成系统应做到如下几点:

  • 1)平均延迟和TP999延迟都要尽可能低;
  • 2)可用性5个9;
  • 3)高QPS。

5、美团为什么没用UUID?


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》。

对于美团点评这些具体的业务系统来说,UUID有以下优点和缺点。

优点:
性能非常高:本地生成,没有网络消耗。

缺点:
  • 1)不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用;
  • 2)信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。

ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用:

  • ① MySQL官方有明确的建议主键要尽量越短越好[4],36个字符长度的UUID不符合要求:

    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有很多合适的应用场景,但对于美团点评相关的业务系统来说,UUID显然不是最佳选择。

6、美团为什么不直接用Snowflake算法?


6.1SnowFlake算法原理


SnowFlake 算法,是 Twitter 开源的分布式 ID 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 ID。

这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 ID,12 bit 作为序列号。

SnowFlake的ID构成:
IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_1.png

SnowFlake的ID样本:
IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_aaa.jpg

给大家举个例子吧,如上图所示,比如下面那个 64 bit 的 long 型数字:

  • 1)第一个部分,是 1 个 bit:0,这个是无意义的;
  • 2)第二个部分,是 41 个 bit:表示的是时间戳;
  • 3)第三个部分,是 5 个 bit:表示的是机房 ID,10001;
  • 4)第四个部分,是 5 个 bit:表示的是机器 ID,1 1001;
  • 5)第五个部分,是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 ID 的序号,0000 00000000。

① 1 bit:是不用的,为啥呢?

因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 ID 都是正数,所以第一个 bit 统一都是 0。

② 41 bit:表示的是时间戳,单位是毫秒。

41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成年就是表示 69 年的时间。

③ 10 bit:记录工作机器 ID,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。

但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 ID。意思就是最多代表 2 ^ 5 个机房(32 个机房),每个机房里可以代表 2 ^ 5 个机器(32 台机器)。

④12 bit:这个是用来记录同一个毫秒内产生的不同 ID。

12 bit 可以代表的最大正整数是 2 ^ 12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 ID。理论上snowflake方案的QPS约为409.6w/s,这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。

简单来说,你的某个服务假设要生成一个全局唯一 ID,那么就可以发送一个请求给部署了 SnowFlake 算法的系统,由这个 SnowFlake 算法系统来生成唯一 ID。

  • 1)这个 SnowFlake 算法系统首先肯定是知道自己所在的机房和机器的,比如机房 ID = 17,机器 ID = 12;
  • 2)接着 SnowFlake 算法系统接收到这个请求之后,首先就会用二进制位运算的方式生成一个 64 bit 的 long 型 ID,64 个 bit 中的第一个 bit 是无意义的;
  • 3)接着 41 个 bit,就可以用当前时间戳(单位到毫秒),然后接着 5 个 bit 设置上这个机房 id,还有 5 个 bit 设置上机器 ID;
  • 4)最后再判断一下,当前这台机房的这台机器上这一毫秒内,这是第几个请求,给这次生成 ID 的请求累加一个序号,作为最后的 12 个 bit。

最终一个 64 个 bit 的 ID 就出来了,类似于:
IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_bbb.jpg

这个算法可以保证说,一个机房的一台机器上,在同一毫秒内,生成了一个唯一的 ID。可能一个毫秒内会生成多个 ID,但是有最后 12 个 bit 的序号来区分开来。

下面我们简单看看这个 SnowFlake 算法的一个代码实现,这就是个示例,大家如果理解了这个意思之后,以后可以自己尝试改造这个算法。

总之就是用一个 64 bit 的数字中各个 bit 位来设置不同的标志位,区分每一个 ID。

6.2SnowFlake算法的代码实现


SnowFlake 算法的一个典型Java实现代码,可以参见文章中的第“6.5 方案四:SnowFlake 算法的思想分析”节:《通俗易懂:如何设计能支撑百万并发的数据库架构?》,是Jack Jiang曾在某项目中实际使用过的代码。

6.3对于美团来说,SnowFlake算法的优缺点


对于美团的业务系统来说,这种方式的优缺点如下。

► 优点:
  • 1)毫秒数在高位,自增序列在低位,整个ID都是趋势递增的;
  • 2)不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的;
  • 3)可以根据自身业务特性分配bit位,非常灵活。

► 缺点:
强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

► 应用举例——Mongdb的objectID:
MongoDB官方文档 ObjectID 可以算作是和snowflake类似方法,通过“时间+机器码+pid+inc”共12个字节,通过4+3+2+3的方式最终标识成一个24长度的十六进制字符。

7、数据库的自增ID对于美团来说,也不合适


以MySQL举例,利用给字段设置auto_increment_incrementauto_increment_offset来保证ID自增,每次业务使用下列SQL读写MySQL得到ID号。
begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_2.png

这种方案的优缺点如下。

优点:

  • 1)非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护;
  • 2)ID号单调自增,可以实现一些对ID有特殊要求的业务。

缺点:

  • 1)强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号;
  • 2)ID发号性能瓶颈限制在单台MySQL的读写性能。

对于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那么整个架构就变成了如下图所示:
IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_3.png

这种必读是后的架构貌似能够满足性能的需求,但有以下几个缺点:

  • 1)系统水平扩展比较困难:比如定义好了步长和机器台数之后,如果要添加机器该怎么做?假设现在只有一台机器发号是1,2,3,4,5(步长是1),这个时候需要扩容机器一台。可以这样做:把第二台机器的初始值设置得比第一台超过很多,比如14(假设在扩容时间之内第一台不可能发到14),同时设置步长为2,那么这台机器下发的号码都是14以后的偶数。然后摘掉第一台,把ID值保留为奇数,比如7,然后修改第一台的步长为2。让它符合我们定义的号段标准,对于这个例子来说就是让第一台以后只能产生奇数。扩容方案看起来复杂吗?貌似还好,现在想象一下如果我们线上有100台机器,这个时候要扩容该怎么做?简直是噩梦。所以系统水平扩展方案复杂难以实现;
  • 2)ID没有了单调递增的特性:只能趋势递增,这个缺点对于一般业务需求不是很重要,可以容忍;
  • 3)数据库压力还是很大:每次获取ID都得读写一次数据库,只能靠堆机器来提高性能。

综合对比上述几种方案,每种方案都不完全符合我们的要求。所以美团建立了Leaf工程(Leaf这个名字是来自德国哲学家、数学家莱布尼茨的一句话: >There are no two identical leaves in the world > “世界上没有两片相同的树叶”),分别在上述第二种(Snowflake)和第三种(数据库自增ID)方案上做了相应的优化,实现了Leaf-snowflake和Leaf-segment方案。接下来,我们详细介绍这两种方案的实现思路。

8、美团的Leaf-segment方案:可生成全局唯一、全局有序的ID


8.1基本原理


美团的Leaf-segment方案,实际上是在上面介绍的数据库自增ID方案上的一种改进方案,它可生成全局唯一、全局有序的ID,可以用于:事务版本号、IM聊天中的增量消息、全局排序等业务中。

美团的Leaf-segment对数据库自增ID方案做了如下改变:

  • 1)原方案每次获取ID都得读写一次数据库,造成数据库压力大。改为利用proxy server批量获取,每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力;
  • 2)各个业务不同的发号需求用biz_tag字段来区分,每个biz-tag的ID获取相互隔离,互不影响。如果以后有性能需求需要对数据库扩容,不需要上述描述的复杂的扩容操作,只需要对biz_tag分库分表就行。

数据库表设计如下:
+-------------+--------------+------+-----+-------------------+-----------------------------+
| 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,大致架构如下图所示:
IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_4.png

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

这种模式有以下优缺点。

优点:

  • 1)Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景;
  • 2)ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求;
  • 3)容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务;
  • 4)可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。

缺点:

  • 1)ID号码不够随机,能够泄露发号数量的信息,不太安全;
  • 2)TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺;
  • 3)DB宕机会造成整个系统不可用。

8.2双buffer优化


对于上述第二个缺点,Leaf-segment方案做了一些优化,简单的说就是:

Leaf 取号段的时机是在号段消耗完的时候进行的,也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间,并且在这期间进来的请求也会因为DB号段没有取回来,导致线程阻塞。如果请求DB的网络和DB的性能稳定,这种情况对系统的影响是不大的,但是假如取DB的时候网络发生抖动,或者DB发生慢查询就会导致整个系统的响应时间变慢。


为此,我们希望DB取号段的过程能够做到无阻塞,不需要在DB取号段的时候阻塞请求线程,即当号段消费到某个点时就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。这样做就可以很大程度上的降低系统的TP999指标。

详细实现如下图所示:
IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_5.png

采用双buffer的方式,Leaf服务内部有两个号段缓存区segment。当前号段已下发10%时,如果下一个号段未更新,则另启一个更新线程去更新下一个号段。当前号段全部下发完后,如果下个号段准备好了则切换到下个号段为当前segment接着下发,循环往复。

主要特性如下:

  • 1)每个biz-tag都有消费速度监控,通常推荐segment长度设置为服务高峰期发号QPS的600倍(10分钟),这样即使DB宕机,Leaf仍能持续发号10-20分钟不受影响;
  • 2)每次请求来临时都会判断下个号段的状态,从而更新此号段,所以偶尔的网络抖动不会影响下个号段的更新。

8.3高可用容灾


对于上述Leaf-segment算法缺点的第三点“DB可用性”问题:我们目前采用一主两从的方式,同时分机房部署,Master和Slave之间采用半同步方式同步数据。同时使用公司Atlas数据库中间件(已开源,改名为 DBProxy)做主从切换。

当然这种方案在一些情况会退化成异步模式,甚至在非常极端情况下仍然会造成数据不一致的情况,但是出现的概率非常小。如果你的系统要保证100%的数据强一致,可以选择使用“类Paxos算法”实现的强一致MySQL方案,如MySQL 5.7前段时间刚刚GA的MySQL Group Replication。但是运维成本和精力都会相应的增加,根据实际情况选型即可。

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_6.png

同时Leaf服务分IDC部署,内部的服务化框架是“MTthrift RPC”。服务调用的时候,根据负载均衡算法会优先调用同机房的Leaf服务。在该IDC内Leaf服务不可用的时候才会选择其他机房的Leaf服务。同时服务治理平台OCTO还提供了针对服务的过载保护、一键截流、动态流量分配等对服务的保护措施。

不过,Leaf-segment方案虽好,但必竟不适用于所有场景。

Leaf-segment方案可以生成趋势递增的ID,同时ID号是可计算的,但不适用于订单ID生成场景。比如竞对在两天中午12点分别下单,通过订单id号相减就能大致计算出公司一天的订单量,这个是不能忍受的。面对这一问题,美团技术团队实现了 Leaf-snowflake这个方案,请继续读下文。

9、美团的Leaf-snowflake方案:可生成全局唯一、局部有序的ID


鉴于上节所说:Leaf-segment方案不适用于美团的订单号这种场景(Leaf-segment方案可以生成趋势递增的ID,同时ID号是可计算的,很容易被猜出美团每日的订单量这种商业秘密),所以Leaf-snowflake方案就应运而生了。

9.1基本原理


IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_7.png

严格来说,Leaf-snowflake方案是Twittersnowflake改进版,它完全沿用snowflake方案的bit位设计(如上图所示),即是“1+41+10+12”的方式组装ID号。

对于workerID的分配,当服务集群数量较小的情况下,完全可以手动配置。Leaf服务规模较大,动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。

Leaf-snowflake是按照下面几个步骤启动的:

  • 1)启动Leaf-snowflake服务,连接Zookeeper,在leaf_forever父节点下检查自己是否已经注册过(是否有该顺序子节点);
  • 2)如果有注册过直接取回自己的workerID(zk顺序节点生成的int类型ID号),启动服务;
  • 3)如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务。

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_8.png

9.2弱依赖ZooKeeper


除了每次会去ZK拿数据以外,也会在本机文件系统上缓存一个workerID文件。当ZooKeeper出现问题,恰好机器出现问题需要重启时,能保证服务能够正常启动。这样做到了对三方组件的弱依赖。一定程度上提高了SLA。

9.3解决时钟问题


因为这种方案依赖时间(见本文“6、美团为什么不直接用Snowflake算法?”一节),如果机器的时钟发生了回拨,那么就会有可能生成重复的ID号,需要解决时钟回退的问题。

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法_9.png

参见上图整个启动流程图,服务启动时首先检查自己是否写过ZooKeeper leaf_forever节点:

  • 1)若写过,则用自身系统时间与leaf_forever/${self}节点记录时间做比较,若小于leaf_forever/${self}时间则认为机器时间发生了大步长回拨,服务启动失败并报警;
  • 2)若未写过,证明是新服务节点,直接创建持久节点leaf_forever/${self}并写入自身系统时间,接下来综合对比其余Leaf节点的系统时间来判断自身系统时间是否准确,具体做法是取leaf_temporary下的所有临时节点(所有运行中的Leaf-snowflake节点)的服务IP:Port,然后通过RPC请求得到所有节点的系统时间,计算sum(time)/nodeSize;
  • 3)若abs( 系统时间-sum(time)/nodeSize ) < 阈值,认为当前系统时间准确,正常启动服务,同时写临时节点leaf_temporary/${self} 维持租约;
  • 4)否则认为本机系统时间发生大步长偏移,启动失败并报警;
  • 5)每隔一段时间(3s)上报自身系统时间写入leaf_forever/${self}。

由于强依赖时钟,对时间的要求比较敏感,在机器工作时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的策略保证,成功避免了对业务造成的影响

PS:网上有人按照文章中Leaf-snowflake方案做了一个开源版本,可以学习一下,项目地址:https://github.com/weizhenyi/leaf-snowflake。作者宣称的测试情况,TPS:1W+/sec,单机190秒生成了200W无重复,单调递增的long型整数ID。

10、本文小结


Leaf在美团点评公司内部服务包含金融、支付交易、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。

目前Leaf的性能在4C8G的机器上QPS能压测到近5w/s,TP999 1ms,已经能够满足大部分的业务的需求。每天提供亿数量级的调用量,作为公司内部公共的基础技术设施,必须保证高SLA和高性能的服务,我们目前还仅仅达到了及格线,还有很多提高的空间。

附录:更多IM开发热门技术文章


新手入门一篇就够:从零开发移动端IM
移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”
移动端IM开发者必读(二):史上最全移动弱网络优化方法总结
从客户端的角度来谈谈移动端IM的消息可靠性和送达机制
现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障
腾讯技术分享:社交网络图片的带宽压缩技术演进之路
小白必读:闲话HTTP短连接中的Session和Token
IM开发基础知识补课:正确理解前置HTTP SSO单点登陆接口的原理
移动端IM中大规模群消息的推送如何保证效率、实时性?
移动端IM开发需要面对的技术问题
开发IM是自己设计协议用字节流好还是字符流好?
请问有人知道语音留言聊天的主流实现方式吗?
IM消息送达保证机制实现(一):保证在线实时消息的可靠投递
IM消息送达保证机制实现(二):保证离线消息的可靠投递
如何保证IM实时消息的“时序性”与“一致性”?
一个低成本确保IM消息时序的方法探讨
IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?
IM群聊消息如此复杂,如何保证不丢不重?
谈谈移动端 IM 开发中登录请求的优化
移动端IM登录时拉取数据如何作到省流量?
浅谈移动端IM的多点登陆和消息漫游原理
完全自已开发的IM该如何设计“失败重试”机制?
通俗易懂:基于集群的移动端IM接入层负载均衡方案分享
微信对网络影响的技术试验及分析(论文全文)
即时通讯系统的原理、技术和应用(技术论文)
开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀
QQ音乐团队分享:Android中的图片压缩技术详解(上篇)
QQ音乐团队分享:Android中的图片压缩技术详解(下篇)
腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率
腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(上篇)
腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(下篇)
如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源
基于社交网络的Yelp是如何实现海量用户图片的无损压缩的?
腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(图片压缩篇)
腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(音视频技术篇)
字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8
全面掌握移动端主流图片格式的特点、性能、调优等
子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践
IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列
微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)
自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)
融云技术分享:解密融云IM产品的聊天消息ID生成策略
>> 更多同类文章 ……

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

标签:ID生成 美团
上一篇:IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略下一篇:IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

本帖已收录至以下技术专辑

推荐方案
评论 13
美团的这两种id生成算法算起来很牛,不过可能不太适合中小场景,
还是有点复杂,
技术不行的程序员可能hold不住。

那个leaf-snowflake值得研究一下,这玩意很实用
签名: 好想把妹!
引用:不要·不要 发表于 2019-09-23 13:12
美团的这两种id生成算法算起来很牛,不过可能不太适合中小场景,
还是有点复杂,
技术不行的程序员可能ho ...

小场景直接自增就够用了,不需要这么大的刀,杀苍蝇。
精辟
签名: 加班、加班、加班
引用:laojichuxin 发表于 2019-09-30 17:19
小场景直接自增就够用了,不需要这么大的刀,杀苍蝇。

活体解刨苍蝇
Leaf-snowflake 多个服务实例是无状态的吗?  如果是,每个机器时钟不能完全保障一致, 业务service的快速的两次请求分别落在不同实例上,是不是就不能保证ID的有序性了?
ID生成更考验架构的设计能力,对于不同场景的理解。  学习
引用:ericqfli 发表于 2020-03-12 10:44
Leaf-snowflake 多个服务实例是无状态的吗?  如果是,每个机器时钟不能完全保障一致, 业务service的快速的两 ...

百度对SnowFlake算法进行了另一种思路的改进,可以学习一下:《IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现
Leaf-segment 是怎么保证全局有序的呢,拿文章中图为例,三个leaf服务分别去了test_tag业务的不同号段分别是1-1000,1001-2000,2001-3000,同时地外提供服务,这时候拿到的 id顺序了能是 1、1001、2、2001、3、2002、1002、4 这个顺序啊?求大神解答
引用:陈萌1 发表于 2022-06-02 00:39
Leaf-segment 是怎么保证全局有序的呢,拿文章中图为例,三个leaf服务分别去了test_tag业务的不同号段分别 ...

不同的leaf服务,对应于不同的业务,用biz_tag字段区分
神秘人  发表于 2 年前
引用:JackJiang 发表于 2022-06-02 11:05
不同的leaf服务,对应于不同的业务,用biz_tag字段区分

如果一个业务的并发数很高,单个leaf服务不够用,需要两个leaf服务,第一个leaf服务号段是1-1000,第二个leaf服务号段是1001-2000。那么这两个leaf服务同时对外提供服务时,会出现发号不是递增的情况呢,1,1001,2,1002,...。单个leaf服务是递增的,但是一个业务如果有多台leaf服务,怎么保证递增呢
引用:qzuser 发表于 2022-06-17 09:47
如果一个业务的并发数很高,单个leaf服务不够用,需要两个leaf服务,第一个leaf服务号段是1-1000,第二个 ...

这样肯定就没法保证递增了,肯定还得有个网关一样的东西,统一调用
引用:JackJiang 发表于 2022-06-17 10:51
这样肯定就没法保证递增了,肯定还得有个网关一样的东西,统一调用

路由层进行biz_tag转发的话,其实还是会有单点问题,感觉想要单调递增且全局唯一的id绕不开单点了。
我也感觉,这只是趋势递增,没法保证全局和会话递增吧。如果要递增,同业务多server肯定要访问同一个leaf。与数据库单点相比,只是说增加了号段缓存,效率能更高吧
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部