Leaf——美團(tuán)點(diǎn)評(píng)分布式ID生成系統(tǒng)

在復(fù)雜分布式系統(tǒng)中夕土,往往需要對(duì)大量的數(shù)據(jù)和消息進(jìn)行唯一標(biāo)識(shí)。如在美團(tuán)點(diǎn)評(píng)的金融瘟判、支付怨绣、餐飲、酒店拷获、貓眼電影等產(chǎn)品的系統(tǒng)中篮撑,數(shù)據(jù)日漸增長(zhǎng),對(duì)數(shù)據(jù)分庫(kù)分表后需要有一個(gè)唯一ID來(lái)標(biāo)識(shí)一條數(shù)據(jù)或消息刀诬,數(shù)據(jù)庫(kù)的自增ID顯然不能滿足需求咽扇;特別一點(diǎn)的如訂單、騎手陕壹、優(yōu)惠券也都需要有唯一ID做標(biāo)識(shí)质欲。此時(shí)一個(gè)能夠生成全局唯一ID的系統(tǒng)是非常必要的。概括下來(lái)糠馆,那業(yè)務(wù)系統(tǒng)對(duì)ID號(hào)的要求有哪些呢嘶伟?

  1. 全局唯一性:不能出現(xiàn)重復(fù)的ID號(hào),既然是唯一標(biāo)識(shí)又碌,這是最基本的要求九昧。
  2. 趨勢(shì)遞增:在MySQL InnoDB引擎中使用的是聚集索引,由于多數(shù)RDBMS使用B-tree的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)索引數(shù)據(jù)毕匀,在主鍵的選擇上面我們應(yīng)該盡量使用有序的主鍵保證寫(xiě)入性能铸鹰。
  3. 單調(diào)遞增:保證下一個(gè)ID一定大于上一個(gè)ID,例如事務(wù)版本號(hào)皂岔、IM增量消息蹋笼、排序等特殊需求。
  4. 信息安全:如果ID是連續(xù)的躁垛,惡意用戶的扒取工作就非常容易做了剖毯,直接按照順序下載指定URL即可;如果是訂單號(hào)就更危險(xiǎn)了教馆,競(jìng)對(duì)可以直接知道我們一天的單量逊谋。所以在一些應(yīng)用場(chǎng)景下,會(huì)需要ID無(wú)規(guī)則土铺、不規(guī)則胶滋。

上述123對(duì)應(yīng)三類不同的場(chǎng)景板鬓,3和4需求還是互斥的,無(wú)法使用同一個(gè)方案滿足镀钓。

同時(shí)除了對(duì)ID號(hào)碼自身的要求穗熬,業(yè)務(wù)還對(duì)ID號(hào)生成系統(tǒng)的可用性要求極高镀迂,想象一下丁溅,如果ID生成系統(tǒng)癱瘓,整個(gè)美團(tuán)點(diǎn)評(píng)支付探遵、優(yōu)惠券發(fā)券窟赏、騎手派單等關(guān)鍵動(dòng)作都無(wú)法執(zhí)行,這就會(huì)帶來(lái)一場(chǎng)災(zāi)難箱季。

由此總結(jié)下一個(gè)ID生成系統(tǒng)應(yīng)該做到如下幾點(diǎn):

  1. 平均延遲和TP999延遲都要盡可能低涯穷;
  2. 可用性5個(gè)9;
  3. 高QPS藏雏。

UUID

UUID(Universally Unique Identifier)的標(biāo)準(zhǔn)型式包含32個(gè)16進(jìn)制數(shù)字拷况,以連字號(hào)分為五段,形式為8-4-4-4-12的36個(gè)字符掘殴,示例:550e8400-e29b-41d4-a716-446655440000赚瘦,到目前為止業(yè)界一共有5種方式生成UUID,詳情見(jiàn)IETF發(fā)布的UUID規(guī)范 A Universally Unique IDentifier (UUID) URN Namespace奏寨。

優(yōu)點(diǎn):

  • 性能非常高:本地生成起意,沒(méi)有網(wǎng)絡(luò)消耗。

缺點(diǎn):

  • 不易于存儲(chǔ):UUID太長(zhǎng)病瞳,16字節(jié)128位揽咕,通常以36長(zhǎng)度的字符串表示,很多場(chǎng)景不適用套菜。

  • 信息不安全:基于MAC地址生成UUID的算法可能會(huì)造成MAC地址泄露亲善,這個(gè)漏洞曾被用于尋找梅麗莎病毒的制作者位置。

  • ID作為主鍵時(shí)在特定的環(huán)境會(huì)存在一些問(wèn)題逗柴,比如做DB主鍵的場(chǎng)景下蛹头,UUID就非常不適用:

    ① MySQL官方有明確的建議主鍵要盡量越短越好[4],36個(gè)字符長(zhǎng)度的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***.

② 對(duì)MySQL索引不利:如果作為數(shù)據(jù)庫(kù)主鍵掘而,在InnoDB引擎下,UUID的無(wú)序性可能會(huì)引起數(shù)據(jù)位置頻繁變動(dòng)于购,嚴(yán)重影響性能袍睡。

類snowflake方案

這種方案大致來(lái)說(shuō)是一種以劃分命名空間(UUID也算,由于比較常見(jiàn)肋僧,所以單獨(dú)分析)來(lái)生成ID的一種算法斑胜,這種方案把64-bit分別劃分成多段控淡,分開(kāi)來(lái)標(biāo)示機(jī)器、時(shí)間等止潘,比如在snowflake中的64-bit分別表示如下圖(圖片來(lái)自網(wǎng)絡(luò))所示:

41-bit的時(shí)間可以表示(1L<<41)/(1000L360024*365)=69年的時(shí)間掺炭,10-bit機(jī)器可以分別表示1024臺(tái)機(jī)器。如果我們對(duì)IDC劃分有需求凭戴,還可以將10-bit分5-bit給IDC涧狮,分5-bit給工作機(jī)器。這樣就可以表示32個(gè)IDC么夫,每個(gè)IDC下可以有32臺(tái)機(jī)器者冤,可以根據(jù)自身需求定義。12個(gè)自增序列號(hào)可以表示2^12個(gè)ID档痪,理論上snowflake方案的QPS約為409.6w/s涉枫,這種分配方式可以保證在任何一個(gè)IDC的任何一臺(tái)機(jī)器在任意毫秒內(nèi)生成的ID都是不同的。

這種方式的優(yōu)缺點(diǎn)是:

優(yōu)點(diǎn):

  • 毫秒數(shù)在高位腐螟,自增序列在低位愿汰,整個(gè)ID都是趨勢(shì)遞增的。
  • 不依賴數(shù)據(jù)庫(kù)等第三方系統(tǒng)乐纸,以服務(wù)的方式部署衬廷,穩(wěn)定性更高,生成ID的性能也是非常高的锯仪。
  • 可以根據(jù)自身業(yè)務(wù)特性分配bit位泵督,非常靈活。

缺點(diǎn):

  • 強(qiáng)依賴機(jī)器時(shí)鐘庶喜,如果機(jī)器上時(shí)鐘回?fù)苄±埃瑫?huì)導(dǎo)致發(fā)號(hào)重復(fù)或者服務(wù)會(huì)處于不可用狀態(tài)。

應(yīng)用舉例Mongdb objectID

MongoDB官方文檔 ObjectID可以算作是和snowflake類似方法久窟,通過(guò)“時(shí)間+機(jī)器碼+pid+inc”共12個(gè)字節(jié)秩冈,通過(guò)4+3+2+3的方式最終標(biāo)識(shí)成一個(gè)24長(zhǎng)度的十六進(jìn)制字符。

數(shù)據(jù)庫(kù)生成

以MySQL舉例斥扛,利用給字段設(shè)置auto_increment_incrementauto_increment_offset來(lái)保證ID自增入问,每次業(yè)務(wù)使用下列SQL讀寫(xiě)MySQL得到ID號(hào)。

begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;

image

這種方案的優(yōu)缺點(diǎn)如下:

優(yōu)點(diǎn):

  • 非常簡(jiǎn)單稀颁,利用現(xiàn)有數(shù)據(jù)庫(kù)系統(tǒng)的功能實(shí)現(xiàn)芬失,成本小,有DBA專業(yè)維護(hù)匾灶。
  • ID號(hào)單調(diào)自增棱烂,可以實(shí)現(xiàn)一些對(duì)ID有特殊要求的業(yè)務(wù)。

缺點(diǎn):

  • 強(qiáng)依賴DB阶女,當(dāng)DB異常時(shí)整個(gè)系統(tǒng)不可用颊糜,屬于致命問(wèn)題哩治。配置主從復(fù)制可以盡可能的增加可用性,但是數(shù)據(jù)一致性在特殊情況下難以保證衬鱼。主從切換時(shí)的不一致可能會(huì)導(dǎo)致重復(fù)發(fā)號(hào)业筏。
  • ID發(fā)號(hào)性能瓶頸限制在單臺(tái)MySQL的讀寫(xiě)性能。

對(duì)于MySQL性能問(wèn)題鸟赫,可用如下方案解決:在分布式系統(tǒng)中我們可以多部署幾臺(tái)機(jī)器蒜胖,每臺(tái)機(jī)器設(shè)置不同的初始值,且步長(zhǎng)和機(jī)器數(shù)相等惯疙。比如有兩臺(tái)機(jī)器翠勉。設(shè)置步長(zhǎng)step為2妖啥,TicketServer1的初始值為1(1霉颠,3,5荆虱,7蒿偎,9,11…)怀读、TicketServer2的初始值為2(2诉位,4,6菜枷,8苍糠,10…)。這是Flickr團(tuán)隊(duì)在2010年撰文介紹的一種主鍵生成策略(Ticket Servers: Distributed Unique Primary Keys on the Cheap )啤誊。如下所示岳瞭,為了實(shí)現(xiàn)上述方案分別設(shè)置兩臺(tái)機(jī)器對(duì)應(yīng)的參數(shù),TicketServer1從1開(kāi)始發(fā)號(hào)蚊锹,TicketServer2從2開(kāi)始發(fā)號(hào)瞳筏,兩臺(tái)機(jī)器每次發(fā)號(hào)之后都遞增2。

TicketServer1:
auto-increment-increment = 2
auto-increment-offset = 1

TicketServer2:
auto-increment-increment = 2
auto-increment-offset = 2

假設(shè)我們要部署N臺(tái)機(jī)器牡昆,步長(zhǎng)需設(shè)置為N姚炕,每臺(tái)的初始值依次為0,1,2…N-1那么整個(gè)架構(gòu)就變成了如下圖所示:

image

這種架構(gòu)貌似能夠滿足性能的需求,但有以下幾個(gè)缺點(diǎn):

  • 系統(tǒng)水平擴(kuò)展比較困難丢烘,比如定義好了步長(zhǎng)和機(jī)器臺(tái)數(shù)之后柱宦,如果要添加機(jī)器該怎么做?假設(shè)現(xiàn)在只有一臺(tái)機(jī)器發(fā)號(hào)是1,2,3,4,5(步長(zhǎng)是1)播瞳,這個(gè)時(shí)候需要擴(kuò)容機(jī)器一臺(tái)掸刊。可以這樣做:把第二臺(tái)機(jī)器的初始值設(shè)置得比第一臺(tái)超過(guò)很多狐史,比如14(假設(shè)在擴(kuò)容時(shí)間之內(nèi)第一臺(tái)不可能發(fā)到14)痒给,同時(shí)設(shè)置步長(zhǎng)為2说墨,那么這臺(tái)機(jī)器下發(fā)的號(hào)碼都是14以后的偶數(shù)。然后摘掉第一臺(tái)苍柏,把ID值保留為奇數(shù)尼斧,比如7,然后修改第一臺(tái)的步長(zhǎng)為2试吁。讓它符合我們定義的號(hào)段標(biāo)準(zhǔn)棺棵,對(duì)于這個(gè)例子來(lái)說(shuō)就是讓第一臺(tái)以后只能產(chǎn)生奇數(shù)。擴(kuò)容方案看起來(lái)復(fù)雜嗎熄捍?貌似還好烛恤,現(xiàn)在想象一下如果我們線上有100臺(tái)機(jī)器,這個(gè)時(shí)候要擴(kuò)容該怎么做余耽?簡(jiǎn)直是噩夢(mèng)缚柏。所以系統(tǒng)水平擴(kuò)展方案復(fù)雜難以實(shí)現(xiàn)。
  • ID沒(méi)有了單調(diào)遞增的特性碟贾,只能趨勢(shì)遞增币喧,這個(gè)缺點(diǎn)對(duì)于一般業(yè)務(wù)需求不是很重要,可以容忍袱耽。
  • 數(shù)據(jù)庫(kù)壓力還是很大杀餐,每次獲取ID都得讀寫(xiě)一次數(shù)據(jù)庫(kù),只能靠堆機(jī)器來(lái)提高性能朱巨。

Leaf這個(gè)名字是來(lái)自德國(guó)哲學(xué)家史翘、數(shù)學(xué)家萊布尼茨的一句話: >There are no two identical leaves in the world > “世界上沒(méi)有兩片相同的樹(shù)葉”

綜合對(duì)比上述幾種方案,每種方案都不完全符合我們的要求冀续。所以Leaf分別在上述第二種和第三種方案上做了相應(yīng)的優(yōu)化琼讽,實(shí)現(xiàn)了Leaf-segment和Leaf-snowflake方案。

Leaf-segment數(shù)據(jù)庫(kù)方案

第一種Leaf-segment方案沥阳,在使用數(shù)據(jù)庫(kù)的方案上跨琳,做了如下改變: - 原方案每次獲取ID都得讀寫(xiě)一次數(shù)據(jù)庫(kù),造成數(shù)據(jù)庫(kù)壓力大桐罕。改為利用proxy server批量獲取脉让,每次獲取一個(gè)segment(step決定大小)號(hào)段的值。用完之后再去數(shù)據(jù)庫(kù)獲取新的號(hào)段功炮,可以大大的減輕數(shù)據(jù)庫(kù)的壓力溅潜。 - 各個(gè)業(yè)務(wù)不同的發(fā)號(hào)需求用biz_tag字段來(lái)區(qū)分,每個(gè)biz-tag的ID獲取相互隔離薪伏,互不影響滚澜。如果以后有性能需求需要對(duì)數(shù)據(jù)庫(kù)擴(kuò)容,不需要上述描述的復(fù)雜的擴(kuò)容操作嫁怀,只需要對(duì)biz_tag分庫(kù)分表就行设捐。

數(shù)據(jù)庫(kù)表設(shè)計(jì)如下:

+-------------+--------------+------+-----+-------------------+-----------------------------+
| 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 |
+-------------+--------------+------+-----+-------------------+-----------------------------+

重要字段說(shuō)明:biz_tag用來(lái)區(qū)分業(yè)務(wù)借浊,max_id表示該biz_tag目前所被分配的ID號(hào)段的最大值,step表示每次分配的號(hào)段長(zhǎng)度萝招。原來(lái)獲取ID每次都需要寫(xiě)數(shù)據(jù)庫(kù)蚂斤,現(xiàn)在只需要把step設(shè)置得足夠大,比如1000槐沼。那么只有當(dāng)1000個(gè)號(hào)被消耗完了之后才會(huì)去重新讀寫(xiě)一次數(shù)據(jù)庫(kù)曙蒸。讀寫(xiě)數(shù)據(jù)庫(kù)的頻率從1減小到了1/step,大致架構(gòu)如下圖所示:

image

test_tag在第一臺(tái)Leaf機(jī)器上是11000的號(hào)段岗钩,當(dāng)這個(gè)號(hào)段用完時(shí)纽窟,會(huì)去加載另一個(gè)長(zhǎng)度為step=1000的號(hào)段,假設(shè)另外兩臺(tái)號(hào)段都沒(méi)有更新兼吓,這個(gè)時(shí)候第一臺(tái)機(jī)器新加載的號(hào)段就應(yīng)該是30014000臂港。同時(shí)數(shù)據(jù)庫(kù)對(duì)應(yīng)的biz_tag這條數(shù)據(jù)的max_id會(huì)從3000被更新成4000,更新號(hào)段的SQL語(yǔ)句如下:

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

這種模式有以下優(yōu)缺點(diǎn):

優(yōu)點(diǎn):

  • Leaf服務(wù)可以很方便的線性擴(kuò)展周蹭,性能完全能夠支撐大多數(shù)業(yè)務(wù)場(chǎng)景趋艘。
  • ID號(hào)碼是趨勢(shì)遞增的8byte的64位數(shù)字,滿足上述數(shù)據(jù)庫(kù)存儲(chǔ)的主鍵要求凶朗。
  • 容災(zāi)性高:Leaf服務(wù)內(nèi)部有號(hào)段緩存,即使DB宕機(jī)显拳,短時(shí)間內(nèi)Leaf仍能正常對(duì)外提供服務(wù)棚愤。
  • 可以自定義max_id的大小,非常方便業(yè)務(wù)從原有的ID方式上遷移過(guò)來(lái)杂数。

缺點(diǎn):

  • ID號(hào)碼不夠隨機(jī)宛畦,能夠泄露發(fā)號(hào)數(shù)量的信息,不太安全揍移。
  • TP999數(shù)據(jù)波動(dòng)大次和,當(dāng)號(hào)段使用完之后還是會(huì)hang在更新數(shù)據(jù)庫(kù)的I/O上,tg999數(shù)據(jù)會(huì)出現(xiàn)偶爾的尖刺那伐。
  • DB宕機(jī)會(huì)造成整個(gè)系統(tǒng)不可用踏施。

雙buffer優(yōu)化

對(duì)于第二個(gè)缺點(diǎn),Leaf-segment做了一些優(yōu)化罕邀,簡(jiǎn)單的說(shuō)就是:

Leaf 取號(hào)段的時(shí)機(jī)是在號(hào)段消耗完的時(shí)候進(jìn)行的畅形,也就意味著號(hào)段臨界點(diǎn)的ID下發(fā)時(shí)間取決于下一次從DB取回號(hào)段的時(shí)間,并且在這期間進(jìn)來(lái)的請(qǐng)求也會(huì)因?yàn)镈B號(hào)段沒(méi)有取回來(lái)诉探,導(dǎo)致線程阻塞日熬。如果請(qǐng)求DB的網(wǎng)絡(luò)和DB的性能穩(wěn)定,這種情況對(duì)系統(tǒng)的影響是不大的肾胯,但是假如取DB的時(shí)候網(wǎng)絡(luò)發(fā)生抖動(dòng)竖席,或者DB發(fā)生慢查詢就會(huì)導(dǎo)致整個(gè)系統(tǒng)的響應(yīng)時(shí)間變慢耘纱。

為此,我們希望DB取號(hào)段的過(guò)程能夠做到無(wú)阻塞毕荐,不需要在DB取號(hào)段的時(shí)候阻塞請(qǐng)求線程揣炕,即當(dāng)號(hào)段消費(fèi)到某個(gè)點(diǎn)時(shí)就異步的把下一個(gè)號(hào)段加載到內(nèi)存中。而不需要等到號(hào)段用盡的時(shí)候才去更新號(hào)段东跪。這樣做就可以很大程度上的降低系統(tǒng)的TP999指標(biāo)畸陡。詳細(xì)實(shí)現(xiàn)如下圖所示:

image

采用雙buffer的方式,Leaf服務(wù)內(nèi)部有兩個(gè)號(hào)段緩存區(qū)segment虽填。當(dāng)前號(hào)段已下發(fā)10%時(shí)丁恭,如果下一個(gè)號(hào)段未更新,則另啟一個(gè)更新線程去更新下一個(gè)號(hào)段斋日。當(dāng)前號(hào)段全部下發(fā)完后牲览,如果下個(gè)號(hào)段準(zhǔn)備好了則切換到下個(gè)號(hào)段為當(dāng)前segment接著下發(fā),循環(huán)往復(fù)恶守。

  • 每個(gè)biz-tag都有消費(fèi)速度監(jiān)控第献,通常推薦segment長(zhǎng)度設(shè)置為服務(wù)高峰期發(fā)號(hào)QPS的600倍(10分鐘),這樣即使DB宕機(jī)兔港,Leaf仍能持續(xù)發(fā)號(hào)10-20分鐘不受影響庸毫。

  • 每次請(qǐng)求來(lái)臨時(shí)都會(huì)判斷下個(gè)號(hào)段的狀態(tài),從而更新此號(hào)段衫樊,所以偶爾的網(wǎng)絡(luò)抖動(dòng)不會(huì)影響下個(gè)號(hào)段的更新飒赃。

Leaf高可用容災(zāi)

對(duì)于第三點(diǎn)“DB可用性”問(wèn)題,我們目前采用一主兩從的方式科侈,同時(shí)分機(jī)房部署载佳,Master和Slave之間采用半同步方式[5]同步數(shù)據(jù)。同時(shí)使用公司Atlas數(shù)據(jù)庫(kù)中間件(已開(kāi)源臀栈,改名為DBProxy)做主從切換蔫慧。當(dāng)然這種方案在一些情況會(huì)退化成異步模式,甚至在非常極端情況下仍然會(huì)造成數(shù)據(jù)不一致的情況权薯,但是出現(xiàn)的概率非常小姑躲。如果你的系統(tǒng)要保證100%的數(shù)據(jù)強(qiáng)一致,可以選擇使用“類Paxos算法”實(shí)現(xiàn)的強(qiáng)一致MySQL方案崭闲,如MySQL 5.7前段時(shí)間剛剛GA的MySQL Group Replication肋联。但是運(yùn)維成本和精力都會(huì)相應(yīng)的增加,根據(jù)實(shí)際情況選型即可刁俭。

image

同時(shí)Leaf服務(wù)分IDC部署橄仍,內(nèi)部的服務(wù)化框架是“MTthrift RPC”。服務(wù)調(diào)用的時(shí)候,根據(jù)負(fù)載均衡算法會(huì)優(yōu)先調(diào)用同機(jī)房的Leaf服務(wù)侮繁。在該IDC內(nèi)Leaf服務(wù)不可用的時(shí)候才會(huì)選擇其他機(jī)房的Leaf服務(wù)虑粥。同時(shí)服務(wù)治理平臺(tái)OCTO還提供了針對(duì)服務(wù)的過(guò)載保護(hù)、一鍵截流宪哩、動(dòng)態(tài)流量分配等對(duì)服務(wù)的保護(hù)措施娩贷。

Leaf-segment方案可以生成趨勢(shì)遞增的ID,同時(shí)ID號(hào)是可計(jì)算的锁孟,不適用于訂單ID生成場(chǎng)景彬祖,比如競(jìng)對(duì)在兩天中午12點(diǎn)分別下單,通過(guò)訂單id號(hào)相減就能大致計(jì)算出公司一天的訂單量品抽,這個(gè)是不能忍受的储笑。面對(duì)這一問(wèn)題,我們提供了 Leaf-snowflake方案圆恤。

image

Leaf-snowflake方案完全沿用snowflake方案的bit位設(shè)計(jì)突倍,即是“1+41+10+12”的方式組裝ID號(hào)。對(duì)于workerID的分配盆昙,當(dāng)服務(wù)集群數(shù)量較小的情況下羽历,完全可以手動(dòng)配置。Leaf服務(wù)規(guī)模較大淡喜,動(dòng)手配置成本太高秕磷。所以使用Zookeeper持久順序節(jié)點(diǎn)的特性自動(dòng)對(duì)snowflake節(jié)點(diǎn)配置wokerID。Leaf-snowflake是按照下面幾個(gè)步驟啟動(dòng)的:

  1. 啟動(dòng)Leaf-snowflake服務(wù)拆火,連接Zookeeper跳夭,在leaf_forever父節(jié)點(diǎn)下檢查自己是否已經(jīng)注冊(cè)過(guò)(是否有該順序子節(jié)點(diǎn))。
  2. 如果有注冊(cè)過(guò)直接取回自己的workerID(zk順序節(jié)點(diǎn)生成的int類型ID號(hào))们镜,啟動(dòng)服務(wù)。
  3. 如果沒(méi)有注冊(cè)過(guò)润歉,就在該父節(jié)點(diǎn)下面創(chuàng)建一個(gè)持久順序節(jié)點(diǎn)模狭,創(chuàng)建成功后取回順序號(hào)當(dāng)做自己的workerID號(hào),啟動(dòng)服務(wù)踩衩。
image

弱依賴ZooKeeper

除了每次會(huì)去ZK拿數(shù)據(jù)以外嚼鹉,也會(huì)在本機(jī)文件系統(tǒng)上緩存一個(gè)workerID文件。當(dāng)ZooKeeper出現(xiàn)問(wèn)題驱富,恰好機(jī)器出現(xiàn)問(wèn)題需要重啟時(shí)锚赤,能保證服務(wù)能夠正常啟動(dòng)。這樣做到了對(duì)三方組件的弱依賴褐鸥。一定程度上提高了SLA

解決時(shí)鐘問(wèn)題

因?yàn)檫@種方案依賴時(shí)間线脚,如果機(jī)器的時(shí)鐘發(fā)生了回?fù)埽敲淳蜁?huì)有可能生成重復(fù)的ID號(hào),需要解決時(shí)鐘回退的問(wèn)題浑侥。

image

參見(jiàn)上圖整個(gè)啟動(dòng)流程圖姊舵,服務(wù)啟動(dòng)時(shí)首先檢查自己是否寫(xiě)過(guò)ZooKeeper leaf_forever節(jié)點(diǎn):

  1. 若寫(xiě)過(guò),則用自身系統(tǒng)時(shí)間與leaf_forever/{self}節(jié)點(diǎn)記錄時(shí)間做比較寓落,若小于leaf_forever/{self}時(shí)間則認(rèn)為機(jī)器時(shí)間發(fā)生了大步長(zhǎng)回?fù)芾ǘ。?wù)啟動(dòng)失敗并報(bào)警。
  2. 若未寫(xiě)過(guò)伶选,證明是新服務(wù)節(jié)點(diǎn)史飞,直接創(chuàng)建持久節(jié)點(diǎn)leaf_forever/${self}并寫(xiě)入自身系統(tǒng)時(shí)間,接下來(lái)綜合對(duì)比其余Leaf節(jié)點(diǎn)的系統(tǒng)時(shí)間來(lái)判斷自身系統(tǒng)時(shí)間是否準(zhǔn)確仰税,具體做法是取leaf_temporary下的所有臨時(shí)節(jié)點(diǎn)(所有運(yùn)行中的Leaf-snowflake節(jié)點(diǎn))的服務(wù)IP:Port构资,然后通過(guò)RPC請(qǐng)求得到所有節(jié)點(diǎn)的系統(tǒng)時(shí)間,計(jì)算sum(time)/nodeSize肖卧。
  3. 若abs( 系統(tǒng)時(shí)間-sum(time)/nodeSize ) < 閾值蚯窥,認(rèn)為當(dāng)前系統(tǒng)時(shí)間準(zhǔn)確,正常啟動(dòng)服務(wù)塞帐,同時(shí)寫(xiě)臨時(shí)節(jié)點(diǎn)leaf_temporary/${self} 維持租約拦赠。
  4. 否則認(rèn)為本機(jī)系統(tǒng)時(shí)間發(fā)生大步長(zhǎng)偏移,啟動(dòng)失敗并報(bào)警葵姥。
  5. 每隔一段時(shí)間(3s)上報(bào)自身系統(tǒng)時(shí)間寫(xiě)入leaf_forever/${self}荷鼠。

由于強(qiáng)依賴時(shí)鐘,對(duì)時(shí)間的要求比較敏感榔幸,在機(jī)器工作時(shí)NTP同步也會(huì)造成秒級(jí)別的回退允乐,建議可以直接關(guān)閉NTP同步。要么在時(shí)鐘回?fù)艿臅r(shí)候直接不提供服務(wù)直接返回ERROR_CODE削咆,等時(shí)鐘追上即可牍疏。或者做一層重試,然后上報(bào)報(bào)警系統(tǒng)拨齐,更或者是發(fā)現(xiàn)有時(shí)鐘回?fù)苤笞詣?dòng)摘除本身節(jié)點(diǎn)并報(bào)警鳞陨,如下:

 //發(fā)生了回?fù)埽丝虝r(shí)間小于上次發(fā)號(hào)時(shí)間
 if (timestamp < lastTimestamp) {

            long offset = lastTimestamp - timestamp;
            if (offset <= 5) {
                try {
                    //時(shí)間偏差大小小于5ms瞻惋,則等待兩倍時(shí)間
                    wait(offset << 1);//wait
                    timestamp = timeGen();
                    if (timestamp < lastTimestamp) {
                       //還是小于厦滤,拋異常并上報(bào)
                        throwClockBackwardsEx(timestamp);
                      }    
                } catch (InterruptedException e) {  
                   throw  e;
                }
            } else {
                //throw
                throwClockBackwardsEx(timestamp);
            }
        }
 //分配ID       

從上線情況來(lái)看,在2017年閏秒出現(xiàn)那一次出現(xiàn)過(guò)部分機(jī)器回?fù)芗呃牵捎贚eaf-snowflake的策略保證掏导,成功避免了對(duì)業(yè)務(wù)造成的影響。

Leaf在美團(tuán)點(diǎn)評(píng)公司內(nèi)部服務(wù)包含金融羽峰、支付交易趟咆、餐飲添瓷、外賣、酒店旅游忍啸、貓眼電影等眾多業(yè)務(wù)線仰坦。目前Leaf的性能在4C8G的機(jī)器上QPS能壓測(cè)到近5w/s,TP999 1ms计雌,已經(jīng)能夠滿足大部分的業(yè)務(wù)的需求悄晃。每天提供億數(shù)量級(jí)的調(diào)用量,作為公司內(nèi)部公共的基礎(chǔ)技術(shù)設(shè)施凿滤,必須保證高SLA和高性能的服務(wù)妈橄,我們目前還僅僅達(dá)到了及格線,還有很多提高的空間翁脆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末眷蚓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子反番,更是在濱河造成了極大的恐慌沙热,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罢缸,死亡現(xiàn)場(chǎng)離奇詭異篙贸,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)枫疆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)爵川,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人息楔,你說(shuō)我怎么就攤上這事寝贡。” “怎么了值依?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵圃泡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我愿险,道長(zhǎng)洞焙,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任拯啦,我火速辦了婚禮,結(jié)果婚禮上熔任,老公的妹妹穿的比我還像新娘褒链。我一直安慰自己,他們只是感情好疑苔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布甫匹。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兵迅。 梳的紋絲不亂的頭發(fā)上抢韭,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音恍箭,去河邊找鬼刻恭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扯夭,可吹牛的內(nèi)容都是我干的鳍贾。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼交洗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼骑科!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起构拳,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咆爽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后置森,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體斗埂,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年暇藏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蜜笤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盐碱,死狀恐怖把兔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瓮顽,我是刑警寧澤县好,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站暖混,受9級(jí)特大地震影響缕贡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拣播,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一晾咪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贮配,春花似錦谍倦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宴猾。三九已至,卻和暖如春叼旋,著一層夾襖步出監(jiān)牢的瞬間仇哆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工夫植, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留讹剔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓偷崩,卻偏偏與公主長(zhǎng)得像辟拷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子阐斜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容