先從TiDB說起
TiDB 是什么
首先引用一下官方的定義:
TiDB 是 PingCAP 公司受 Google Spanner / F1 論文啟發(fā)而設計的開源分布式 HTAP (Hybrid Transactional and Analytical Processing) 數(shù)據(jù)庫纠屋,結合了傳統(tǒng)的 RDBMS 和 NoSQL 的最佳特性。TiDB 兼容 MySQL,支持無限的水平擴展窘游,具備強一致性和高可用性甸私。TiDB 的目標是為 OLTP (Online Transactional Processing) 和 OLAP (Online Analytical Processing) 場景提供一站式的解決方案。
TiDB 具備如下核心特性:
- 高度兼容 MySQL
大多數(shù)情況下神帅,無需修改代碼即可從 MySQL 輕松遷移至 TiDB罕容,分庫分表后的 MySQL 集群亦可通過 TiDB 工具進行實時遷移。
- 水平彈性擴展
通過簡單地增加新節(jié)點即可實現(xiàn) TiDB 的水平擴展高帖,按需擴展吞吐或存儲缰儿,輕松應對高并發(fā)、海量數(shù)據(jù)場景散址。
- 分布式事務
TiDB 100% 支持標準的 ACID 事務乖阵。
- 真正金融級高可用
相比于傳統(tǒng)主從 (M-S) 復制方案,基于 Raft 的多數(shù)派選舉協(xié)議可以提供金融級的 100% 數(shù)據(jù)強一致性保證预麸,且在不丟失大多數(shù)副本的前提下瞪浸,可以實現(xiàn)故障的自動恢復 (auto-failover),無需人工介入吏祸。
- 一站式 HTAP 解決方案
TiDB 作為典型的 OLTP 行存數(shù)據(jù)庫对蒲,同時兼具強大的 OLAP 性能,配合 TiSpark贡翘,可提供一站式 HTAP 解決方案齐蔽,一份存儲同時處理 OLTP & OLAP,無需傳統(tǒng)繁瑣的 ETL 過程床估。
TiDB 的設計目標是 100% 的 OLTP 場景和 80% 的 OLAP 場景含滴。它解決了我們工作中面臨的因為數(shù)據(jù)量大到一定程度后橫向擴展受限,人工進行分庫分表丐巫,人工進行數(shù)據(jù)庫事務的問題谈况。降低了開發(fā)人員的心智負擔,讓開發(fā)人員可以更專注于具體業(yè)務递胧,提高了生產(chǎn)效率碑韵。
TiDB 整體架構
TiDB 有 TiDB Server 、PD Server缎脾、 TiKV Server 三個核心組件祝闻。
PD 是整個集群的管理模塊,它擁有上帝視角遗菠。 其主要工作有三個:一是存儲集群的元信息(某個 Key 存儲在哪個 TiKV 節(jié)點)联喘;二是對 TiKV 集群進行調度和負載均衡(如數(shù)據(jù)的遷移、Raft group leader 的遷移等)辙纬;三是分配全局唯一且遞增的事務 ID豁遭。
TiKV Server 是一個分布式的提供事務的 Key-Value 存儲引擎。 TiDB Server 負責接收 SQL 請求贺拣,處理 SQL 相關的邏輯蓖谢,并通過 PD 找到存儲計算所需數(shù)據(jù)的 TiKV 地址捂蕴,與 TiKV 交互獲取數(shù)據(jù),最終返回結果闪幽。
TiDB Server 是無狀態(tài)的啥辨,其本身并不存儲數(shù)據(jù),只負責計算盯腌,可以無限水平擴展委可,
在執(zhí)行一條Insert語句會做什么事情?
畫不多說腊嗡, 先上圖:
不要被這么復雜的圖嚇怕,因為其中的絕大部分內容都不會涉及(我也不會拾酝,不會還要拿來show --!!)燕少。
主體流程還是很容易明白的,首先蒿囤,TiDB會解析執(zhí)行的SQL語句客们,使用parser 將其變成AST,然后通過執(zhí)行計劃將其拆解成一個個的executor材诽。在獲取到所有的executor的執(zhí)行結果后底挫,對數(shù)據(jù)進行拼裝,最終返回給客戶端脸侥。
那么建邓,在數(shù)據(jù)庫中數(shù)據(jù)是怎么進行存儲,Server又是如何進行訪問的呢睁枕?
其實官边,數(shù)據(jù)庫將表中的row映射成kv結構,然后將kv進行物理存儲外遇。MySQL如此注簿,TiDB也是如此。這種想法其實也很自然跳仿,數(shù)據(jù)庫表中的每行使用id 作為key诡渴,行的內容作為value存儲下來,查詢某條記錄菲语,就變成了根據(jù)某個key妄辩,找對應的value。
Key的生成策略
首先山上,我們需要面對的問題就是對于一個數(shù)據(jù)庫表恩袱,使用什么樣的key生成策略來保存行的內容。同時因為數(shù)據(jù)庫的索引結構也可以用表存儲胶哲,因此畔塔,數(shù)據(jù)庫的索引也會被持久化到kv 中。
TiDB 對每個表分配一個 TableID,每一個索引都會分配一個 IndexID澈吨,每一行分配一個 RowID(如果表有整數(shù)型的 Primary Key把敢,那么會用 Primary Key 的值當做 RowID),其中 TableID 在整個集群內唯一谅辣,IndexID/RowID 在表內唯一修赞,這些 ID 都是 int64 類型。
- 數(shù)據(jù)庫行的key生成策略
${tablePrefix}_${rowPrefix}_${tableID}_${rowID}
其中tablePrefix
是固定的t_
桑阶, rowPrefix
是固定的r_
柏副。比如 table table_demo
的ID 為10,其中第一條數(shù)據(jù)的row ID 是1蚣录,那么它的key是t_r_10_1
- 數(shù)據(jù)庫索引key的生成策略
- 主鍵或者唯一鍵的索引
${tablePrefix}_${idxPrefix}_${tableID}_${indexID}_${indexColumnsValue}
其中idxPrefix
是固定的i_
割择, 比如id字段的索引ID 為2, 那么它對應的key 是 t_i_10_2_1
--> t_r_10_1
- 非主鍵索引
${tablePrefix}_${idxPrefix}_${tableID}_${indexID}_${ColumnsValue}_${rowID}
具體的細節(jié)可以查看參考資料萎河,里面有非常詳細的表述荔泳。這里不做過多的解釋
TiDB 和TiKV的交互接口
在整個學習的過程中,頭腦中需要有一個問題虐杯,select
玛歌,insert
操作是怎么在TiKV中實現(xiàn)的。
既然TiDB 已經(jīng)將具體的數(shù)據(jù)庫的row擎椰, 轉化成了kv支子,那么問題本身也就轉化成了在TiKV中如何實現(xiàn)get
,set
命令达舒。
這里先列舉相關的TiKV暴露給TiDB調用的接口译荞,學習的過程可以從這里入手。
service Tikv {
// KV commands with mvcc/txn supported.
rpc KvGet(kvrpcpb.GetRequest) returns (kvrpcpb.GetResponse) {}
rpc KvScan(kvrpcpb.ScanRequest) returns (kvrpcpb.ScanResponse) {}
rpc KvPrewrite(kvrpcpb.PrewriteRequest) returns (kvrpcpb.PrewriteResponse) {}
rpc KvCommit(kvrpcpb.CommitRequest) returns (kvrpcpb.CommitResponse) {}
rpc KvImport(kvrpcpb.ImportRequest) returns (kvrpcpb.ImportResponse) {}
rpc KvCleanup(kvrpcpb.CleanupRequest) returns (kvrpcpb.CleanupResponse) {}
rpc KvBatchGet(kvrpcpb.BatchGetRequest) returns (kvrpcpb.BatchGetResponse) {}
rpc KvBatchRollback(kvrpcpb.BatchRollbackRequest) returns (kvrpcpb.BatchRollbackResponse) {}
rpc KvScanLock(kvrpcpb.ScanLockRequest) returns (kvrpcpb.ScanLockResponse) {}
rpc KvResolveLock(kvrpcpb.ResolveLockRequest) returns (kvrpcpb.ResolveLockResponse) {}
rpc KvGC(kvrpcpb.GCRequest) returns (kvrpcpb.GCResponse) {}
rpc KvDeleteRange(kvrpcpb.DeleteRangeRequest) returns (kvrpcpb.DeleteRangeResponse) {}
// RawKV commands.
rpc RawGet(kvrpcpb.RawGetRequest) returns (kvrpcpb.RawGetResponse) {}
rpc RawBatchGet(kvrpcpb.RawBatchGetRequest) returns (kvrpcpb.RawBatchGetResponse) {}
rpc RawPut(kvrpcpb.RawPutRequest) returns (kvrpcpb.RawPutResponse) {}
rpc RawBatchPut(kvrpcpb.RawBatchPutRequest) returns (kvrpcpb.RawBatchPutResponse) {}
rpc RawDelete(kvrpcpb.RawDeleteRequest) returns (kvrpcpb.RawDeleteResponse) {}
rpc RawBatchDelete(kvrpcpb.RawBatchDeleteRequest) returns (kvrpcpb.RawBatchDeleteResponse) {}
rpc RawScan(kvrpcpb.RawScanRequest) returns (kvrpcpb.RawScanResponse) {}
rpc RawDeleteRange(kvrpcpb.RawDeleteRangeRequest) returns (kvrpcpb.RawDeleteRangeResponse) {}
rpc RawBatchScan(kvrpcpb.RawBatchScanRequest) returns (kvrpcpb.RawBatchScanResponse) {}
// SQL push down commands.
rpc Coprocessor(coprocessor.Request) returns (coprocessor.Response) {}
rpc CoprocessorStream(coprocessor.Request) returns (stream coprocessor.Response) {}
// Raft commands (tikv <-> tikv).
rpc Raft(stream raft_serverpb.RaftMessage) returns (raft_serverpb.Done) {}
rpc Snapshot(stream raft_serverpb.SnapshotChunk) returns (raft_serverpb.Done) {}
// Region commands.
rpc SplitRegion (kvrpcpb.SplitRegionRequest) returns (kvrpcpb.SplitRegionResponse) {}
// transaction debugger commands.
rpc MvccGetByKey(kvrpcpb.MvccGetByKeyRequest) returns (kvrpcpb.MvccGetByKeyResponse) {}
rpc MvccGetByStartTs(kvrpcpb.MvccGetByStartTsRequest) returns (kvrpcpb.MvccGetByStartTsResponse) {}
}