順序铣缠、時鐘與分布式系統(tǒng)

Ordering

現(xiàn)實(shí)生活中時間可以記錄事情發(fā)生的時刻、比較事情發(fā)生的先后順序昆禽。
分布式系統(tǒng)的一些場景也需要記錄和比較不同節(jié)點(diǎn)間事件發(fā)生的順序蝗蛙。
如數(shù)據(jù)寫入先后順序,事件發(fā)生的先后順序等等醉鳖。

關(guān)系

復(fù)習(xí)下離散數(shù)學(xué)中關(guān)系:

假設(shè)A是一個集合 {1,2,3,4} 捡硅;R是集合A上的關(guān)系,例如{<1,1>,<2,2>,<3,3>,<4,4>,<1,2>,<1,4>,<2,4>,<3,4>}

  • 自反性:任取一個A中的元素x盗棵,如果都有<x,x>在R中壮韭,那么R是自反的北发。
    • <1,1>,<2,2>,<3,3>,<4,4>
  • 對稱性:任取一個A中的元素x,y,如果<x,y> 在關(guān)系R上,那么<y,x> 也在關(guān)系R上喷屋,那么R是對稱的琳拨。
  • 反對稱性:任取一個A中的元素x,y(x!=y),如果<x,y> 在關(guān)系R上,那么<y,x> 不在關(guān)系R上屯曹,那么R是反對稱的狱庇。
    • 對于 <1,2>,有 <2,1> 不在R中恶耽;對于<2,4> 有<4,2>不在R中密任;對于<3,4> 有<4,3> 不在 R中,滿足偷俭。
  • 傳遞性:任取一個A中的元素x,y,z浪讳,如果<x,y>,<y,z> 在關(guān)系R上,那么 <x,z> 也在關(guān)系R上涌萤,那么R是對稱的淹遵。
    • <1,1><1,2>在R中,并且<1,2>在R中形葬;<1,1><1,4>在R中合呐,并且<1,4>在R中;<2,2><2,4>在R中笙以,并且<2,4>在R中淌实;<3,3><3,4>在R中,并且<3,4>在R中猖腕;等等其他拆祈,滿足重贺。
  • 完全性(全關(guān)系):包含了自反性舌劳;對集合A中所有<x,y>,都有關(guān)系x到y(tǒng)或y到x亿傅;
    • R中并沒有<1, 3>老玛,所以不滿足完全性

偏序The Partial Ordering

集合內(nèi)只有部分元素之間是可以比較的淤年。

偏序關(guān)系的定義(R為A上的偏序關(guān)系):
設(shè)R是集合A上的一個二元關(guān)系,若R滿足:

  • 反對稱性:對任意x,y∈A蜡豹,若xRy麸粮,且yRx,則x=y镜廉;
  • 傳遞性:對任意x, y,z∈A弄诲,若xRy,且yRz娇唯,則xRz
  • 自反性:對任意x∈A齐遵,有xRx寂玲;

一個partitial ordering關(guān)系滿足的條件是自反的,反對稱的和可傳遞的梗摇,因此在partitial ordering中拓哟,可能有兩個元素之間是不相關(guān)的。

全序The Total Ordering

集合內(nèi)只有部分元素之間是可以比較的留美。
比如:比如復(fù)數(shù)集中并不是所有的數(shù)都可以比較大小彰檬,那么“大小”就是復(fù)數(shù)集的一個偏序關(guān)系。

全序關(guān)系的定義:

  • 反對稱性:對任意x,y∈A谎砾,若xRy逢倍,且yRx,則x=y景图;
  • 傳遞性:對任意x, y,z∈A较雕,若xRy,且yRz挚币,則xRz
  • 完全性(total relation全關(guān)系):對任意x,y∈A亮蒋,由xRy或yRx (包括了自反性)

完全性本身也包括了自反性,所以全序關(guān)系是偏序關(guān)系妆毕。

所以偏序中滿足完全性就是全序了慎玖。

一個total ordering關(guān)系滿足的條件是反對稱的,可傳遞的和完全性笛粘,因此在total ordering中趁怔,兩個元素一定是有關(guān)系的,要么是a<>b或b<>a薪前。

happens before

在分布式系統(tǒng)中润努,一個進(jìn)程包含一系列的事件,對于同一進(jìn)程內(nèi)的事件示括,如果a happens before b铺浇,那么a發(fā)生在b之前。并且垛膝,假定收或發(fā)消息都是一個事件鳍侣。

happens before的定義如下(用->表示)

  • 如果a和b在同一進(jìn)程中,并且a發(fā)生在b之前吼拥,那么a->b
  • 如果a是一個進(jìn)程發(fā)消息的事件倚聚,b是另一個進(jìn)程接收這條消息的事件,則a->b
  • 如果a->b且b->c扔罪,那么a->c。
  • 如果同時不滿足a->b桶雀,且b->a矿酵,那么說a和b是并發(fā)的concurrent
img1/1/clock/happens_before_lock_ordering.jpeg

[圖來自Time, Clocks, and the Ordering of Events in a Distributed System]

以一個例子來說明happens before關(guān)系唬复,如上圖,垂直線上代表一個進(jìn)程全肮,從下往上敞咧,時間依次增加,水平的距離代表空間的隔離辜腺。原點(diǎn)代表一個事件休建,而曲線代表一條消息。

從圖中很容易地看出评疗,如果一個事件a测砂,能通過進(jìn)程的線和消息線,到達(dá)b百匆,那么a->b砌些。

在圖中,p3和q4是并行的事件加匈,因?yàn)榇媪В挥械搅藀4才能確定q4的發(fā)生,而q3也只能確定p1發(fā)生雕拼。

clock時鐘

物理時鐘

晶振和時鐘偏移

計算機(jī)有固定頻率晶體的震蕩次數(shù)纵东,晶體的振蕩周期決定了單機(jī)的時鐘精度。

時鐘頻率也可能因?yàn)闇囟鹊韧獠恳蛩貙?dǎo)致時鐘偏移啥寇,普通的石英晶體的漂移大約10^{-6}

原子鐘的漂移約為 10^{-13}
所以原子鐘精度遠(yuǎn)遠(yuǎn)高于石英晶體偎球。

分布式下帶來的問題

不同機(jī)器上的物理時鐘難以同步,導(dǎo)致無法區(qū)分在分布式系統(tǒng)中多個節(jié)點(diǎn)的事件時序示姿。即使設(shè)置了 NTP 時間同步節(jié)點(diǎn)間也存在毫秒級別的偏差甜橱,因而分布式系統(tǒng)需要有另外的方法記錄事件順序關(guān)系。

1978年Lamport在《Time, Clocks and the Ordering of Events in a Distributed System》中提出了邏輯時鐘的概念栈戳,來解決分布式系統(tǒng)中區(qū)分事件發(fā)生的時序問題岂傲。

邏輯時鐘Logical clocks

邏輯時鐘指的是分布式系統(tǒng)中用于區(qū)分事件的發(fā)生順序的時間機(jī)制。 從某種意義上講子檀,現(xiàn)實(shí)世界中的物理時間其實(shí)是邏輯時鐘的特例镊掖。

Logical Clock解決的問題是找到一種方法,給分布式系統(tǒng)中所有時間定一個序褂痰,這個序能夠正確地排列出具有因果關(guān)系的事件(注意亩进,是不能保證并發(fā)事件的真實(shí)順序的),使得分布式系統(tǒng)在邏輯上不會發(fā)生因果倒置的錯誤缩歪。因果一致性

Lamport timestamps

論文

Time, Clocks, and the Ordering of Events in a Distributed System

Lamport timestamps

Leslie Lamport 在1978年提出邏輯時鐘的概念归薛,并描述了一種邏輯時鐘的表示方法,這個方法被稱為Lamport時間戳(Lamport timestamps)。

分布式系統(tǒng)中按是否存在節(jié)點(diǎn)交互可分為三類事件:

  • 發(fā)生在節(jié)點(diǎn)內(nèi)部
  • 發(fā)送事件
  • 接收事件

時鐘的定義如下

  • 對于一個進(jìn)程i主籍,Ci(a)表示進(jìn)程i中事件a的發(fā)生時間
  • 對于整個系統(tǒng)來講习贫,對于任意的事件b,其發(fā)生時間為C(b)千元,當(dāng)b為進(jìn)程j的事件時苫昌,則C(b) = Cj(b)
    為了使得事件按照正確的排序,需要使得如果事件a發(fā)生在事件b之前幸海,那么a發(fā)生的時間要小于b祟身,如下
for any events a, b
if a->b then C(a) < C(b)

根據(jù)關(guān)系->的定義,我們可以得出

  • 如果a和b都是進(jìn)程i中的事件物独,且a發(fā)生在b之前袜硫,那么Ci(a) < Ci(b)
  • 如果事件a發(fā)送消息給事件b,a屬于進(jìn)程i议纯,b屬于進(jìn)程j父款,那么Ci(a) < Cj(b)
img1/1/clock/happens_before.png

為了讓系統(tǒng)滿足上述條件,在實(shí)現(xiàn)中瞻凤,需要滿足以下原則

  • 對于每個進(jìn)程憨攒,相鄰的事件的時鐘要增加1
  • (a) 如果事件a是進(jìn)程i發(fā)送消息m的事件,發(fā)送時帶時間戳Tm = Ci(a)阀参,(b)事件b是進(jìn)程j接受消息m的事件肝集,那么事件b的取值為max(進(jìn)程b的當(dāng)前時鐘,Tm+1)

假設(shè)有事件a蛛壳、b杏瞻,C(a)、C(b)分別表示事件a衙荐、b對應(yīng)的Lamport時間戳捞挥,如果a->b,則C(a) < C(b),a發(fā)生在b之前(happened before)忧吟。

所以Lamport timestamps原理如下:

  • 每個事件對應(yīng)一個Lamport時間戳砌函,初始值為0
  • 如果事件在節(jié)點(diǎn)內(nèi)發(fā)生,時間戳加1
  • 如果事件屬于發(fā)送事件溜族,時間戳加1并在消息中帶上該時間戳
  • 如果事件屬于接收事件讹俊,時間戳 = Max(本地時間戳,消息中的時間戳) + 1

通過該定義煌抒,事件集中Lamport時間戳不等的事件可進(jìn)行比較仍劈,我們獲得事件的偏序關(guān)系(partial order)。

img1/1/clock/logical_time.png

上圖更形象的解釋了事件之間的關(guān)系寡壮。

以B4事件為基準(zhǔn):

  • B4左邊深灰色的區(qū)域的事件贩疙,都發(fā)生在B4前讹弯,和B4具有因果關(guān)系这溅,這些事件屬于與B4因果關(guān)系中的因(cause)
  • B4右邊的深紅色區(qū)域的事件芍躏,都發(fā)生在B4后,和B4具有因果關(guān)系降狠,這些事件屬于與B4因果關(guān)系中的果(effect)
  • B4上下的白色區(qū)域是跟B4無關(guān)的事件对竣,可以認(rèn)為是并發(fā)關(guān)系(concurrent)
  • 在淺灰色和淺紅色區(qū)域中的事件榜配,C2、A3兩個事件與B4是并行關(guān)系蛋褥,根據(jù)Lamport timestamps的定義临燃,將他們判定為與B4具前后關(guān)系。(所以Lamport timestamps并不能嚴(yán)格的表示并行關(guān)系)

Lamport timestamps與偏序關(guān)系

Lamport timestamps只保證因果關(guān)系(偏序)的正確性膜廊,不保證絕對時序的正確性淫茵。

Lamport logical clock

由于Lamport timestamps只能得到偏序關(guān)系,如果要得到全序關(guān)系铆铆,就需要給Ci(a) = Cj(b)的事件定一個先后順序丹喻。

total order的事件關(guān)系=>定義如下:
如果事件a發(fā)生在進(jìn)程Pi,事件b發(fā)生在進(jìn)程Pj谅猾,那么當(dāng)滿足下列兩者條件之一時骑冗,a=>b

  • Ci(a) < Cj(b)
  • Ci(a) = Cj(b) 且 Pi < Pj

根據(jù)以上條件,對于任意的兩個事件巧涧,都能判斷出它們之間的關(guān)系遥倦,因此是total ordering的。

當(dāng)Lamport timestamp一致時缩筛,通過義Pi < Pj來定義順序瞎抛,確保分布式場景下各個進(jìn)程間發(fā)生的事件的全序定義艺演。至于Pj < Pj:可采用不同的方式桐臊,Lamport Logical Clock提到的 arbitrary total ordering断凶。

vector clock

Lamport timestamp得到的是全序關(guān)系,但無法嚴(yán)格表示對于沒有因果關(guān)系肿男、存在同時發(fā)生關(guān)系(concurrent)的事件却嗡。

Vector clock是在Lamport timestamp基礎(chǔ)上改進(jìn)的一種邏輯時鐘方法,它構(gòu)不但記錄本節(jié)點(diǎn)的Lamport timestamp冠王,同時也記錄了其他節(jié)點(diǎn)的Lamport timestamp舌镶。

原理如下:

  • 本地vector clock的clock數(shù)組中每一個邏輯時間(clock)對應(yīng)一個進(jìn)程的clock
  • 初始化vector clock中每一個邏輯時間為0;
  • 每一次處理內(nèi)完內(nèi)部事件哟楷,將vector clock中自己的邏輯時間戳+1否灾;
  • 每發(fā)送一個消息的時候,將vector clock中自己的邏輯時間+1惩阶,且將其和消息一起發(fā)送出去
  • 每接收到一個消息的時候扣汪,需要將本地的vector clock中自己的邏輯時間戳+1崭别,且將自己vector clock中的邏輯時間和消息中攜帶的進(jìn)行比較恐锣,取最大的更新本地vector clock中的邏輯時間舞痰。

img1/1/clock/1000px-Vector_Clock.svg.png

圖來源于wikipedia

vector clock判定并發(fā)關(guān)系:

  • 事件i响牛、事件j對應(yīng)的vector clock中,每一個進(jìn)程Pk的邏輯時間戳都滿足Vi[Pk]<Vj[Pk]時论衍,我們稱事件i happen before事件j;
  • vector clock中炬丸,存在P1稠炬、P2,使得Vi[P1]<Vj[P1]暮屡,Vi[P2]>Vj[P2]毅桃,我們稱事件i和事件j是并發(fā)關(guān)系(沒有因果關(guān)系);

和之前l(fā)amport timestamp的一樣莺掠,以B4事件為基準(zhǔn)(vector clock為[A:2,B:4,C:1])读宙,根據(jù)vector clock的判定结闸,可以判斷出

  • 灰色區(qū)域的事件happens before B4事件,B4事件happens before紅色區(qū)域的事件
  • 白色區(qū)域與B4事件沒有因果關(guān)系扎附。

特性:

  • vector clock不需要在節(jié)點(diǎn)之間同步時鐘结耀,不需要在所有節(jié)點(diǎn)上維護(hù)一段數(shù)據(jù)的版本數(shù);
  • 缺點(diǎn)是時鐘值的大小隨著節(jié)點(diǎn)增多和時間不斷增長

version vector

分布式系統(tǒng)多個副本被同時更新時香伴,會導(dǎo)致副本之間數(shù)據(jù)的不一致即纲。version vector用于來發(fā)現(xiàn)這些不一致的沖突。

version vector只能發(fā)現(xiàn)沖突蜂厅,無法解決沖突膊畴;當(dāng)然也可以通過再添加一個維度信息timestamp唇跨,發(fā)生沖突時進(jìn)行比較,但是又回到了物理時鐘不同步的問題改橘。

下圖展示了數(shù)據(jù)由不同副本處理后導(dǎo)致的不同版本沖突玉控。
D5時發(fā)現(xiàn)了數(shù)據(jù)的沖突,這時會將不同版本數(shù)據(jù)都存儲下來碌识,一般由客戶端來解決沖突丸冕。

image

version vector與vector clock的差異

  • vector clocks 使用 receive和send 方法來更新clock,而version vector使用sync方法來更新薛窥。
  • vector clocks是給事件定序的诅迷,確定事件的因果關(guān)系;而version vector是確定同一個數(shù)據(jù)不同版本的因果關(guān)系趟畏。

分布式與時鐘

分布式系統(tǒng)中滩租,每個節(jié)點(diǎn)的物理時鐘是不同步的,都有一定的差異猎莲。

這樣就帶來了一些分布式系統(tǒng)實(shí)現(xiàn)的難題著洼,如基于MVCC實(shí)現(xiàn)的事務(wù),基于MVCC實(shí)現(xiàn)事務(wù)會要求版本之間能判斷先后順序豹悬,只有確定先后才知道應(yīng)該用哪一個版本的數(shù)據(jù)液荸,確定先后順序就涉及到時間娇钱,而不同機(jī)器之間的本地時鐘是無法保證一致的,所以這就需要確保時鐘的同步。

而通常解決方案有兩種:

  • 中心化的時鐘方案细疚,如Timestamp oracle(TSO)
  • 無中心化的時鐘方案川梅,如google True Time,Hybrid Logic Time

Timestamp oracle

如果我們整個系統(tǒng)不復(fù)雜吧彪,而且沒有跨全球的需求姨裸,這時用一臺中心授時服務(wù)就可以了怨酝。

如TiDB使用的就是TSO方案,tipb作為一個TSO集群赡艰,來提供授時服務(wù)慷垮。

使用TSO的好處在于因?yàn)橹挥幸粋€中心授時,所以我們一定能確定所有時間的時間汤纸,但TSO需要關(guān)注幾個問題:

  • 網(wǎng)絡(luò)延時:因?yàn)樗械氖录夹枰獜腡SO獲取時間惯驼,所以TSO只適合小集群部署祟牲,不能是那種全球級別的數(shù)據(jù)庫
  • 性能:每個事件都需要從TSO獲取時間,所以TSO需要非常高的性能
  • 容錯:TSO是一個單點(diǎn)议惰,需要考慮節(jié)點(diǎn)的failover

True Time

由于節(jié)點(diǎn)間NTP是有偏差的乡恕,且可能出現(xiàn)時間回退的情況傲宜,所以NTP無法準(zhǔn)確的判定事件的全序關(guān)系。在Google Spanner里面辆憔,通過引入True Time來解決了分布式時間問題报嵌。

True Time實(shí)現(xiàn)

Spanner通過使用GPS + 原子鐘atomic clock來對集群的機(jī)器時間進(jìn)行校對锚国,保證了集群機(jī)器的時間戳差距不會超過一個上限值(ε)。

用兩種技術(shù)來處理绘沉,是因?yàn)閷?dǎo)致這兩種技術(shù)的失敗的原因是不同的梆砸。

  • GPS會有一個天線园欣,電波干擾會導(dǎo)致其失靈。原子鐘很穩(wěn)定日矫。
  • 當(dāng)GPS失靈的時候哪轿,原子鐘仍然能保證在相當(dāng)長的時間內(nèi),不會出現(xiàn)偏差杨耙。

API

  • TT.now() : 返回一個當(dāng)前時間飘痛,其位于范圍區(qū)間[earliest,latest]
  • TT.after(t) : 當(dāng)前時間是否在t之后
  • TT.before(t) : 當(dāng)前時間是否在t之前

雖然spanner引入了TrueTime可以得到全球范圍的時序一致性宣脉,但由于TrueTime返回的時間仍然有一定的偏差,如果要給兩個事件定序竹祷,就需要等待2個偏差的時間間隔塑陵,來確保其先后順序蜡励。

  • 事件a:[Tai, Taj], Taj-Tai=ε
  • 事件b:[Tbi, Tbj], Tbj-Tbi=ε
  • 所以要確定b>a, 那么就要確保Tbi > Taj, 就需要在事件b進(jìn)行等待巍虫,以確保:事件b時間 - 事件a時間 > 2ε

Hybrid logical clock

HLC

Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases

TrueTime 需要硬件的支持鳍刷,所以有一定的成本输瓜,而HLC無需硬件支持也能解決分布式下時間問題。

HLC同時使用了物理時鐘和邏輯時鐘(physical clock + logical clock)搔啊,能夠保證單點(diǎn)的時間發(fā)生器是單調(diào)遞增的北戏,同時能夠盡量控制不同節(jié)點(diǎn)之間的時鐘偏差在規(guī)定的偏差范圍內(nèi)嗜愈。

判斷兩個事件的先后順序:先判斷物理時間莽龟,再判斷邏輯時間毯盈。

HLC的算法

l.j維護(hù)的是節(jié)點(diǎn)j當(dāng)前已知的最大的物理時間(wall time)病袄,c.j則是當(dāng)前的邏輯時間益缠。

// 在節(jié)點(diǎn)j上面:初始化: l.j = 0,c.j = 0捺信。
initially l.j :=0; c.j := 0

// 本地事件或者發(fā)送消息時迄靠,
// 如果本地時鐘pt大于當(dāng)前的混合邏輯時鐘的l喇辽,
// 則將l更新成本地時鐘菩咨,將c清零。
// 否則特占,l保持不變云茸,將c加1标捺。
Send or local event
{
    l'.j := l.j;
    l.j := max(l'.j, pt.j);    // 本地物理時間pt
    if (l.j = l'.j) then 
        c.j := c.j+1
    else 
        c.j := 0;
    Timestamp with l.j, c.j
}

// 收到消息時
// l在 當(dāng)前的邏輯時鐘的l亡容、機(jī)器的本地時鐘pt、收到消息里面帶的l茂缚,三者中取最大的。
// 如果l部分是更新為本地時鐘了帖汞,則將c清零翩蘸。否則淮逊,c取較大的那個l對應(yīng)到的c加1泄鹏。
Receive event of message m
{
    l'.j := l.j;
    l.j := max(l'.j, l.m, pt.j);
    if (l.j = l'.j = l.m) then 
        c.j := max(c.j, c.m) + 1
    elseif (l.j=l'.j) then 
        c.j := c.j + 1
    elseif (l.j=l.m) then 
        c.j := c.m + 1
    else 
        c.j := 0
    Timestamp with l.j, c.j
}

特性

HLC算法保證了HLC時間有如下特性:

  • 事件e發(fā)生在事件f之前,那么事件e的HLC時間一定小于事件f的HLC時間:(l.e, c.e) < (l.f, c.f)
  • 本地WallTime大于等于本地物理時間(l.e ≥ pt.e):HLC時間總是不斷遞增舶治,不會隨著物理時間發(fā)生回退霉猛。
  • 對事件e珠闰,l.e是事件e能感知的到的最大物理時間值:如果l.e > pt.e伏嗜,那么一定存在著一個發(fā)生在e之前的事件g,有pt.g=l.e裸影。簡單來說是如果出現(xiàn)l.e > pt.e肯定是因?yàn)橛幸粋€HLC時間更大的的節(jié)點(diǎn)把當(dāng)前節(jié)點(diǎn)的HLC時間往后推了轩猩。
  • WallTime和物理時鐘的偏差是有界的(ε ≥ |pt.e - l.e| ):因?yàn)楣?jié)點(diǎn)之間通過NTP服務(wù)校時羞迷,那么節(jié)點(diǎn)之間的物理時鐘偏差一定小于某個值ε衔瓮。那么對于任一事件b和e抖甘,如果b hb e,那么事件b的物理時間pt.b一定滿足pt.e + ε ≥ pt.b偷办。結(jié)合特性3存在一個事件g滿足澄港,l.e = pt.g回梧。那么 pt.e + ε ≥ l.e=pt.g > pt.e。

開源實(shí)現(xiàn)

CockroachDB采用基于NTP時鐘同步的HLC去中心化方案湖苞。

時鐘同步

所有節(jié)點(diǎn)間的RPC消息都會把時間戳帶入到消息中财骨,接收到消息的節(jié)點(diǎn)會通過消息中的時間戳更新自己的時間, 從而達(dá)到節(jié)點(diǎn)間時間同步的效果隆箩。

代碼分析

參考:https://github.com/cockroachdb/cockroach/blob/v1.1.3/pkg/util/hlc/hlc.go

HLC定義

// Timestamp represents a state of the hybrid logical clock.
type Timestamp struct {
    // Holds a wall time, typically a unix epoch time
    // expressed in nanoseconds.
    WallTime int64 `protobuf:"varint,1,opt,name=wall_time,json=wallTime" json:"wall_time"`
    // The logical component captures causality for events whose wall
    // times are equal. It is effectively bounded by (maximum clock
    // skew)/(minimal ns between events) and nearly impossible to
    // overflow.
    Logical int32 `protobuf:"varint,2,opt,name=logical" json:"logical"`
}
  • WallTime:本地已知物理時鐘
  • Logical:邏輯時鐘
  • Timestamp:HLC摘仅,單調(diào)遞增

獲取物理時鐘

// PhysicalNow returns the local wall time. It corresponds to the physicalClock
// provided at instantiation. For a timestamp value, use Now() instead.
func (c *Clock) PhysicalNow() int64 {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.getPhysicalClockLocked()
}

// getPhysicalClockLocked returns the current physical clock and checks for
// time jumps.
func (c *Clock) getPhysicalClockLocked() int64 {
    // physicalClock 就是 UnixNano
    newTime := c.physicalClock()

    if c.mu.lastPhysicalTime != 0 {
        interval := c.mu.lastPhysicalTime - newTime
        // 檢查時鐘是否回退
        if interval > int64(c.maxOffset/10) {
            c.mu.monotonicityErrorsCount++
            log.Warningf(context.TODO(), "backward time jump detected (%f seconds)", float64(-interval)/1e9)
        }
    }

    c.mu.lastPhysicalTime = newTime
    return newTime
}


// UnixNano returns the local machine's physical nanosecond
// unix epoch timestamp as a convenience to create a HLC via
// c := hlc.NewClock(hlc.UnixNano, ...).
func UnixNano() int64 {
    return timeutil.Now().UnixNano()
}

獲取當(dāng)前HLC時鐘

// Now returns a timestamp associated with an event from
// the local machine that may be sent to other members
// of the distributed network. This is the counterpart
// of Update, which is passed a timestamp received from
// another member of the distributed network.
func (c *Clock) Now() Timestamp {
    c.mu.Lock()
    defer c.mu.Unlock()
    if physicalClock := c.getPhysicalClockLocked(); c.mu.timestamp.WallTime >= physicalClock {
        // The wall time is ahead, so the logical clock ticks.
        c.mu.timestamp.Logical++
    } else {
        // Use the physical clock, and reset the logical one.
        c.mu.timestamp.WallTime = physicalClock
        c.mu.timestamp.Logical = 0
    }
    return c.mu.timestamp
}
  • 如果當(dāng)前物理時鐘小于WallTime娃属,則將邏輯時鐘+1
  • 如果當(dāng)前物理時鐘大于WallTime矾端,則更新WallTime為當(dāng)前物理時鐘秩铆,且將邏輯時鐘設(shè)置為0

節(jié)點(diǎn)時鐘同步

節(jié)點(diǎn)之間通過在RPC請求中攜帶HLC時間來進(jìn)行時鐘同步灯变。

// sendSingleRange gathers and rearranges the replicas, and makes an RPC call.
func (ds *DistSender) sendSingleRange(
    ctx context.Context, ba roachpb.BatchRequest, desc *roachpb.RangeDescriptor,
) (*roachpb.BatchResponse, *roachpb.Error) {
    ......
    
    br, err := ds.sendRPC(ctx, desc.RangeID, replicas, ba)
    if err != nil {
        log.ErrEvent(ctx, err.Error())
        return nil, roachpb.NewError(err)
    }

    // If the reply contains a timestamp, update the local HLC with it.
    if br.Error != nil && br.Error.Now != (hlc.Timestamp{}) {
        ds.clock.Update(br.Error.Now)
    } else if br.Now != (hlc.Timestamp{}) {
        ds.clock.Update(br.Now)
    }
    
    ......
}


// Update takes a hybrid timestamp, usually originating from
// an event received from another member of a distributed
// system. The clock is updated and the hybrid timestamp
// associated to the receipt of the event returned.
// An error may only occur if offset checking is active and
// the remote timestamp was rejected due to clock offset,
// in which case the timestamp of the clock will not have been
// altered.
// To timestamp events of local origin, use Now instead.
func (c *Clock) Update(rt Timestamp) Timestamp {
    c.mu.Lock()
    defer c.mu.Unlock()
    
    // 如果本地物理時間pt
    physicalClock := c.getPhysicalClockLocked()

    // 大于本地WallTime且大于rt.WallTime:
    //      更新本地WallTime=pt滚粟,且logical=0
    if physicalClock > c.mu.timestamp.WallTime && physicalClock > rt.WallTime {
        // Our physical clock is ahead of both wall times. It is used
        // as the new wall time and the logical clock is reset.
        c.mu.timestamp.WallTime = physicalClock
        c.mu.timestamp.Logical = 0
        return c.mu.timestamp
    }

    // In the remaining cases, our physical clock plays no role
    // as it is behind the local or remote wall times. Instead,
    // the logical clock comes into play.
    
    // 如果rt.WallTime > 本地WallTime:
    //   檢查rt.WallTime與pt是否大于時鐘偏差凡壤;
    //   本地WallTime=rt.WallTime,logical++
    if rt.WallTime > c.mu.timestamp.WallTime {
        offset := time.Duration(rt.WallTime-physicalClock) * time.Nanosecond
        if c.maxOffset > 0 && offset > c.maxOffset {
            log.Warningf(context.TODO(), "remote wall time is too far ahead (%s) to be trustworthy - updating anyway", offset)
        }
        // The remote clock is ahead of ours, and we update
        // our own logical clock with theirs.
        c.mu.timestamp.WallTime = rt.WallTime
        c.mu.timestamp.Logical = rt.Logical + 1
    } else if c.mu.timestamp.WallTime > rt.WallTime {
       // 如果本地WallTime>rt.WallTime:logical++
        // Our wall time is larger, so it remains but we tick
        // the logical clock.
        c.mu.timestamp.Logical++
    } else {
        // Both wall times are equal, and the larger logical
        // clock is used for the update.
        if rt.Logical > c.mu.timestamp.Logical {
            c.mu.timestamp.Logical = rt.Logical
        }
        c.mu.timestamp.Logical++
    }
    return c.mu.timestamp
}

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者箕别。
  • 序言:七十年代末究孕,一起剝皮案震驚了整個濱河市厨诸,隨后出現(xiàn)的幾起案子禾酱,更是在濱河造成了極大的恐慌颤陶,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垦江,死亡現(xiàn)場離奇詭異比吭,居然都是意外死亡衩藤,警方通過查閱死者的電腦和手機(jī)涛漂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門匈仗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人间狂,你說我怎么就攤上這事前标【嗯耍” “怎么了音比?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵洞翩,是天一觀的道長。 經(jīng)常有香客問我已亥,道長来屠,這世上最難降的妖魔是什么虑椎? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮俱笛,結(jié)果婚禮上捆姜,老公的妹妹穿的比我還像新娘。我一直安慰自己迎膜,他們只是感情好泥技,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著磕仅,像睡著了一般珊豹。 火紅的嫁衣襯著肌膚如雪榕订。 梳的紋絲不亂的頭發(fā)上平夜,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天,我揣著相機(jī)與錄音卸亮,去河邊找鬼忽妒。 笑死,一個胖子當(dāng)著我的面吹牛兼贸,可吹牛的內(nèi)容都是我干的段直。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼溶诞,長吁一口氣:“原來是場噩夢啊……” “哼鸯檬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起螺垢,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤喧务,失蹤者是張志新(化名)和其女友劉穎赖歌,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體功茴,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庐冯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了坎穿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片展父。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖玲昧,靈堂內(nèi)的尸體忽然破棺而出栖茉,到底是詐尸還是另有隱情,我是刑警寧澤孵延,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布吕漂,位于F島的核電站,受9級特大地震影響尘应,放射性物質(zhì)發(fā)生泄漏痰娱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一菩收、第九天 我趴在偏房一處隱蔽的房頂上張望梨睁。 院中可真熱鬧,春花似錦娜饵、人聲如沸坡贺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遍坟。三九已至,卻和暖如春晴股,著一層夾襖步出監(jiān)牢的瞬間愿伴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工电湘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留隔节,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓寂呛,卻偏偏與公主長得像怎诫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贷痪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348

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

  • 久違的晴天幻妓,家長會。 家長大會開好到教室時劫拢,離放學(xué)已經(jīng)沒多少時間了肉津。班主任說已經(jīng)安排了三個家長分享經(jīng)驗(yàn)强胰。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,513評論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友妹沙。感恩相遇偶洋!感恩不離不棄。 中午開了第一次的黨會初烘,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,559評論 0 11
  • 可愛進(jìn)取涡真,孤獨(dú)成精分俯。努力飛翔肾筐,天堂翱翔。戰(zhàn)爭美好缸剪,孤獨(dú)進(jìn)取吗铐。膽大飛翔,成就輝煌杏节。努力進(jìn)取唬渗,遙望,和諧家園奋渔∧魇牛可愛游走...
    趙原野閱讀 2,721評論 1 1
  • 在妖界我有個名頭叫胡百曉,無論是何事嫉鲸,只要找到胡百曉即可有解決的辦法撑蒜。因?yàn)槭侵缓偞蠹乙杂瀭饔灲形摇皟A城百曉”,...
    貓九0110閱讀 3,255評論 7 3