在單機數據庫領域,咱們爲每一個事務都分配一個序列號,好比Oracle的SCN(SystemChangeNumber),MySQL的LSN(LogSequenceNumber),這個序列號能夠是邏輯的,也能夠是物理的。咱們依賴這個序列號對系統中發生的事務進行排序,確保全部事務都有嚴格的前後關係。數據庫中全部的事務都按分配的序列號排序,對於任什麼時候間點發生的讀,保證能讀到這個時間點以前的提交的事務,而且讀不到以後發生的事務。因此,通常來講,不管系統中序列號是邏輯仍是物理的,都與真實的物理時間有一個對應的單獨遞增關係。在單機數據庫時代,這個相對容易作到,系統中有一個惟一的序列號分配器,保證有序。算法
來到分佈式數據時代,一個數據庫系統再也不只有一個節點能夠處理事務,多個節點上發生的事務如何保證有序是本文想要討論的問題。最簡單的想法是,咱們在數據庫系統中專門添加一個組件,這個組件做用就是分配時間戳,付出的代價是,任何一個事務提交,都須要有一個網絡RTT的消耗,而且分配時間戳的組件是整個系統的單點,可能成爲系統的瓶頸。實際上,目前主流分佈式數據庫系統還提供了兩個可選方案,一種是Spanner數據庫的TrueTime機制,另一種CockroachDB數據庫的HLC(HybridLogicClock)機制。數據庫
先說說HLC的由來,咱們前面提到分佈式數據庫中,要爲每一個事務都分配一個合理的序列號比較麻煩,實際上這不僅僅是數據庫的問題,仍是全部分佈式系統中的共性問題,如何爲系統中發生的事件排序。既然各個節點的物理時鐘不一致,不如都採用邏輯時鐘(LogicClock),邏輯時鐘只保證因果一致性,即不保證全局有序,只保證有前後順序的事件有序。哪些事件有前後關係,主要包括兩類,1.單節點內部前後發生的事件;2.節點間有通訊的事件,發送消息的節點必定早於接收消息的節點。因爲分配的序列號與物理時鐘徹底無關,真實時間沒法與序列號對應,致使沒法用於實際的生產環境。HLC源於LC,是對LC的改進,一樣是保證因果序,但引入了物理時間戳做爲序列號的一部分,這樣能與物理時鐘對應起來,採用物理時鐘+邏輯時鐘混合的方式做爲序列號,提供一種事務序列號的分配方法。網絡
接下里咱們看看HLC是怎麼實現的?HLC分配算法很簡單,源於[論文](https://cse.buffalo.edu/tech-reports/2014-04.pdf)。併發
l.j表示本地HLC,pt.j表示物理時間戳,c.j表示邏輯時間戳,對於發送事件或是本地事件,遞增邏輯時間戳部分,確保本地事件有序;對於接收事件節點,取本地時間戳和發送節點時間戳的最大值,若是物理時間戳部分相同,則遞增邏輯時間戳部分。整個邏輯是簡單清晰的,迴歸到數據庫系統,則是事務提交時,若是是本地事務,則取本地HLC和本地物理時間的最大值,避免時鐘跳變;對於分佈式事務,則選擇多個參與者節點中最大的HLC,而且推高各個節點的本地HLC,從而保證事務的序列號HLC一直都是單調遞增的。分佈式
在分佈式數據庫領域引入HLC算法能爲每一個事務都分配一個合理序列號,而且保證有因果關係的事務經過序列號就能肯定。在數據庫領域,怎麼定義因果關係,對於單節點事務而言,若是事務嚴格時間前後發生順序(事務A提交後,事務B纔開始),或者存在依賴關係(好比更新同一行),則認爲存在因果關係;若是事務能併發執行提交,但不存在依賴關係,則不存在因果關係,事務序列號分配沒有約束。對於跨節點分佈式事務而言,若是涉及到節點更新與其它事務有依賴關係,則存在因果序,不然沒有。數據庫系統引入HLC機制後,可以保證有依賴關係的事務,分配的序列號也必定有前後順序。HLC由物理時鐘+邏輯時鐘兩部分組成,論文中建議48位做爲物理時鐘位,16位做爲邏輯時鐘位,那麼提供的時鐘精度大約是15微妙,每一個微妙的邏輯時鐘LC能夠跳變65536次。性能
前面提到了TrueTime機制,在對比兩種分配序列號機制以前,先簡單介紹下TrueTime。咱們之因此使用LC,HLC主要緣由是咱們各個節點的物理時鐘不一致,咱們沒法按單機思惟來解決分佈式系統問題。TrueTIme機制是經過在集羣中引入原子鐘和GPS等硬件設備,來確保集羣中各個節點的物理時鐘偏差在必定範圍內,配合特殊的事務commit-wait邏輯,保證全局的事務有序。spa
咱們看一個問題,假設集羣最大物理時鐘偏差是100,用戶前後執行了兩個事務A和B,分別在節點Node1和Node2上提交,Node1的物理時鐘比Node2物理時鐘快70,事務A的提交時間物理時間戳爲120,記爲t1;事務B的提交物理時間戳爲55,記爲t2,顯然從時間戳來看t1>t2,可是事務A卻先於事務B發生,這顯然與實際狀況不符。因爲事務A和B並無任何交集,按咱們的定義,它們之間沒有因果關係,因此HLC機制不保證最終分配的時間戳HLC(A) < HLC(B)。3d
如今看看TrueTime機制如何給兩個事務排序。假設事務A提交時,經過TrueTime-API得到的是一個時間戳是一個範圍,好比[t1-ε1,t1+ε1],事務B開始時,獲取的時間戳範圍是[t2-ε2,t2+ε2],因爲事務A先於事務B發生,所以須要能保證t1+ε1 < t2-ε2,t2-t1 > ε1+ε2,那麼顯然只要事務提交時,等待(ε1+ε2)時間,那麼必定能知足t1+ε1 < t2-ε2,也就是事務A提交時間戳<事務B提交的時間戳。實際上TrueTime機制保證的時間戳偏差最大在7ms,那麼事務提交時,則必須等待大概14ms,才能保證事務有序。對於分佈式事務,事務會跨多個節點,事務的時間戳會選取幾個節點中最大的時間戳。所以,實際上對於集羣中任何一個節點不管是本地事務仍是分佈式事務,發生的前後順序都與時間戳順序一致,也就能保證所謂的外部一致性。TrueTime機制經過commit-wait的模式經過犧牲延遲,保證了全局順序。blog
TrueTime機制引入了特殊的硬件設備,外加commit-wait機制,經過犧牲必定的latency,來保證全局事務的順序。HLC機制對於寫請求則相對輕量,尤爲是本地事務,沒有任何網絡交互,也不須要額外的時間等待,不一樣節點上有前後順序事務,實際上分配的序列號沒有嚴格的前後順序,只能保證因果序。排序
若是採用專門的事務序列號分配組件服務,好比TSO(TimeStamp Oracle),顯然全部事務都是有序的,相似於單機數據庫;若是是TrueTime機制,對於任何一個時間戳,讀取的快照偏差都在指定的範圍(7ms之內),結合commit-wait,也能保證全局有序;而對於HLC機制,因爲並無解決物理時鐘問題,要控制偏差可能須要藉助NTP等服務,固然這個偏差可能在150ms或更多,不如原子鐘和GPS精確,若是採用commit-wait機制,事務延遲過大,就沒法用於實際生產環境了,因此沒法提供全局序。那麼全局序和因果序對於讀有什麼影響?對於快照讀,HLC機制若是隻根據本地HLC時間戳生成,那麼由於時鐘偏差,可能會漏掉部分已提交的事務;若是與全部節點通訊並獲取HLC,那麼至少當前的這個快照是能讀到全部已提交的事務的,但讀性能開銷就比較大了(與全部節點都有網絡交互)。固然,這個就看場景了,好比要建全局索引,經過與全部節點通訊獲取HLC,實際上就至關於與全部節點都有因果關係,間接獲得了一個全局一致性快照。這個快照點之前的事務均可以讀取到,這個點之後的事務都不讀到。顯然,全局序更貼合實際應用場景,但咱們也能夠具體問題具體分析,不能說只提供因果序的系統沒有價值。
數據庫系統中,給事務分配合理的序列號,須要與實際發生的事務前後順序保證單調遞增關係。這個需求在單機數據庫時代很容易知足,由於只有一個時鐘源。進入分佈式數據庫時代,因爲一個數據庫系統中有多個節點,每一個節點都須要承擔分配事務序列號的任務,但自然的各個節點時鐘存在偏差,致使須要引入特殊的事務序列號分配機制,好比HLC機制,或者TrueTime機制。TrueTime機制本質就是硬件方案,將集羣中節點間的物理時鐘偏差控制在很小的範圍內,結合commit-wait模式,保證全部事務全局有序,理論上要保證全局有序,TrueTime並不是是充分條件,只須要commit-wait便可,就看wait的時間長短了。HLC機制則是軟件方案,只保證事務因果序,而且解決了本地時鐘跳變問題。HLC機制配合NTP服務,也能提供必定偏差範圍內的快照讀。