范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Leaf美团点评分布式ID生成系统

  在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求;特别一点的如订单、骑手、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。概括下来,那业务系统对ID号的要求有哪些呢?全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。
  上述123对应三类不同的场景,3和4需求还是互斥的,无法使用同一个方案满足。
  同时除了对ID号码自身的要求,业务还对ID号生成系统的可用性要求极高,想象一下,如果ID生成系统瘫痪,整个美团点评支付、优惠券发券、骑手派单等关键动作都无法执行,这就会带来一场灾难。
  由此总结下一个ID生成系统应该做到如下几点:平均延迟和TP999延迟都要尽可能低;可用性5个9;高QPS。
  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太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。信息不安全:基于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的无序性可能会引起数据位置频繁变动,严重影响性能。
  类snowflake方案
  这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflake中的64-bit分别表示如下图(图片来自网络)所示:
  image
  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都是不同的。
  这种方式的优缺点是:
  优点:毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。可以根据自身业务特性分配bit位,非常灵活。
  缺点:强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
  应用举例Mongdb objectID
  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;
  image
  这种方案的优缺点如下:
  优点:非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。ID号单调自增,可以实现一些对ID有特殊要求的业务。
  缺点:强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。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那么整个架构就变成了如下图所示:
  image
  这种架构貌似能够满足性能的需求,但有以下几个缺点:系统水平扩展比较困难,比如定义好了步长和机器台数之后,如果要添加机器该怎么做?假设现在只有一台机器发号是1,2,3,4,5(步长是1),这个时候需要扩容机器一台。可以这样做:把第二台机器的初始值设置得比第一台超过很多,比如14(假设在扩容时间之内第一台不可能发到14),同时设置步长为2,那么这台机器下发的号码都是14以后的偶数。然后摘掉第一台,把ID值保留为奇数,比如7,然后修改第一台的步长为2。让它符合我们定义的号段标准,对于这个例子来说就是让第一台以后只能产生奇数。扩容方案看起来复杂吗?貌似还好,现在想象一下如果我们线上有100台机器,这个时候要扩容该怎么做?简直是噩梦。所以系统水平扩展方案复杂难以实现。ID没有了单调递增的特性,只能趋势递增,这个缺点对于一般业务需求不是很重要,可以容忍。数据库压力还是很大,每次获取ID都得读写一次数据库,只能靠堆机器来提高性能。
  Leaf这个名字是来自德国哲学家、数学家莱布尼茨的一句话: >There are no two identical leaves in the world > "世界上没有两片相同的树叶"
  综合对比上述几种方案,每种方案都不完全符合我们的要求。所以Leaf分别在上述第二种和第三种方案上做了相应的优化,实现了Leaf-segment和Leaf-snowflake方案。
  Leaf-segment数据库方案
  第一种Leaf-segment方案,在使用数据库的方案上,做了如下改变: - 原方案每次获取ID都得读写一次数据库,造成数据库压力大。改为利用proxy server批量获取,每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。 - 各个业务不同的发号需求用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,大致架构如下图所示:
  image
  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服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。
  缺点:ID号码不够随机,能够泄露发号数量的信息,不太安全。TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。DB宕机会造成整个系统不可用。
  双buffer优化
  对于第二个缺点,Leaf-segment做了一些优化,简单的说就是:
  Leaf 取号段的时机是在号段消耗完的时候进行的,也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间,并且在这期间进来的请求也会因为DB号段没有取回来,导致线程阻塞。如果请求DB的网络和DB的性能稳定,这种情况对系统的影响是不大的,但是假如取DB的时候网络发生抖动,或者DB发生慢查询就会导致整个系统的响应时间变慢。
  为此,我们希望DB取号段的过程能够做到无阻塞,不需要在DB取号段的时候阻塞请求线程,即当号段消费到某个点时就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。这样做就可以很大程度上的降低系统的TP999指标。详细实现如下图所示:
  image
  采用双buffer的方式,Leaf服务内部有两个号段缓存区segment。当前号段已下发10%时,如果下一个号段未更新,则另启一个更新线程去更新下一个号段。当前号段全部下发完后,如果下个号段准备好了则切换到下个号段为当前segment接着下发,循环往复。每个biz-tag都有消费速度监控,通常推荐segment长度设置为服务高峰期发号QPS的600倍(10分钟),这样即使DB宕机,Leaf仍能持续发号10-20分钟不受影响。每次请求来临时都会判断下个号段的状态,从而更新此号段,所以偶尔的网络抖动不会影响下个号段的更新。
  Leaf高可用容灾
  对于第三点"DB可用性"问题,我们目前采用一主两从的方式,同时分机房部署,Master和Slave之间采用半同步方式[5]同步数据。同时使用公司Atlas数据库中间件(已开源,改名为DBProxy)做主从切换。当然这种方案在一些情况会退化成异步模式,甚至在非常极端情况下仍然会造成数据不一致的情况,但是出现的概率非常小。如果你的系统要保证100%的数据强一致,可以选择使用"类Paxos算法"实现的强一致MySQL方案,如MySQL 5.7前段时间刚刚GA的MySQL Group Replication。但是运维成本和精力都会相应的增加,根据实际情况选型即可。
  image
  同时Leaf服务分IDC部署,内部的服务化框架是"MTthrift RPC"。服务调用的时候,根据负载均衡算法会优先调用同机房的Leaf服务。在该IDC内Leaf服务不可用的时候才会选择其他机房的Leaf服务。同时服务治理平台OCTO还提供了针对服务的过载保护、一键截流、动态流量分配等对服务的保护措施。
  Leaf-segment方案可以生成趋势递增的ID,同时ID号是可计算的,不适用于订单ID生成场景,比如竞对在两天中午12点分别下单,通过订单id号相减就能大致计算出公司一天的订单量,这个是不能忍受的。面对这一问题,我们提供了 Leaf-snowflake方案。
  image
  Leaf-snowflake方案完全沿用snowflake方案的bit位设计,即是"1+41+10+12"的方式组装ID号。对于workerID的分配,当服务集群数量较小的情况下,完全可以手动配置。Leaf服务规模较大,动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。Leaf-snowflake是按照下面几个步骤启动的:启动Leaf-snowflake服务,连接Zookeeper,在leaf_forever父节点下检查自己是否已经注册过(是否有该顺序子节点)。如果有注册过直接取回自己的workerID(zk顺序节点生成的int类型ID号),启动服务。如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务。
  image
  弱依赖ZooKeeper
  除了每次会去ZK拿数据以外,也会在本机文件系统上缓存一个workerID文件。当ZooKeeper出现问题,恰好机器出现问题需要重启时,能保证服务能够正常启动。这样做到了对三方组件的弱依赖。一定程度上提高了SLA
  解决时钟问题
  因为这种方案依赖时间,如果机器的时钟发生了回拨,那么就会有可能生成重复的ID号,需要解决时钟回退的问题。
  image
  参见上图整个启动流程图,服务启动时首先检查自己是否写过ZooKeeper leaf_forever节点:若写过,则用自身系统时间与leaf_forever/${self}节点记录时间做比较,若小于leaf_forever/${self}时间则认为机器时间发生了大步长回拨,服务启动失败并报警。若未写过,证明是新服务节点,直接创建持久节点leaf_forever/${self}并写入自身系统时间,接下来综合对比其余Leaf节点的系统时间来判断自身系统时间是否准确,具体做法是取leaf_temporary下的所有临时节点(所有运行中的Leaf-snowflake节点)的服务IP:Port,然后通过RPC请求得到所有节点的系统时间,计算sum(time)/nodeSize。若abs( 系统时间-sum(time)/nodeSize ) < 阈值,认为当前系统时间准确,正常启动服务,同时写临时节点leaf_temporary/${self} 维持租约。否则认为本机系统时间发生大步长偏移,启动失败并报警。每隔一段时间(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的策略保证,成功避免了对业务造成的影响。
  Leaf在美团点评公司内部服务包含金融、支付交易、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。目前Leaf的性能在4C8G的机器上QPS能压测到近5w/s,TP999 1ms,已经能够满足大部分的业务的需求。每天提供亿数量级的调用量,作为公司内部公共的基础技术设施,必须保证高SLA和高性能的服务,我们目前还仅仅达到了及格线,还有很多提高的空间。

魅族手机要凉了?又或者是一次新生的机会魅族与小米,称得上同时代的产物。相比小米的膨胀式发展而言,魅族多少显然有点落魄。这不,有关魅族的传言再次风声四起。有人传言魅族科技高级副总裁李楠已经离职,并得到了接近李楠的消息人士格兰仕赢了!刚刚,八部门联合出击,电商二选一乱象迎来终结二选一由来已久,但一直都是暗地里的潜规则,很少会拿到面上来说,所以关于二选一事件被人熟知的并不多,最为轰动的还要数当年的3Q大战。2012年11月3日,腾讯宣布在装有360软件的电名门修谱2021互联网推动家谱文化发展,加速高质量家谱形成修家谱今年是我国实施十四五规划的开局之年。党的十九届五中全会提出,我国已转向高质量发展阶段,十四五时期经济社会发展要以推动高质量发展为主题。家谱作为中国非常重要的传统文化,一直都是名门修谱家谱中,姓和氏的含义,取名有讲究修家谱随着清明节越来越近,很多家族陆续启动修谱。可是,大家在修家谱的时候,姓氏源流的查考是很重要的一个环节。对于每个不同的姓氏家族,氏族发展起源自然不一样。但是又有几个人知道,家谱名门修谱绿色清明,修家谱和祭祀同样重要清明修谱清明节这一天,很多人会不远万里回到自己的故乡祭祖扫墓,为的就是向祖先表达自己对家族对先人的敬畏。当然清明族人团聚聊得最多的除了祖先的励志故事外,翻开家谱,听长辈们讲解家族的名门修谱清明修谱,口口相传的家族信息,要不要修?清明修谱清明修谱是很多家族用来纪念先祖的一种方式之一,家谱记录了一个家族的发展,但是除了家谱以外,也有很多和家谱相关的故事靠着口口相传的形式,一代又一代的流传了下来。这些没有被写入名门修谱为什么修谱要选在清明节前后修?修谱从古至今,华夏子孙以姓氏作为家族延续的标志,以家谱作为承载家族历史的载体。在五千年悠久的历史长河中,成就了一个又一个不同的家族文化。随着清明节的脚步慢慢临近,很多家族启动家族修名门修谱互联网家谱的普及,改变了传统修谱的习惯传统修谱流程家谱文化,是中国传统文化的重要组成部分。近几年随着大家物质生活的提高,家谱文化又在民间慢慢的兴起了起来。对于修谱,很多人的印象一定是一门传统手艺。殊不知,现在在信息时代名门修谱家谱传承好家风,影响子女的一生好家风现今,家风成为当下最热搜的词汇之一,为什么家风二字会如此受年轻人欢迎,其魅力在何处?要如何形成良好家风呢?家风事关一个家庭的当前和未来,它很大程度上影响着一个家庭中后辈的健康名门修谱清代一届有26000多名进士,有多少会修家谱?传统修谱修家谱在很多人的眼中,是一件光宗耀祖的事情。之所以要修,除了要做家族文化的传承外,更重要的是要把自己的光辉事迹也写入家谱之中。清代文人考科举,也是光耀门楣的事。尤其是在科举碧桂园发布凤凰云平台在线选房购房省时省力早上起床打开手机浏览新闻信息,上班用手机打车或是扫码乘地铁,一日三餐用手机现场支付或叫外卖,晚上还要在手机上购物移动互联网时代,我们大量的日常生活所需都可以在手机上快速实现。那么,
四川地震频发的现象背后和应对地震灾后的个人措施自2008年5月12日起,四川发生地震的信息一直牵动着全国人们的心。对于四川发生过的数次地震,许多人存在很多疑问和思考。实际上相比东边邻国日本,以及中国台湾岛,四川地震的频率还真没rpc端口?http端口?背景今天翻看falcon的监控组件之HBS(HeartbeatServer)的时候有个知识点如果hbs的配置文件维持默认,rpc端口就是6030,http端口是6031,agent天地国亲师是中华封建落后的糟粕还是世界社会文明的必须?许多人家里挂着天地国亲师,不管是有文化还是没文化,不管是知道其意思,还是不知道其意思。反正大家挂着,我们也挂着,渐渐成为了一个民族文化的传统和标志。今天我们作一详细了解,首先说排在无证大巴车黑车运营,行走在悬崖钢丝上的逐利说到大巴车经营,如果回过去一二十年,用五个字来形容这份工作,那就是丰厚的利润。而今,由于国家对大巴车经营管控越来越严格,安全措施越来越加强,各项保险支出,安保设备投入,以及网约车,还在用ifelse你就out了延续一贯风格,简单直接,深入浅出,融会贯通就靠你自己了一般来说,我们的后台系统都有角色的概念,不同管理员权限不一样,能够执行的操作也不同。系统管理员开发人员运维人员测试人员比如一个一次生产中使用list。parallelStream()血的教训项目背景最近在完成一项历史数据的清洗工作,主要就是把以前的垃圾数据调用第三方渠道来完成一次认证,迫于监管的淫威吧。但是第三方的qps又没有多高,政府机关的接口你懂得。于是,代码随心InfluxDB简单上手(一)背景最近公司的监控项目中用到时序数据库,现在简单介绍学习以下,后面再详细研究。什么是时序数据?Atimeseriesdatabase(TSDB)isasoftwaresystemt程序员怎么会不知道C10K问题呢?今天简单说下C10K的问题,关于这个问题Ruby的作者松本行弘在代码的未来云计算时代的编程一章中有详细的阐述,有兴趣的同学可以直接去购买。本人已经在某淘,购买一本哈哈哈在做技术规划13缓存的使用姿势(一)如何选择缓存的读写策略?今天讲讲缓存的读写策略,你可能觉得很简单啊,查询缓存,不存在则查询数据库,然后回种缓存。实际上针对不同的业务场景缓存的读写策略是不同的。选择读写策略的时候,需要考虑如下因素缓存中是10发号器如何保证分库分表后ID的全局唯一性?前面两节中,我们了解了分布式存储的两个核心数据冗余和数据分片架构图如下我们通过分库分表和主从读写分离的方式解决了数据库扩展的问题,但是数据库采用了分库分表之后,也带来了很多限制,比嵌入式中自定义协议的一些典型例子关于自定义协议,对于会的人很简单,对于不会的人就摸不着头脑。下面分享一些关于自定义协议的笔记。什么是协议?协议这个概念我觉得挺杂的。就像嵌入式的概念一样,说法不一,比如大家常常争论