一. 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/