TiDB 簡介
TiDB 是 PingCAP 公司自主設計、研發的開源分布式關系型數據 庫,是一款同時支持在線事務處理與在線分析處理 (Hybrid Transactional and Analytical Processing, HTAP) 的融合型分布 式數據庫產品,具備水平擴容或者縮容、金融級高可用、實時 HTAP、云原生的分布式數據庫、兼容 MySQL 5.7 協議和 MySQL 生態等重要特性。目標是為用戶提供一站式 OLTP (Online Transactional Processing)、OLAP (Online Analytical Processing)、HTAP 解決方案。TiDB 適合高可用、強一致要求 較高、數據規模較大等各種應用場景。
TiDB用來解決mysql單節點容量問題,有了TiDB之后,以往的技術如用redis緩存MySQL的數據的這種方案就沒有必要存在了,MySQL的分布分表,各種復雜的不同的水平分表分庫、垂直分表分庫也沒有必要去學了。TiDB都可以解決這些問題,因為它是一種分布式的。
分布式系統
分布式系統是一種其組件位于不同的聯網計算機上的系統,然 后通過互相傳遞消息來進行通訊和協調,為了達到共同的目 標,這些組件會相互作用;換句話說,分布式系統把需要進行 大量計算的工程數據分割成若干個小塊,由多臺計算機分別進 行計算和存儲,然后將結果統一合并到數據結論的科學;本質 上就是進行數據存儲與計算的分治;
帶來的問題:CAP理論:一致性、可用性(主數據庫宕機,從數據庫進行替換使用)、分區容錯性(分布式系統在遇到某節點或網絡分區故障的時候仍然能夠對外 提供滿足一致性或可用性的服務;)
應用場景
1、對數據一致性及高可靠、系統高可用、可擴展性、容災要求較高的金融行業屬性的場景。
TiDB 采用多副本 + Multi-Raft 協議的方式將數據調度到不同的機房、機架、機器,當部分機器出現故障時系統可自動進行切換,確保系統的 RTO <= 30s 及 RPO = 0。
RTO(Recovery Time Objective):恢復時間目標,即在發生系統故障或災難事件時,恢復系統功能所需的時間。它代表了組織能夠接受的最長系統停機時間。
RPO(Recovery Point Objective):恢復點目標,即在發生系統故障或災難事件時,允許數據丟失的時間范圍。它表示組織庫接受的數據丟失程度。
RTO關注的是從故障中恢復正常操作所需的時間,而RPO關注的是在故障發生前可接受丟失的數據量。
2、對存儲容量、可擴展性、并發要求較高的海量數據及高并發的 OLTP 場景。
TiDB 采用計算、存儲分離的架構,可對計算、存儲分別進行 擴容和縮容,計算最大支持 512 節點,每個節點最大支持 1000 并發,集群容量最大支持 PB 級別。
3、Real-time HTAP 場景
TiDB 在 4.0 版本中引入列存儲引擎 TiFlash 結合行存儲引擎 TiKV 構建真正的 HTAP 數據庫,在增加少量存儲成本的情況 下,可以在同一個系統中做聯機交易處理、實時數據分析, 極大地節省企業的成本。
4、數據匯聚、二次加工處理的場景
業務通過 ETL 工具或者 TiDB 的同步工具將數據同步到 TiDB,在 TiDB 中可通過 SQL 直接生成報表。(ETL 是將業務系統的數據經過抽取、清洗轉換之后加載到數據倉庫的過程,目的是將企業中的分散、零亂、標準不統一的數據整合到一起,為企業的決策提供分析依據)
關系型模型
在傳統的在線交易場景里,關系型模型仍然是標準;關系型數 據庫的關鍵在于一定要具備事務;
事務
事務的本質是:并發控制的單元,是用戶定義的一個操作序列;這些操作要么都做,要么都不做,是一個不可分割的工作單位;
為了保證系統始終處于一個完整且正確的狀態;
ACID 特性
原子性:事務包含的全部操作時一個不可分割的整體;要么全部執 行,要么全部不執行;
一致性:事務的前后,所有的數據都保持一個一致的狀態;不能違反 數據的一致性檢測;
隔離性:各個并發事務之間互相影響的程度;主要規定多個并發事務 訪問同一個數據資源,各個并發事務對該數據資源訪問的行 為;不同的隔離性是應對不同的現象(臟讀、可重復讀、幻 讀等);
持久性:事務一旦完成要將數據所做的變更記錄下來;包括數據存儲 和多副本的網絡備份;
TiDB可以視為分布式的mysql
TiDB 部署本地測試集群
# 下載并安裝
TiUP curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh
# 聲明全局環境變量
source .bash_profile
# 運行最新版本的 TiDB 集群, 其中 TiDB、TiKV、PD 和 TiFlash 實例各 1 個
tiup playground
# 如果想指定版本并運行多個
tiup playground v4.0.16 --db 3 --pd 3 --kv 3 -- monitor
# 注意: 按照上面的部署, 在結束部署測試后 TiUP 會清理掉原 集群數據,重新執行該命令后會得到一個全新的集群。 # 如果希望持久化數據, 并指定存儲目錄為 /tmp/tidb
tiup playground -T /tmp/tidb --host 0.0.0.0
與傳統非分布式數據庫架構對比
-
兩者都支持 ACID、事務強一致性;
-
分布式架構,組件解耦,擁有良好的擴展性,支持彈性的擴縮容;
-
默認支持高可用,在少數副本失效的情況下,數據庫能夠自動進 行故障轉移,對業務透明;
-
采用水平擴展,在大數據量、高吞吐的業務場景中具有先天優勢;
-
強項不在于輕量的簡單 SQL 的響應速度,而在于大量高并發 SQL 的吞吐;
TiDB 分布式數據庫整體架構
-
由多模塊組成,各模塊互相通信,組成完整的 TiDB 系統;
-
前端 stateless、后端 stateful (Raft);
-
兼容 MySQL;
TiDB Server 的模塊
SQL 層,對外暴露 MySQL 協議的連接 endpoint,負責接受客 戶端的連接,執行 SQL 解析和優化,最終生成分布式執行計 劃。TiDB 層本身是無狀態的,實踐中可以啟動多個 TiDB 實例,通過負載均衡組件(如 LVS、HAProxy 或 F5)對外提供統 一的接入地址,客戶端的連接可以均勻地分攤在多個 TiDB 實例 上以達到負載均衡的效果。TiDB Server 本身并不存儲數據,只是解析SQL,將實際的數據讀取請求轉發給底層的存儲節點 TiKV(或 TiFlash)。
數據映射關系
數據與 KV 的映射關系中定義如下:
tablePrefix = []byte{'t'}
recordPrefixSep = []byte{'r'}
indexPrefixSep = []byte{'i'}
假設表結構如下:
CREATE TABLE User ( ID int, Name varchar(20), Role varchar(20), Age int, UID int, PRIMARY KEY (ID), KEY idxAge (Age), UNIQUE KEY idxUID (UID)
);
假設表數據如下:
1, "TiDB", "SQL Layer", 10, 10001
2, "TiKV", "KV Engine", 20, 10002
3, "PD", "Manager", 30, 10003
表數據與 KV 的映射關系
Key 的形式:tablePrefix{TableID}_recordPrefixSep{RowID}
;
Value 的形式:[col1, col2, col3, col4]
映射示例:
# 假設系統為user表分配了表ID為10
t10_r1 --> ["TiDB", "SQL Layer", 10, 10001]
t10_r2 --> ["TiKV", "KV Engine", 20, 10002]
t10_r3 --> ["PD", "Manager", 30, 10003]
索引數據和 KV 的映射關系
對于唯一索引:
Key 的形式: tablePrefix{tableID}_indexPrefixSep{indexID}_indexe dColumnsValue
Value 的形式: RowID
映射示例:
# 假設系統為idxUID 分配的索引ID為3
t10_i3_10001 --> 1
t10_i3_10002 --> 2
t10_i3_10003 --> 3
非唯一索引:
Key 的形式: tablePrefix{TableID}_indexPrefixSep{IndexID}_indexe dColumnsValue_{RowID}
Value 的形式:null
映射示例:
# 假設系統為idxAge 分配的索引ID為2
t10_i2_10_1 --> null
t10_i2_20_2 --> null
t10_i2_30_3 --> null
PD(Placement Driver)Server
整個 TiDB 集群的元信息管理模塊,負責存儲每個 TiKV 節點實 時的數據分布情況和集群的整體拓撲結構,提供 TiDB Dashboard 管控界面,并為分布式事務分配事務 ID。PD 不僅 存儲元信息,同時還會根據 TiKV 節點實時上報的數據分布狀 態,下發數據調度命令給具體的 TiKV 節點,可以說是整個集群 的“大腦”。此外,PD 本身也是由至少 3 個節點構成,擁有高可 用的能力。建議部署奇數個 PD 節點。
調度需求
-
作為一個分布式高可用存儲系統,必須滿足的需求,包括幾種:
-
副本數量不能多也不能少
-
副本需要根據拓撲結構分布在不同屬性的機器上
-
節點宕機或異常能夠自動合理快速地進行容災
-
作為一個良好的分布式系統,需要考慮的地方包括:
-
維持整個集群的 Leader 分布均勻
-
維持每個節點的儲存容量均勻
-
維持訪問熱點分布均勻
-
控制負載均衡的速度,避免影響在線服務
-
管理節點狀態,包括手動上線/下線節點
滿足第一類需求后,整個系統將具備強大的容災功能。滿足第 二類需求后,可以使得系統整體的資源利用率更高且合理,具 備良好的擴展性。
調度操作
-
增加一個副本
-
刪除一個副本
-
將 Leader 角色在一個 Raft Group 的不同副本之間 transfer (遷移)。
信息收集
-
每個 TiKV 節點會定期向 PD 匯報節點的狀態信息
-
每個 Raft Group 的 Leader 會定期向 PD 匯報 Region 的狀態信 息
存儲節點
TiKV Server
負責存儲數據,從外部看 TiKV 是一個分布式的提供事務的 KeyValue 存儲引擎。存儲數據的基本單位是 Region,每個 Region 負責存儲一個 Key Range(從 StartKey 到 EndKey 的左閉右開 區間)的數據,每個 TiKV 節點會負責多個 Region。TiKV 的 API 在 KV 鍵值對層面提供對分布式事務的原生支持,默認提供了 SI (Snapshot Isolation) 的隔離級別,這也是 TiDB 在 SQL 層 面支持分布式事務的核心。TiDB 的 SQL 層做完 SQL 解析后, 會將 SQL 的執行計劃轉換為對 TiKV API 的實際調用。所以,數 據都存儲在 TiKV 中。另外,TiKV 中的數據都會自動維護多副本 (默認為三副本),天然支持高可用和自動故障轉移。
TiFlash
TiFlash 是一類特殊的存儲節點。和普通 TiKV 節點不一樣的 是,在 TiFlash 內部,數據是以列式的形式進行存儲,主要的功 能是為分析型的場景加速。
-
列式存儲可以滿足快速讀取特定列的需求,在線分析處理往往需 要在上百列的寬表中讀取指定列分析;
-
列式存儲就近存儲同一列的數據,使用壓縮算法可以得到更高的壓縮率,減少存儲占用的磁盤空間;
RocksDB
RocksDB 作為 TiKV 的核心存儲引擎,用于存儲 Raft 日志以及 用戶數據。每個 TiKV 實例中有兩個 RocksDB 實例,一個用于 存儲 Raft 日志(通常被稱為 raftdb),另一個用于存儲用戶數 據以及 MVCC 信息(通常被稱為 kvdb)。kvdb 中有四個 ColumnFamily:raft、lock、default 和 write:
-
raft 列:用于存儲各個 Region 的元信息。僅占極少量空間,用 戶可以不必關注。
-
lock 列:用于存儲悲觀事務的悲觀鎖以及分布式事務的一階段 Prewrite 鎖。當用戶的事務提交之后,lock cf 中對應的數據會 很快刪除掉,因此大部分情況下 lock cf 中的數據也很少(少于 1GB)。如果 lock cf 中的數據大量增加,說明有大量事務等待提交,系統出現了 bug 或者故障。
-
write 列:用于存儲用戶真實的寫入數據以及 MVCC 信息(該數 據所屬事務的開始時間以及提交時間)。當用戶寫入了一行數據 時,如果該行數據長度小于 255 字節,那么會被存儲 write 列 中,否則的話該行數據會被存入到 default 列中。由于 TiDB 的 非 unique 索引存儲的 value 為空,unique 索引存儲的 value 為主鍵索引,因此二級索引只會占用 writecf 的空間。
-
default 列:用于存儲超過 255 字節長度的數據。
內存占用
為了提高讀取性能以及減少對磁盤的讀取,RocksDB 將存儲在 磁盤上的文件都按照一定大小切分成 block(默認是 64KB), 讀取 block 時先去內存中的 BlockCache 中查看該塊數據是否存 在,存在的話則可以直接從內存中讀取而不必訪問磁盤。
BlockCache 按照 LRU 算法淘汰低頻訪問的數據,TiKV 默認將 系統總內存大小的 45% 用于 BlockCache,用戶也可以自行修 改 storage.block-cache.capacity
配置設置為合適的值,但 是不建議超過系統總內存的 60%。
寫入 RocksDB 中的數據會寫入 MemTable,當一個 MemTable 的大小超過 128MB 時,會切換到一個新的 MemTable 來提供寫入。TiKV 中一共有 2 個 RocksDB 實例, 合計 4 個 ColumnFamily,每個 ColumnFamily 的單個 MemTable 大小限制是 128MB,最多允許 5 個 MemTable 存 在,否則會阻塞前臺寫入,因此這部分占用的內存最多為 4 x 5 x 128MB = 2.5GB。這部分占用內存較少,不建議用戶自行更 改。
空間占用
-
多版本:RocksDB 作為一個 LSM-tree 結構的鍵值存儲引擎, MemTable 中的數據會首先被刷到 L0。L0 層的 SST 之間的范圍 可能存在重疊(因為文件順序是按照生成的順序排列),因此同 一個 key 在 L0 中可能存在多個版本。當文件從 L0 合并到 L1 的 時候,會按照一定大小(默認是 8MB)切割為多個文件,同一 層的文件的范圍互不重疊,所以 L1 及其以后的層每一層的 key 都只有一個版本。
-
空間放大:RocksDB 的每一層文件總大小都是上一層的 x 倍, 在 TiKV 中這個配置默認是 10,因此 90% 的數據存儲在最后一層,這也意味著 RocksDB 的空間放大不超過 1.11(L0 層的數 據較少,可以忽略不計)。
-
TiKV 的空間放大:TiKV 在 RocksDB 之上還有一層自己的 MVCC,當用戶寫入一個 key 的時候,實際上寫入到 RocksDB 的是 key + commit_ts,也就是說,用戶的更新和刪除都是會寫 入新的 key 到 RocksDB。TiKV 每隔一段時間會刪除舊版本的數 據(通過 RocksDB 的 Delete 接口),因此可以認為用戶存儲在 TiKV 上的數據的實際空間放大為,1.11 加最近 10 分鐘內寫入 的數據(假設 TiKV 回收舊版本數據足夠及時)。
compact
RocksDB 中,將內存中的 MemTable 轉化為磁盤上的 SST 文 件,以及合并各個層級的 SST 文件等操作都是在后臺線程池中 執行的。后臺線程池的默認大小是 8,當機器 CPU 數量小于等 于 8 時,則后臺線程池默認大小為 CPU 數量減一。通常來說, 用戶不需要更改這個配置。如果用戶在一個機器上部署了多個 TiKV 實例,或者機器的讀負載比較高而寫負載比較低,那么可 以適當調低 rocksdb/max-background-jobs
至 3 或者 4。
WriteStall(寫停頓)
RocksDB 的 L0 與其他層不同,L0 的各個 SST 是按照生成順序 排列,各個 SST 之間的 key 范圍存在重疊,因此查詢的時候必 須依次查詢 L0 中的每一個 SST。為了不影響查詢性能,當 L0 中的文件數量過多時,會觸發 WriteStall 阻塞寫入。
如果用戶遇到了寫延遲突然大幅度上漲,可以先查看 Grafana RocksDB KV 面板 WriteStall Reason 指標,如果是 L0 文件數 量過多引起的 WriteStall,可以調整下面幾個配置到 64。
rocksdb.defaultcf.level0-slowdown-writes-trigger
rocksdb.writecf.level0-slowdown-writes-trigger
rocksdb.lockcf.level0-slowdown-writes-trigger
rocksdb.defaultcf.level0-stop-writes-trigger
rocksdb.writecf.level0-stop-writes-trigger
rocksdb.lockcf.level0-stop-writes-trigger
參考連接:https://github.com/0voice