TiDB 架構及設計實現
一. TiDB的核心特性
高度相容 MySQL
大多數情況下,無需修改程式碼即可從 MySQL 輕鬆遷移至 TiDB,分庫分表後的 MySQL 叢集亦可通過 TiDB 工具進行實時遷移。
水平彈性擴充套件
通過簡單地增加新節點即可實現 TiDB 的水平擴充套件,按需擴充套件吞吐或儲存,輕鬆應對高併發、海量資料場景。
分散式事務
TiDB 100% 支援標準的 ACID 事務。
高可用
相比於傳統主從 (M-S) 複製方案,基於 Raft 的多數派選舉協議可以提供金融級的 100% 資料強一致性保證,且在不丟失大多數副本的前提下,可以實現故障的自動恢復 (auto-failover),無需人工介入。
一站式 HTAP 解決方案
TiDB 作為典型的 OLTP 行存資料庫,同時兼具強大的 OLAP 效能,配合 TiSpark,可提供一站式 HTAP 解決方案,一份儲存同時處理 OLTP & OLAP,無需傳統繁瑣的 ETL 過程。
雲原生 SQL 資料庫
TiDB 是為雲而設計的資料庫,同 Kubernetes 深度耦合,支援公有云、私有云和混合雲,使部署、配置和維護變得十分簡單。
二.TiDB 整體架構
TiDB Server
TiDB Server 負責接收SQL請求,處理SQL相關的邏輯,並通過PD找到儲存計算所需資料的TiKV地址,與TiKV互動獲取資料,最終返回結果。TiDB Server 是無狀態的,其本身並不儲存資料,只負責計算,可以無限水平擴充套件,可以通過負載均衡元件(LVS、HAProxy或F5)對外提供統一的接入地址。
PD Server
Placement Driver(簡稱PD)是整個叢集的管理模組,其主要工作有三個:一是儲存叢集的元資訊(某個Key儲存在那個TiKV節點);二是對TiKV叢集進行排程和負載均衡(如資料的遷移、Raft group leader的遷移等);三是分配全域性唯一且遞增的事務ID。
PD 是一個叢集,需要部署奇數個節點,一般線上推薦至少部署3個節點。PD在選舉的過程中無法對外提供服務,這個時間大約是3秒。
TiKV Server
TiKV Server 負責儲存資料,從外部看TiKV是一個分散式的提供事務的Key-Value儲存引擎。儲存資料的基本單位是Region,每個Region負責儲存一個Key Range(從StartKey到EndKey的左閉右開區間)的資料,每個TiKV節點會負責多個Region。TiKV使用Raft協議做複製,保持資料的一致性和容災。副本以Region為單位進行管理,不同節點上的多個Region構成一個Raft Group,互為副本。資料在多個TiKV之間的負載均衡由PD排程,這裡也就是以Region為單位進行排程
三. 儲存結構
一個 Region 的多個 Replica 會儲存在不同的節點上,構成一個 Raft Group。其中一個 Replica 會作為這個 Group 的 Leader,其他的 Replica 作為 Follower。所有的讀和寫都是通過 Leader 進行,再由 Leader 複製給 Follower。
Key-Value 模型
TiDB對每個表分配一個TableID,每一個索引都會分配一個IndexID,每一行分配一個RowID(如果表有整形的Primary Key,那麼會用Primary Key的值當做RowID),其中TableID在整個叢集內唯一,IndexID/RowID 在表內唯一,這些ID都是int64型別。每行資料按照如下規則進行編碼成Key-Value pair:
Key: tablePrefix_rowPrefix_tableID_rowID
Value: [col1, col2, col3, col4]
其中Key的tablePrefix/rowPrefix都是特定的字串常量,用於在KV空間內區分其他資料。對於Index資料,會按照如下規則編碼成Key-Value pair
Key: tablePrefix_idxPrefix_tableID_indexID_indexColumnsValue
Value: rowID
Index 資料還需要考慮Unique Index 和 非 Unique Index兩種情況,對於Unique Index,可以按照上述編碼規則。但是對於非Unique Index,通常這種編碼並不能構造出唯一的Key,因為同一個Index的tablePrefix_idxPrefix_tableID_indexID_都一樣,可能有多行資料的ColumnsValue都是一樣的,所以對於非Unique Index的編碼做了一點調整:
Key: tablePrefix_idxPrefix_tableID_indexID_ColumnsValue_rowID
Value:null
這樣能夠對索引中的每行資料構造出唯一的Key。注意上述編碼規則中的Key裡面的各種xxPrefix都是字串常量,作用都是用來區分名稱空間,以免不同型別的資料之間互相沖突,定義如下:
var(
tablePrefix = []byte{'t'}
recordPrefixSep = []byte("_r")
indexPrefixSep = []byte("_i")
)
舉個簡單的例子,假設表中有3行資料:
1,“TiDB”, “SQL Layer”, 10
2,“TiKV”, “KV Engine”, 20
3,“PD”, “Manager”, 30
那麼首先每行資料都會對映為一個Key-Value pair,注意,這個表有一個Int型別的Primary Key,所以RowID的值即為這個Primary Key的值。假設這個表的Table ID 為10,其中Row的資料為:
t_r_10_1 --> ["TiDB", "SQL Layer", 10]
t_r_10_2 --> ["TiKV", "KV Engine", 20]
t_r_10_3 --> ["PD", "Manager", 30]
除了Primary Key之外,這個表還有一個Index,假設這個Index的ID為1,其資料為:
t_i_10_1_10_1 --> null
t_i_10_1_20_2 --> null
t_i_10_1_30_3 --> null
Database/Table 都有元資訊,也就是其定義以及各項屬性,這些資訊也需要持久化,我們也將這些資訊儲存在TiKV中。每個Database/Table都被分配了一個唯一的ID,這個ID作為唯一標識,並且在編碼為Key-Value時,這個ID都會編碼到Key中,再加上m_字首。這樣可以構造出一個Key,Value中儲存的是序列化後的元資料。除此之外,還有一個專門的Key-Value儲存當前Schema資訊的版本。TiDB使用Google F1的Online Schema變更演算法,有一個後臺執行緒在不斷的檢查TiKV上面儲存的Schema版本是否發生變化,並且保證在一定時間內一定能夠獲取版本的變化(如果確實發生了變化)。
四. SQL 運算
使用者的 SQL 請求會直接或者通過 Load Balancer 傳送到 tidb-server,tidb-server 會解析 MySQL Protocol Packet,獲取請求內容,然後做語法解析、查詢計劃制定和優化、執行查詢計劃獲取和處理資料。資料全部儲存在 TiKV 叢集中,所以在這個過程中 tidb-server 需要和 tikv-server 互動,獲取資料。最後 tidb-server 需要將查詢結果返回給使用者。
五. 調 度
排程的流程
PD 不斷的通過 Store 或者 Leader 的心跳包收集資訊,獲得整個叢集的詳細資料,並且根據這些資訊以及排程策略生成排程操作序列,每次收到 Region Leader 發來的心跳包時,PD 都會檢查是否有對這個 Region 待進行的操作,通過心跳包的回覆訊息,將需要進行的操作返回給 Region Leader,並在後面的心跳包中監測執行結果。
注意這裡的操作只是給 Region Leader 的建議,並不保證一定能得到執行,具體是否會執行以及什麼時候執行,由 Region Leader 自己根據當前自身狀態來定。
資訊收集
排程依賴於整個叢集資訊的收集,需要知道每個TiKV節點的狀態以及每個Region的狀態。TiKV叢集會向PD彙報兩類資訊:
(1)每個TiKV節點會定期向PD彙報節點的整體資訊。
TiKV節點(Store)與PD之間存在心跳包,一方面PD通過心跳包檢測每個Store是否存活,以及是否有新加入的Store;另一方面,心跳包中也會攜帶這個Store的狀態資訊,主要包括:
a) 總磁碟容量
b) 可用磁碟容量
c) 承載的Region數量
d) 資料寫入速度
e) 傳送/接受的Snapshot數量(Replica之間可能會通過Snapshot同步資料)
f) 是否過載
g) 標籤資訊(標籤是否具備層級關係的一系列Tag)
(2)每個 Raft Group 的 Leader 會定期向 PD 彙報Region資訊
每個Raft Group 的 Leader 和 PD 之間存在心跳包,用於彙報這個Region的狀態,主要包括下面幾點資訊:
a) Leader的位置
b) Followers的位置
c) 掉線Replica的個數
d) 資料寫入/讀取的速度
PD 不斷的通過這兩類心跳訊息收集整個叢集的資訊,再以這些資訊作為決策的依據。
除此之外,PD 還可以通過管理介面接受額外的資訊,用來做更準確的決策。比如當某個 Store 的心跳包中斷的時候,PD 並不能判斷這個節點是臨時失效還是永久失效,只能經過一段時間的等待(預設是 30 分鐘),如果一直沒有心跳包,就認為是 Store 已經下線,再決定需要將這個 Store 上面的 Region 都排程走。但是有的時候,是運維人員主動將某臺機器下線,這個時候,可以通過 PD 的管理介面通知 PD 該 Store 不可用,PD 就可以馬上判斷需要將這個 Store 上面的 Region 都排程走。
排程策略
PD 收集以上資訊後,還需要一些策略來制定具體的排程計劃。
一個Region的Replica數量正確
當PD通過某個Region Leader的心跳包發現這個Region的Replica的數量不滿足要求時,需要通過Add/Remove Replica操作調整Replica數量。出現這種情況的可能原因是:
A.某個節點掉線,上面的資料全部丟失,導致一些Region的Replica數量不足
B.某個掉線節點又恢復服務,自動接入叢集,這樣之前已經彌補了Replica的Region的Replica數量過多,需要刪除某個Replica
C.管理員調整了副本策略,修改了max-replicas的配置
訪問熱點數量在 Store 之間均勻分配
每個Store以及Region Leader 在上報資訊時攜帶了當前訪問負載的資訊,比如Key的讀取/寫入速度。PD會檢測出訪問熱點,且將其在節點之間分散開。
各個 Store 的儲存空間佔用大致相等
每個 Store 啟動的時候都會指定一個 Capacity 引數,表明這個 Store 的儲存空間上限,PD 在做排程的時候,會考慮節點的儲存空間剩餘量。
控制排程速度,避免影響線上服務
排程操作需要耗費 CPU、記憶體、磁碟 IO 以及網路頻寬,我們需要避免對線上服務造成太大影響。PD 會對當前正在進行的運算元量進行控制,預設的速度控制是比較保守的,如果希望加快排程(比如已經停服務升級,增加新節點,希望儘快排程),那麼可以通過 pd-ctl 手動加快排程速度。
支援手動下線節點
當通過 pd-ctl 手動下線節點後,PD 會在一定的速率控制下,將節點上的資料排程走。當排程完成後,就會將這個節點置為下線狀態。
一個 Raft Group 中的多個 Replica 不在同一個位置
內容個人梳理總結於TiDB官網 https://www.pingcap.com/docs-cn/