TiDB簡介及TiDB部署、原理和使用介紹
從MySQL架構(gòu)到TiDB
數(shù)據(jù)庫分類
? 介紹TiDB數(shù)據(jù)庫之前,先引入使用場景。如今的數(shù)據(jù)庫種類繁多,RDBMS(關(guān)系型數(shù)據(jù)庫)、NoSQL(Not Only SQL)、NewSQL,在數(shù)據(jù)庫領(lǐng)域均有一席之地,可謂百家爭鳴之勢。那么我們?yōu)槭裁匆獙W(xué)習(xí)使用TiDB呢?接下來就從我們最熟悉的MySQL的使用說起。
MySQL痛點(diǎn)
? 假設(shè)現(xiàn)在有一個(gè)高速發(fā)展的互聯(lián)網(wǎng)公司,核心業(yè)務(wù)庫MySQL的數(shù)據(jù)量已經(jīng)近億行,且還在不斷增長中,公司對于數(shù)據(jù)資產(chǎn)較為重視,所有數(shù)據(jù)要求多副本保存至少5年,且除了有對歷史數(shù)據(jù)進(jìn)行統(tǒng)計(jì)分析的離線報(bào)表業(yè)務(wù)外,還有一些針對用戶數(shù)據(jù)實(shí)時(shí)查詢的需求。
? 根據(jù)以往的MySQL使用經(jīng)驗(yàn),MySQL單表在 5000 萬行以內(nèi)時(shí),性能較好,單表超過5000萬行后,數(shù)據(jù)庫性能、可維護(hù)性都會極劇下降。當(dāng)然這時(shí)候可以做MySQL分庫分表,如使用Mycat或Sharding-jdbc。
MySQL分庫分表
MySQL分庫分表的優(yōu)點(diǎn)非常明顯
-
將大表拆分成小表,單表數(shù)據(jù)量可以控制到 5000 萬行以內(nèi),使MySQL 性能穩(wěn)定可控。
-
將單張大表拆分成小表后,能水平擴(kuò)展,通過部署到多臺服務(wù)器,提升整個(gè)集群的 QPS、TPS、Latency 等數(shù)據(jù)庫服務(wù)指標(biāo)。
MySQL分庫分表的缺點(diǎn)也非常明顯
-
分表跨實(shí)例后,產(chǎn)生分布式事務(wù)管理難題,一旦數(shù)據(jù)庫服務(wù)器宕機(jī),有事務(wù)不一致風(fēng)險(xiǎn)。
-
分表后,對 SQL 語句有一定限制,對業(yè)務(wù)方功能需求大打折扣。尤其對于實(shí)時(shí)報(bào)表統(tǒng)計(jì)類需求,限制非常之大。
-
分表后,需要維護(hù)的對象呈指數(shù)增長(MySQL實(shí)例數(shù)、需要執(zhí)行的 SQL 變更數(shù)量等)
MySQL痛點(diǎn)解決方案
基于以上核心痛點(diǎn),我們需要探索新的數(shù)據(jù)庫技術(shù)方案來應(yīng)對業(yè)務(wù)爆發(fā)式增長所帶來的挑戰(zhàn),為業(yè)務(wù)提供更好的數(shù)據(jù)庫服務(wù)支撐。
調(diào)研市場上的各大數(shù)據(jù)庫,我們可以考慮選用NewSQL技術(shù)來解決,因?yàn)镹ewSQL技術(shù)有如下顯著特點(diǎn):
- 無限水平擴(kuò)展能力
- 分布式強(qiáng)一致性,確保數(shù)據(jù) 100% 安全
- 完整的分布式事務(wù)處理能力與 ACID 特性
而TiDB數(shù)據(jù)庫 GitHub的活躍度及社區(qū)貢獻(xiàn)者方面都可以算得上是國際化的開源項(xiàng)目,是NewSQL技術(shù)中的代表性產(chǎn)品,所以我們可以選擇使用TiDB數(shù)據(jù)庫!
四大核心應(yīng)用場景
- 對數(shù)據(jù)一致性及高可靠、系統(tǒng)高可用、可擴(kuò)展性、容災(zāi)要求較高的金融行業(yè)屬性的場景
? 眾所周知,金融行業(yè)對數(shù)據(jù)一致性及高可靠、系統(tǒng)高可用、可擴(kuò)展性、容災(zāi)要求較高。傳統(tǒng)的解決方案是同城兩個(gè)機(jī)房提供服務(wù)、異地一個(gè)機(jī)房提供數(shù)據(jù)容災(zāi)能力但不提供服務(wù),此解決方案存在以下缺點(diǎn):資源利用率低、維護(hù)成本高、RTO (Recovery Time Objective) 及 RPO (Recovery Point Objective) 無法真實(shí)達(dá)到企業(yè)所期望的值。TiDB 采用多副本 + Multi-Raft 協(xié)議的方式將數(shù)據(jù)調(diào)度到不同的機(jī)房、機(jī)架、機(jī)器,當(dāng)部分機(jī)器出現(xiàn)故障時(shí)系統(tǒng)可自動(dòng)進(jìn)行切換,確保系統(tǒng)的 RTO <= 30s 及 RPO = 0。
- 對存儲容量、可擴(kuò)展性、并發(fā)要求較高的海量數(shù)據(jù)及高并發(fā)的 OLTP 場景
? 隨著業(yè)務(wù)的高速發(fā)展,數(shù)據(jù)呈現(xiàn)爆炸性的增長,傳統(tǒng)的單機(jī)數(shù)據(jù)庫無法滿足因數(shù)據(jù)爆炸性的增長對數(shù)據(jù)庫的容量要求,可行方案是采用分庫分表的中間件產(chǎn)品或者 NewSQL 數(shù)據(jù)庫替代、采用高端的存儲設(shè)備等,其中性價(jià)比最大的是 NewSQL 數(shù)據(jù)庫,例如:TiDB。TiDB 采用計(jì)算、存儲分離的架構(gòu),可對計(jì)算、存儲分別進(jìn)行擴(kuò)容和縮容,計(jì)算最大支持 512 節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)最大支持 1000 并發(fā),集群容量最大支持 PB 級別。
- Real-time HTAP 場景
? 隨著 5G、物聯(lián)網(wǎng)、人工智能的高速發(fā)展,企業(yè)所生產(chǎn)的數(shù)據(jù)會越來越多,其規(guī)??赡苓_(dá)到數(shù)百 TB 甚至 PB 級別,傳統(tǒng)的解決方案是通過 OLTP 型數(shù)據(jù)庫處理在線聯(lián)機(jī)交易業(yè)務(wù),通過 ETL 工具將數(shù)據(jù)同步到 OLAP 型數(shù)據(jù)庫進(jìn)行數(shù)據(jù)分析,這種處理方案存在存儲成本高、實(shí)時(shí)性差等多方面的問題。TiDB 在 4.0 版本中引入列存儲引擎 TiFlash 結(jié)合行存儲引擎 TiKV 構(gòu)建真正的 HTAP 數(shù)據(jù)庫,在增加少量存儲成本的情況下,可以在同一個(gè)系統(tǒng)中做聯(lián)機(jī)交易處理、實(shí)時(shí)數(shù)據(jù)分析,極大地節(jié)省企業(yè)的成本。
- 數(shù)據(jù)匯聚、二次加工處理的場景
? 當(dāng)前絕大部分企業(yè)的業(yè)務(wù)數(shù)據(jù)都分散在不同的系統(tǒng)中,沒有一個(gè)統(tǒng)一的匯總,隨著業(yè)務(wù)的發(fā)展,企業(yè)的決策層需要了解整個(gè)公司的業(yè)務(wù)狀況以便及時(shí)做出決策,故需要將分散在各個(gè)系統(tǒng)的數(shù)據(jù)匯聚在同一個(gè)系統(tǒng)并進(jìn)行二次加工處理生成 T+0 或 T+1 的報(bào)表。傳統(tǒng)常見的解決方案是采用 ETL + Hadoop 來完成,但 Hadoop 體系太復(fù)雜,運(yùn)維、存儲成本太高無法滿足用戶的需求。與 Hadoop 相比,TiDB 就簡單得多,業(yè)務(wù)通過 ETL 工具或者 TiDB 的同步工具將數(shù)據(jù)同步到 TiDB,在 TiDB 中可通過 SQL 直接生成報(bào)表。
總結(jié)
? 傳統(tǒng)關(guān)系型數(shù)據(jù)庫歷史比較久,目前RDBMS的代表為Oracle、MySQL、PostgreSQL,在數(shù)據(jù)庫領(lǐng)域也是“輩份”比較高的,其廣泛應(yīng)用在各行各業(yè),RDBMS大多為本地存儲或共享存儲。
? 但是此類數(shù)據(jù)庫存在著一些問題,如自身容量的限制。隨著業(yè)務(wù)量不斷增加,容量漸漸成為瓶頸,此時(shí)DBA會通過多次的庫表sharding,以此來緩解容量問題。大量的分庫分表,不僅耗費(fèi)了大量人力,還使得業(yè)務(wù)訪問數(shù)據(jù)庫的路由邏輯變得復(fù)雜。除此之外,RDBMS伸縮性比較差,通常集群擴(kuò)容縮容成本較高,且不滿足分布式的事務(wù)。
? NoSQL類數(shù)據(jù)庫的代表為Hbase、Redis、MongoDB、Cassandra等,這類數(shù)據(jù)庫解決了 RDBMS伸縮性差的問題,集群容量擴(kuò)容變得方便很多,但是由于存儲方式為多個(gè)KV存儲,所以對SQL的兼容性就大打折扣。對于NoSQL類數(shù)據(jù)庫來說,只能滿足部分分布式事務(wù)的特點(diǎn)。
? NewSQL領(lǐng)域的代表是Google的spanner和F1,其號稱可以實(shí)現(xiàn)全球數(shù)據(jù)中心容災(zāi),且完全滿足分布式事務(wù)的ACID,但是只能在Google云上使用。TiDB誕生在大背景下,也彌補(bǔ)了國內(nèi)在NewSQL領(lǐng)域中的空缺。TiDB自2015年5月寫下第一行代碼以來,至今已發(fā)布大小版本幾十次,版本迭代十分迅速
TiDB數(shù)據(jù)庫簡介
TiDB概述
TiDB數(shù)據(jù)庫官方網(wǎng)址:https://pingcap.com/zh/
? TiDB 是 PingCAP 公司自主設(shè)計(jì)、研發(fā)的開源分布式關(guān)系型數(shù)據(jù)庫,是一款同時(shí)支持在線事務(wù)處理與在線分析處理 (Hybrid Transactional and Analytical Processing, HTAP) 的融合型分布式數(shù)據(jù)庫產(chǎn)品,具備水平擴(kuò)容或者縮容、金融級高可用、實(shí)時(shí) HTAP、云原生的分布式數(shù)據(jù)庫、兼容 MySQL 5.7 協(xié)議和 MySQL 生態(tài)等重要特性。目標(biāo)是為用戶提供一站式 OLTP (Online Transactional Processing)、OLAP (Online Analytical Processing)、HTAP 解決方案。TiDB 適合高可用、強(qiáng)一致要求較高、數(shù)據(jù)規(guī)模較大等各種應(yīng)用場景
數(shù)據(jù)庫分類梳理
SQL、NoSQL、NewSQL
SQL
關(guān)系型數(shù)據(jù)庫(RDBMS,即SQL數(shù)據(jù)庫)
商業(yè)軟件: Oracle,DB2
開源軟件:MySQL,PostgreSQL
關(guān)系型數(shù)據(jù)庫面臨的單機(jī)版本已經(jīng)很難滿足海量數(shù)據(jù)的需求
NoSQL
NoSQL = Not Only SQL,意即“不僅僅是SQL,提倡運(yùn)用非關(guān)系型的數(shù)據(jù)存儲
普遍選擇犧牲掉復(fù)雜 SQL 的支持及 ACID 事務(wù)換取彈性擴(kuò)展能力
通常不保證強(qiáng)一致性的(支持最終一致)
主要分類
- 鍵值(Key-Value)數(shù)據(jù)庫:如 MemcacheDB,Redis
- 文檔存儲:如 MongoDB
- 列存儲:方便存儲結(jié)構(gòu)化和半結(jié)構(gòu)化數(shù)據(jù),并做數(shù)據(jù)壓縮,對某幾列的查詢有非常大的IO優(yōu)勢: 如 HBase,Cassandra
- 圖數(shù)據(jù)庫:存儲圖關(guān)系(注意:不是圖片)。如 Neo4J
NewSQL
針對OLTP的讀寫,提供與NOSQL相同的可擴(kuò)展性和性能,同時(shí)能支持滿足ACID特性的事務(wù),即保持NoSQL的高可擴(kuò)展和高性能,并且保持關(guān)系模型
為什么需要NewSQL
-
NoSQL 不能完全取代 RDBMS
-
單機(jī)RDBMS 無法滿足性能需求
-
使用“單機(jī)RDBMS + 中間件”方式,在中間件層很難解決分布式事務(wù)、高可用問題
NewSQL設(shè)計(jì)架構(gòu)
- 可以基于全新的數(shù)據(jù)庫平臺,也可以基于現(xiàn)有的SQL引擎優(yōu)化。
- 無共享存儲(MPP架構(gòu))是比較常見的架構(gòu)
- 基于多副本實(shí)現(xiàn)高可用和容災(zāi)
- 分布式查詢
- 數(shù)據(jù)Sharding機(jī)制
- 通過2PC,Paxos/Raft等協(xié)議實(shí)現(xiàn)數(shù)據(jù)一致
代表產(chǎn)品
- Google Spanner
- OceanBase
- TiDB
OLTP、OLAP
OLTP
-
強(qiáng)調(diào)支持短時(shí)間內(nèi)大量并發(fā)的事務(wù)操作(增刪改查)能力,每個(gè)操作涉及的數(shù)據(jù)量都很?。ū热鐜资綆装僮止?jié))
-
強(qiáng)調(diào)事務(wù)的強(qiáng)一致性(如銀行轉(zhuǎn)賬交易,差錯(cuò)零容忍)
舉例:“雙十一”期間,可能有幾十萬用戶在同一秒內(nèi)下訂單。后臺數(shù)據(jù)庫要能夠并發(fā)的、以近乎實(shí)時(shí)的速度處理這些訂單請求
OLAP
- 偏向于復(fù)雜的只讀查詢,讀取海量數(shù)據(jù)進(jìn)行分析計(jì)算,查詢時(shí)間往往很長
舉例:“雙十一”結(jié)束,淘寶的運(yùn)營人員對訂單進(jìn)行分析挖掘,找出一些市場規(guī)律等等。這種分析可能需要讀取所有的歷史訂單進(jìn)行計(jì)算,耗時(shí)幾十秒甚至幾十分鐘。
- OLAP代表產(chǎn)品:
- Greenplum
- TeraData
- 阿里AnalyticDB
TiDB誕生
? 著名的開源分布式緩存服務(wù) Codis 的作者,PingCAP聯(lián)合創(chuàng)始人& CTO ,資深 infrastructure工程師的黃東旭,擅長分布式存儲系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn),開源狂熱分子的技術(shù)大神級別人物。即使在互聯(lián)網(wǎng)如此繁榮的今天,在數(shù)據(jù)庫這片邊界模糊且不確定地帶,他還在努力尋找確定性的實(shí)踐方向。
? 2012 年底,他看到 Google 發(fā)布的兩篇論文,如同棱鏡般折射出他自己內(nèi)心微爍的光彩。這兩篇論文描述了 Google 內(nèi)部使用的一個(gè)海量關(guān)系型數(shù)據(jù)F1/Spanner,解決了關(guān)系型數(shù)據(jù)庫、彈性擴(kuò)展以及全球分布的問題,并在生產(chǎn)中大規(guī)模使用?!叭绻@個(gè)能實(shí)現(xiàn),對數(shù)據(jù)存儲領(lǐng)域來說將是顛覆性的”,黃東旭為完美方案的出現(xiàn)而興奮, PingCAP 的 TiDB 在此基礎(chǔ)上誕生了。
TiDB架構(gòu)特性
TiDB整體架構(gòu)
? TiDB集群主要包括三個(gè)核心組件:TiDB Server,PD Server 和TiKV Server。此外,還有用于解決用戶復(fù)雜 OLAP 需求的 TiSpark 組件和簡化云上部署管理的 TiDB Operator組件
? TiDB作為新一代的NewSQL數(shù)據(jù)庫,在數(shù)據(jù)庫領(lǐng)域已經(jīng)逐漸站穩(wěn)腳跟,結(jié)合了Etcd/MySQL/HDFS/HBase/Spark等技術(shù)的突出特點(diǎn),隨著TiDB的大面積推廣,會逐漸弱化OLTP/OLAP的界限,并簡化目前冗雜的ETL流程,引起新一輪的技術(shù)浪潮。一言以蔽之,TiDB,前景可待,未來可期。
TiDB架構(gòu)圖
TiDB Server
? TiDB Server 負(fù)責(zé)接收 SQL 請求,處理 SQL 相關(guān)的邏輯并通過 PD Server找到存儲計(jì)算所需數(shù)據(jù)的TiKV 地址,與 TiKV 交互獲取數(shù)據(jù),最終返回結(jié)果。TiDB Server 是無狀態(tài)的,其本身并不存儲數(shù)據(jù),只負(fù)責(zé)計(jì)算,可以無限水平擴(kuò)展,可以通過負(fù)載均衡組件(如LVS、HAProxy 或 F5)對外提供統(tǒng)一的接入地址
PD Server
Placement Driver (簡稱 PD) 是整個(gè)集群的管理模塊,其主要工作有三個(gè):
-
一是存儲集群的元信息(某個(gè) Key 存儲在哪個(gè) TiKV 節(jié)點(diǎn));
-
二是對 TiKV 集群進(jìn)行調(diào)度和負(fù)載均衡(如數(shù)據(jù)的遷移、Raft group leader 的遷移等);
-
三是分配全局唯一且遞增的事務(wù) ID。
PD 通過 Raft 協(xié)議保證數(shù)據(jù)的安全性。Raft 的leader server 負(fù)責(zé)處理所有操作,其余的 PD server 僅用于保證高可用。建議部署奇數(shù)個(gè) PD 節(jié)點(diǎn)
TiKV Server
? TiKV Server 負(fù)責(zé)存儲數(shù)據(jù),從外部看 TiKV 是一個(gè)分布式的提供事務(wù)的 Key-Value 存儲引擎。存儲數(shù)據(jù)的基本單位是Region,每個(gè) Region 負(fù)責(zé)存儲一個(gè) Key Range(從 StartKey 到 EndKey 的左閉右開區(qū)間)的數(shù)據(jù),每個(gè)TiKV 節(jié)點(diǎn)會負(fù)責(zé)多個(gè) Region。TiKV 使用 Raft 協(xié)議做復(fù)制,保持?jǐn)?shù)據(jù)的一致性和容災(zāi)。副本以 Region 為單位進(jìn)行管理,不同節(jié)點(diǎn)上的多個(gè) Region 構(gòu)成一個(gè) Raft Group,互為副本。數(shù)據(jù)在多個(gè) TiKV 之間的負(fù)載均衡由 PD 調(diào)度,這里也是以 Region 為單位進(jìn)行調(diào)度
TiSpark
? TiSpark 作為TiDB中解決用戶復(fù)雜 OLAP 需求的主要組件,將 Spark SQL 直接運(yùn)行在 TiDB 存儲層上,同時(shí)融合 TiKV 分布式集群的優(yōu)勢,并融入大數(shù)據(jù)社區(qū)生態(tài)。至此,TiDB可以通過一套系統(tǒng),同時(shí)支持OLTP與OLAP,免除用戶數(shù)據(jù)同步的煩惱
TiDB Operator
? TiDB Operator 提供在主流云基礎(chǔ)設(shè)施(Kubernetes)上部署管理 TiDB 集群的能力。它結(jié)合云原生社區(qū)的容器編排最佳實(shí)踐與 TiDB 的專業(yè)運(yùn)維知識,集成一鍵部署、多集群混部、自動(dòng)運(yùn)維、故障自愈等能力,極大地降低了用戶使用和管理 TiDB 的門檻與成本
TiDB核心特性
TiDB 具備如下眾多特性,其中兩大核心特性為:水平擴(kuò)展與高可用
高度兼容 MySQL
? 大多數(shù)情況下,無需修改代碼即可從 MySQL 輕松遷移至 TiDB,分庫分表后的 MySQL 集群亦可通過 TiDB 工具進(jìn)行實(shí)時(shí)遷移。對于用戶使用的時(shí)候,可以透明地從MySQL切換到TiDB 中,只是“新MySQL”的后端是存儲“無限的”,不再受制于Local的磁盤容量。在運(yùn)維使用時(shí)也可以將TiDB當(dāng)做一個(gè)從庫掛到MySQL主從架構(gòu)中。
分布式事務(wù)
TiDB 100% 支持標(biāo)準(zhǔn)的 ACID 事務(wù)。
一站式 HTAP 解決方案
HTAP: Hybrid Transactional/Analytical Processing
TiDB 作為典型的 OLTP 行存數(shù)據(jù)庫,同時(shí)兼具強(qiáng)大的 OLAP 性能,配合 TiSpark,可提供一站式 HTAP 解決方案,一份存儲同時(shí)處理 OLTP & OLAP,無需傳統(tǒng)繁瑣的 ETL 過程。
云原生 SQL 數(shù)據(jù)庫
TiDB 是為云而設(shè)計(jì)的數(shù)據(jù)庫,支持公有云、私有云和混合云,配合 TiDB Operator 項(xiàng)目 可實(shí)現(xiàn)自動(dòng)化運(yùn)維,使部署、配置和維護(hù)變得十分簡單。
水平彈性擴(kuò)展
通過簡單地增加新節(jié)點(diǎn)即可實(shí)現(xiàn) TiDB 的水平擴(kuò)展,按需擴(kuò)展吞吐或存儲,輕松應(yīng)對高并發(fā)、海量數(shù)據(jù)場景。
真正金融級高可用
相比于傳統(tǒng)主從 (M-S) 復(fù)制方案,基于 Raft 的多數(shù)派選舉協(xié)議可以提供金融級的 100% 數(shù)據(jù)強(qiáng)一致性保證,且在不丟失大多數(shù)副本的前提下,可以實(shí)現(xiàn)故障的自動(dòng)恢復(fù) (auto-failover),無需人工介入
∩核心特性-水平擴(kuò)展∩
無限水平擴(kuò)展是 TiDB 的一大特點(diǎn),這里說的水平擴(kuò)展包括兩方面:計(jì)算能力(TiDB)和存儲能力(TiKV)。
-
TiDB Server 負(fù)責(zé)處理 SQL 請求,隨著業(yè)務(wù)的增長,可以簡單的添加 TiDB Server 節(jié)點(diǎn),提高整體的處理能力,提供更高的吞吐
-
TiKV 負(fù)責(zé)存儲數(shù)據(jù),隨著數(shù)據(jù)量的增長,可以部署更多的 TiKV Server 節(jié)點(diǎn)解決數(shù)據(jù) Scale 的問題。
-
PD 會在 TiKV 節(jié)點(diǎn)之間以 Region 為單位做調(diào)度,將部分?jǐn)?shù)據(jù)遷移到新加的節(jié)點(diǎn)上。
所以在業(yè)務(wù)的早期,可以只部署少量的服務(wù)實(shí)例(推薦至少部署 3 個(gè) TiKV, 3 個(gè) PD,2 個(gè) TiDB),隨著業(yè)務(wù)量的增長,按照需求添加 TiKV 或者 TiDB 實(shí)例。
∩核心特性-高可用∩
高可用是 TiDB 的另一大特點(diǎn),TiDB/TiKV/PD 這三個(gè)組件都能容忍部分實(shí)例失效,不影響整個(gè)集群的可用性。下面分別說明這三個(gè)組件的可用性、單個(gè)實(shí)例失效后的后果以及如何恢復(fù)。
TiDB
TiDB 是無狀態(tài)的,推薦至少部署兩個(gè)實(shí)例,前端通過負(fù)載均衡組件對外提供服務(wù)。當(dāng)單個(gè)實(shí)例失效時(shí),會影響正在這個(gè)實(shí)例上進(jìn)行的 Session,從應(yīng)用的角度看,會出現(xiàn)單次請求失敗的情況,重新連接后即可繼續(xù)獲得服務(wù)。單個(gè)實(shí)例失效后,可以重啟這個(gè)實(shí)例或者部署一個(gè)新的實(shí)例。
PD
PD 是一個(gè)集群,通過 Raft 協(xié)議保持?jǐn)?shù)據(jù)的一致性,單個(gè)實(shí)例失效時(shí),如果這個(gè)實(shí)例不是 Raft 的 leader,那么服務(wù)完全不受影響;如果這個(gè)實(shí)例是 Raft 的 leader,會重新選出新的 Raft leader,自動(dòng)恢復(fù)服務(wù)。PD 在選舉的過程中無法對外提供服務(wù),這個(gè)時(shí)間大約是3秒鐘。推薦至少部署三個(gè) PD 實(shí)例,單個(gè)實(shí)例失效后,重啟這個(gè)實(shí)例或者添加新的實(shí)例。
TiKV
TiKV 是一個(gè)集群,通過 Raft 協(xié)議保持?jǐn)?shù)據(jù)的一致性(副本數(shù)量可配置,默認(rèn)保存三副本),并通過 PD 做負(fù)載均衡調(diào)度。單個(gè)節(jié)點(diǎn)失效時(shí),會影響這個(gè)節(jié)點(diǎn)上存儲的所有 Region。對于 Region 中的 Leader 節(jié)點(diǎn),會中斷服務(wù),等待重新選舉;對于 Region 中的 Follower 節(jié)點(diǎn),不會影響服務(wù)。當(dāng)某個(gè) TiKV 節(jié)點(diǎn)失效,并且在一段時(shí)間內(nèi)(默認(rèn) 30 分鐘)無法恢復(fù),PD 會將其上的數(shù)據(jù)遷移到其他的 TiKV 節(jié)點(diǎn)上。
TiDB存儲能力和計(jì)算能力
存儲能力-TiKV-LSM
? TiKV Server通常是3+的,TiDB每份數(shù)據(jù)缺省為3副本,這一點(diǎn)與HDFS有些相似,但是通過Raft協(xié)議進(jìn)行數(shù)據(jù)復(fù)制,TiKV Server上的數(shù)據(jù)的是以Region為單位進(jìn)行,由PD Server集群進(jìn)行統(tǒng)一調(diào)度,類似HBASE的Region調(diào)度。
? TiKV集群存儲的數(shù)據(jù)格式是KV的,在TiDB中,并不是將數(shù)據(jù)直接存儲在 HDD/SSD中,而是通過RocksDB實(shí)現(xiàn)了TB級別的本地化存儲方案,著重提的一點(diǎn)是:RocksDB和HBASE一樣,都是通過 LSM樹作為存儲方案,避免了B+樹葉子節(jié)點(diǎn)膨脹帶來的大量隨機(jī)讀寫。從而提升了整體的吞吐量
計(jì)算能力-TiDB Server
? TiDB Server本身是無狀態(tài)的,意味著當(dāng)計(jì)算能力成為瓶頸的時(shí)候,可以直接擴(kuò)容機(jī)器,對用戶是透明的。理論上TiDB Server的數(shù)量并沒有上限限制
TiDB實(shí)驗(yàn)環(huán)境安裝部署
[注意]:這里先部署一個(gè)實(shí)驗(yàn)環(huán)境用于快速了解熟悉,生產(chǎn)級別的安裝部署放后面介紹
單機(jī)上模擬部署生產(chǎn)環(huán)境集群
申請一臺阿里云ECS云主機(jī)實(shí)例作為部署環(huán)境,搶占式資源,全部選便宜的選項(xiàng)即可,使用一天僅幾塊錢
可以預(yù)設(shè)好root密碼,之后使用公網(wǎng)ip使用xshell連接,如果本地有服務(wù)器,使用自己現(xiàn)有服務(wù)器即可
機(jī)器環(huán)境信息:
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# free -g
total used free shared buff/cache available
Mem: 30 0 29 0 0 30
Swap: 0 0 0
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
安裝MySQL-mariadb-server
安裝MySQL-mariadb-server用于連接TiDB數(shù)據(jù)庫,和后續(xù)測試使用
# yum安裝
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# yum install mariadb* -y
# 啟動(dòng)服務(wù)和開機(jī)自啟
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# systemctl start mariadb && systemctl enable mariadb
Created symlink from /etc/systemd/system/multi-user.target.wants/mariadb.service to /usr/lib/systemd/system/mariadb.service.
[root@iZ0jlfl8zktqzyt15o1o16Z ~]#
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# netstat -tnlpu|grep 3306
tcp6 0 0 :::3306 :::* LISTEN 11576/mysqld
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# mysql -uroot -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 2
Server version: 5.5.68-MariaDB MariaDB Server
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> ALTER USER 'root'@'localhost' IDENTIFIED BY 'Root@123';
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'USER 'root'@'localhost' IDENTIFIED BY 'Root@123'' at line 1
MariaDB [(none)]> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MariaDB [mysql]> UPDATE user SET password=password('123456') WHERE user='root';
Query OK, 4 rows affected (0.00 sec)
Rows matched: 4 Changed: 4 Warnings: 0
MariaDB [mysql]> flush privileges;
Query OK, 0 rows affected (0.00 sec)
MariaDB [mysql]> exit;
Bye
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# mysql -uroot -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 5.5.68-MariaDB MariaDB Server
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> exit
Bye
[root@iZ0jlfl8zktqzyt15o1o16Z ~]#
安裝單機(jī)版
在單機(jī)上模擬部署生產(chǎn)環(huán)境集群
適用場景:希望用單臺 Linux 服務(wù)器,體驗(yàn) TiDB 最小的完整拓?fù)涞募?,并模擬生產(chǎn)環(huán)境下的部署步驟
# 在單機(jī)上模擬部署生產(chǎn)環(huán)境集群
# 下載并安裝 TiUP
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh
WARN: adding root certificate via internet: https://tiup-mirrors.pingcap.com/root.json
You can revoke this by remove /root/.tiup/bin/7b8e153f2e2d0928.root.json
Successfully set mirror to https://tiup-mirrors.pingcap.com
Detected shell: bash
Shell profile: /root/.bash_profile
/root/.bash_profile has been modified to add tiup to PATH
open a new terminal or source /root/.bash_profile to use it
Installed path: /root/.tiup/bin/tiup
===============================================
Have a try: tiup playground
===============================================
# 根據(jù)提示source引入環(huán)境變量
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# source /root/.bash_profile
# 安裝 TiUP 的 cluster 組件
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# tiup cluster
tiup is checking updates for component cluster ...
A new version of cluster is available:
The latest version: v1.10.3
Local installed version:
Update current component: tiup update cluster
Update all components: tiup update --all
The component `cluster` version is not installed; downloading from repository.
download https://tiup-mirrors.pingcap.com/cluster-v1.10.3-linux-amd64.tar.gz 8.28 MiB / 8.28 MiB 100.00% 12.07 MiB/s
Starting component `cluster`: /root/.tiup/components/cluster/v1.10.3/tiup-cluster
Deploy a TiDB cluster for production
# 根據(jù)提示信息需要update
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# tiup update --self && tiup update cluster
download https://tiup-mirrors.pingcap.com/tiup-v1.10.3-linux-amd64.tar.gz 6.81 MiB / 6.81 MiB 100.00% 12.83 MiB/s
Updated successfully!
component cluster version v1.10.3 is already installed
Updated successfully!
# 由于模擬多機(jī)部署,需要通過 root 用戶調(diào)大 sshd 服務(wù)的連接數(shù)限制
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# cat /etc/ssh/sshd_config | grep MaxSessions
#MaxSessions 10
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# echo "MaxSessions 20" >> /etc/ssh/sshd_config
# 或者vim打開文件打開注釋,把10改為20保存
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# cat /etc/ssh/sshd_config | grep MaxSessions|grep -vE "^#"
MaxSessions 20
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# service sshd restart
Redirecting to /bin/systemctl restart sshd.service
# 創(chuàng)建啟動(dòng)機(jī)器定義文件
#按下面的配置模板,編輯配置文件,命名為 topo.yaml,其中:
#user: "tidb":表示通過 tidb 系統(tǒng)用戶(部署會自動(dòng)創(chuàng)建)來做集群的內(nèi)部管理,默認(rèn)使用 22 端口通過 ssh 登錄目標(biāo)機(jī)器
#replication.enable-placement-rules:設(shè)置這個(gè) PD 參數(shù)來確保 TiFlash 正常運(yùn)行
#host:設(shè)置為本部署主機(jī)的 IP
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# vi topo.yaml
# # Global variables are applied to all deployments and used as the default value of
# # the deployments if a specific deployment value is missing.
global:
user: "tidb"
ssh_port: 22
deploy_dir: "/tidb-deploy"
data_dir: "/tidb-data"
# # Monitored variables are applied to all the machines.
monitored:
node_exporter_port: 9100
blackbox_exporter_port: 9115
server_configs:
tidb:
log.slow-threshold: 300
tikv:
readpool.storage.use-unified-pool: false
readpool.coprocessor.use-unified-pool: true
pd:
replication.enable-placement-rules: true
replication.location-labels: ["host"]
tiflash:
logger.level: "info"
pd_servers:
- host: 172.28.54.199
tidb_servers:
- host: 172.28.54.199
tikv_servers:
- host: 172.28.54.199
port: 20160
status_port: 20180
config:
server.labels: { host: "logic-host-1" }
- host: 172.28.54.199
port: 20161
status_port: 20181
config:
server.labels: { host: "logic-host-2" }
- host: 172.28.54.199
port: 20162
status_port: 20182
config:
server.labels: { host: "logic-host-3" }
tiflash_servers:
- host: 172.28.54.199
monitoring_servers:
- host: 172.28.54.199
grafana_servers:
- host: 172.28.54.199
# 查看可以部署的版本信息
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# tiup list tidb
# tiup cluster deploy <cluster-name> <tidb-version> ./topo.yaml --user root -p
#參數(shù) <cluster-name> 表示設(shè)置集群名稱
#參數(shù) <tidb-version> 表示設(shè)置集群版本,可以通過 tiup list tidb 命令來查看當(dāng)前支持部署的 TiDB 版本
#參數(shù) -p 表示在連接目標(biāo)機(jī)器時(shí)使用密碼登錄
# 按照引導(dǎo),輸入”y”及 root 密碼,來完成部署:
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# tiup cluster deploy wangtingtidb v5.2.4 ./topo.yaml --user root -p
tiup is checking updates for component cluster ...
Starting component `cluster`: /root/.tiup/components/cluster/v1.10.3/tiup-cluster deploy wangtingtidb v5.2.4 ./topo.yaml --user root -p
Input SSH password:
+ Detect CPU Arch Name
- Detecting node 172.28.54.199 Arch info ... Done
+ Detect CPU OS Name
- Detecting node 172.28.54.199 OS info ... Done
Please confirm your topology:
Cluster type: tidb
Cluster name: wangtingtidb
Cluster version: v5.2.4
Role Host Ports OS/Arch Directories
---- ---- ----- ------- -----------
pd 172.28.54.199 2379/2380 linux/x86_64 /tidb-deploy/pd-2379,/tidb-data/pd-2379
tikv 172.28.54.199 20160/20180 linux/x86_64 /tidb-deploy/tikv-20160,/tidb-data/tikv-20160
tikv 172.28.54.199 20161/20181 linux/x86_64 /tidb-deploy/tikv-20161,/tidb-data/tikv-20161
tikv 172.28.54.199 20162/20182 linux/x86_64 /tidb-deploy/tikv-20162,/tidb-data/tikv-20162
tidb 172.28.54.199 4000/10080 linux/x86_64 /tidb-deploy/tidb-4000
tiflash 172.28.54.199 9000/8123/3930/20170/20292/8234 linux/x86_64 /tidb-deploy/tiflash-9000,/tidb-data/tiflash-9000
prometheus 172.28.54.199 9090 linux/x86_64 /tidb-deploy/prometheus-9090,/tidb-data/prometheus-9090
grafana 172.28.54.199 3000 linux/x86_64 /tidb-deploy/grafana-3000
Attention:
1. If the topology is not what you expected, check your yaml file.
2. Please confirm there is no port/directory conflicts in same host.
Do you want to continue? [y/N]: (default=N) y
+ Generate SSH keys ... Done
+ Download TiDB components
- Download pd:v5.2.4 (linux/amd64) ... Done
- Download tikv:v5.2.4 (linux/amd64) ... Done
- Download tidb:v5.2.4 (linux/amd64) ... Done
- Download tiflash:v5.2.4 (linux/amd64) ... Done
- Download prometheus:v5.2.4 (linux/amd64) ... Done
- Download grafana:v5.2.4 (linux/amd64) ... Done
- Download node_exporter: (linux/amd64) ... Done
- Download blackbox_exporter: (linux/amd64) ... Done
+ Initialize target host environments
- Prepare 172.28.54.199:22 ... Done
+ Deploy TiDB instance
- Copy pd -> 172.28.54.199 ... Done
- Copy tikv -> 172.28.54.199 ... Done
- Copy tikv -> 172.28.54.199 ... Done
- Copy tikv -> 172.28.54.199 ... Done
- Copy tidb -> 172.28.54.199 ... Done
- Copy tiflash -> 172.28.54.199 ... Done
- Copy prometheus -> 172.28.54.199 ... Done
- Copy grafana -> 172.28.54.199 ... Done
- Deploy node_exporter -> 172.28.54.199 ... Done
- Deploy blackbox_exporter -> 172.28.54.199 ... Done
+ Copy certificate to remote host
+ Init instance configs
- Generate config pd -> 172.28.54.199:2379 ... Done
- Generate config tikv -> 172.28.54.199:20160 ... Done
- Generate config tikv -> 172.28.54.199:20161 ... Done
- Generate config tikv -> 172.28.54.199:20162 ... Done
- Generate config tidb -> 172.28.54.199:4000 ... Done
- Generate config tiflash -> 172.28.54.199:9000 ... Done
- Generate config prometheus -> 172.28.54.199:9090 ... Done
- Generate config grafana -> 172.28.54.199:3000 ... Done
+ Init monitor configs
- Generate config node_exporter -> 172.28.54.199 ... Done
- Generate config blackbox_exporter -> 172.28.54.199 ... Done
+ Check status
Enabling component pd
Enabling instance 172.28.54.199:2379
Enable instance 172.28.54.199:2379 success
Enabling component tikv
Enabling instance 172.28.54.199:20162
Enabling instance 172.28.54.199:20160
Enabling instance 172.28.54.199:20161
Enable instance 172.28.54.199:20160 success
Enable instance 172.28.54.199:20162 success
Enable instance 172.28.54.199:20161 success
Enabling component tidb
Enabling instance 172.28.54.199:4000
Enable instance 172.28.54.199:4000 success
Enabling component tiflash
Enabling instance 172.28.54.199:9000
Enable instance 172.28.54.199:9000 success
Enabling component prometheus
Enabling instance 172.28.54.199:9090
Enable instance 172.28.54.199:9090 success
Enabling component grafana
Enabling instance 172.28.54.199:3000
Enable instance 172.28.54.199:3000 success
Enabling component node_exporter
Enabling instance 172.28.54.199
Enable 172.28.54.199 success
Enabling component blackbox_exporter
Enabling instance 172.28.54.199
Enable 172.28.54.199 success
Cluster `wangtingtidb` deployed successfully, you can start it with command: `tiup cluster start wangtingtidb --init`
# 根據(jù)提示命令tiup cluster start wangtingtidb --init啟動(dòng)集群
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# tiup cluster start wangtingtidb --init
tiup is checking updates for component cluster ...
Starting component `cluster`: /root/.tiup/components/cluster/v1.10.3/tiup-cluster start wangtingtidb --init
Starting cluster wangtingtidb...
+ [ Serial ] - SSHKeySet: privateKey=/root/.tiup/storage/cluster/clusters/wangtingtidb/ssh/id_rsa, publicKey=/root/.tiup/storage/cluster/clusters/wangtingtidb/ssh/id_rsa.pub
+ [Parallel] - UserSSH: user=tidb, host=172.28.54.199
+ [Parallel] - UserSSH: user=tidb, host=172.28.54.199
+ [Parallel] - UserSSH: user=tidb, host=172.28.54.199
+ [Parallel] - UserSSH: user=tidb, host=172.28.54.199
+ [Parallel] - UserSSH: user=tidb, host=172.28.54.199
+ [Parallel] - UserSSH: user=tidb, host=172.28.54.199
+ [Parallel] - UserSSH: user=tidb, host=172.28.54.199
+ [Parallel] - UserSSH: user=tidb, host=172.28.54.199
+ [ Serial ] - StartCluster
Starting component pd
Starting instance 172.28.54.199:2379
Start instance 172.28.54.199:2379 success
Starting component tikv
Starting instance 172.28.54.199:20162
Starting instance 172.28.54.199:20160
Starting instance 172.28.54.199:20161
Start instance 172.28.54.199:20160 success
Start instance 172.28.54.199:20161 success
Start instance 172.28.54.199:20162 success
Starting component tidb
Starting instance 172.28.54.199:4000
Start instance 172.28.54.199:4000 success
Starting component tiflash
Starting instance 172.28.54.199:9000
Start instance 172.28.54.199:9000 success
Starting component prometheus
Starting instance 172.28.54.199:9090
Start instance 172.28.54.199:9090 success
Starting component grafana
Starting instance 172.28.54.199:3000
Start instance 172.28.54.199:3000 success
Starting component node_exporter
Starting instance 172.28.54.199
Start 172.28.54.199 success
Starting component blackbox_exporter
Starting instance 172.28.54.199
Start 172.28.54.199 success
+ [ Serial ] - UpdateTopology: cluster=wangtingtidb
Started cluster `wangtingtidb` successfully
The root password of TiDB database has been changed.
The new password is: 'rpi381$9*!cvX07D-w'.
Copy and record it to somewhere safe, it is only displayed once, and will not be stored.
The generated password can NOT be get and shown again.
# 注意TiDB的初始密碼會控制臺輸出,拷貝留存一份
[root@iZ0jlfl8zktqzyt15o1o16Z ~]#
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# mysql -h 172.28.54.199 -P 4000 -u root -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.7.25-TiDB-v5.2.4 TiDB Server (Apache License 2.0) Community Edition, MySQL 5.7 compatible
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MySQL [mysql]> Set password for 'root'@'%'=password('123456');
Query OK, 0 rows affected (0.02 sec)
MySQL [mysql]> flush privileges;
Query OK, 0 rows affected (0.01 sec)
MySQL [mysql]> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
Query OK, 0 rows affected (0.02 sec)
MySQL [mysql]> exit;
Bye
驗(yàn)證集群
# 執(zhí)行以下命令確認(rèn)當(dāng)前已經(jīng)部署的集群列表:
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# tiup cluster list
tiup is checking updates for component cluster ...
Starting component `cluster`: /root/.tiup/components/cluster/v1.10.3/tiup-cluster list
Name User Version Path PrivateKey
---- ---- ------- ---- ----------
wangtingtidb tidb v5.2.4 /root/.tiup/storage/cluster/clusters/wangtingtidb /root/.tiup/storage/cluster/clusters/wangtingtidb/ssh/id_rsa
# 執(zhí)行以下命令查看集群的拓?fù)浣Y(jié)構(gòu)和狀態(tài):
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# tiup cluster display wangtingtidb
tiup is checking updates for component cluster ...
Starting component `cluster`: /root/.tiup/components/cluster/v1.10.3/tiup-cluster display wangtingtidb
Cluster type: tidb
Cluster name: wangtingtidb
Cluster version: v5.2.4
Deploy user: tidb
SSH type: builtin
Dashboard URL: http://172.28.54.199:2379/dashboard
Grafana URL: http://172.28.54.199:3000
ID Role Host Ports OS/Arch Status Data Dir Deploy Dir
-- ---- ---- ----- ------- ------ -------- ----------
172.28.54.199:3000 grafana 172.28.54.199 3000 linux/x86_64 Up - /tidb-deploy/grafana-3000
172.28.54.199:2379 pd 172.28.54.199 2379/2380 linux/x86_64 Up|L|UI /tidb-data/pd-2379 /tidb-deploy/pd-2379
172.28.54.199:9090 prometheus 172.28.54.199 9090 linux/x86_64 Up /tidb-data/prometheus-9090 /tidb-deploy/prometheus-9090
172.28.54.199:4000 tidb 172.28.54.199 4000/10080 linux/x86_64 Up - /tidb-deploy/tidb-4000
172.28.54.199:9000 tiflash 172.28.54.199 9000/8123/3930/20170/20292/8234 linux/x86_64 Up /tidb-data/tiflash-9000 /tidb-deploy/tiflash-9000
172.28.54.199:20160 tikv 172.28.54.199 20160/20180 linux/x86_64 Up /tidb-data/tikv-20160 /tidb-deploy/tikv-20160
172.28.54.199:20161 tikv 172.28.54.199 20161/20181 linux/x86_64 Up /tidb-data/tikv-20161 /tidb-deploy/tikv-20161
172.28.54.199:20162 tikv 172.28.54.199 20162/20182 linux/x86_64 Up /tidb-data/tikv-20162 /tidb-deploy/tikv-20162
Total nodes: 8
訪問 TiDB 的 Grafana 監(jiān)控:
通過 http://39.101.65.150:3000 訪問集群 Grafana 監(jiān)控頁面,默認(rèn)用戶名和密碼均為 admin。
第一次訪問需要修改初始密碼,改為123456
訪問 TiDB 的 Dashboard:
通過 http://39.101.65.150:2379/dashboard 訪問集群 TiDB Dashboard 監(jiān)控頁面,默認(rèn)用戶名為 root,密碼為剛才MySQL中新設(shè)置的123456
# 命令行在mariadb和TiDB分別建庫
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# mysql -u root -P 3306 -h 39.101.65.150 -p"123456" -e "create database mariadb_666;"
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# mysql -u root -P 4000 -h 39.101.65.150 -p"123456" -e "create database tidb_666;"
可以看到在兩個(gè)數(shù)據(jù)庫中均已經(jīng)看到
TiDB使用介紹
SQL操作
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# mysql -u root -P 4000 -h 39.101.65.150 -p"123456"
Welcome to the MariaDB monitor. Commands end with ; or \g.
# 創(chuàng)建一個(gè)名為 samp_db 的數(shù)據(jù)庫
MySQL [(none)]> CREATE DATABASE IF NOT EXISTS samp_db;
Query OK, 0 rows affected (0.08 sec)
# 查看數(shù)據(jù)庫
MySQL [(none)]> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| INFORMATION_SCHEMA |
| METRICS_SCHEMA |
| PERFORMANCE_SCHEMA |
| mysql |
| samp_db |
| test |
| tidb_666 |
+--------------------+
7 rows in set (0.01 sec)
# 刪除數(shù)據(jù)庫
MySQL [(none)]> DROP DATABASE samp_db;
Query OK, 0 rows affected (0.19 sec)
MySQL [(none)]> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| INFORMATION_SCHEMA |
| METRICS_SCHEMA |
| PERFORMANCE_SCHEMA |
| mysql |
| test |
| tidb_666 |
+--------------------+
6 rows in set (0.01 sec)
MySQL [(none)]> CREATE DATABASE IF NOT EXISTS samp_db;
Query OK, 0 rows affected (0.08 sec)
# 切換數(shù)據(jù)庫
MySQL [(none)]> USE samp_db;
Database changed
# 創(chuàng)建表
MySQL [samp_db]> CREATE TABLE IF NOT EXISTS person (
-> number INT(11),
-> name VARCHAR(255),
-> birthday DATE
-> );
Query OK, 0 rows affected (0.08 sec)
# 查看建表語句
MySQL [samp_db]> SHOW CREATE table person;
+--------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+--------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| person | CREATE TABLE `person` (
`number` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`birthday` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+--------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
# 查看表的列
MySQL [samp_db]> SHOW FULL COLUMNS FROM person;
+----------+--------------+-------------+------+------+---------+-------+---------------------------------+---------+
| Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment |
+----------+--------------+-------------+------+------+---------+-------+---------------------------------+---------+
| number | int(11) | NULL | YES | | NULL | | select,insert,update,references | |
| name | varchar(255) | utf8mb4_bin | YES | | NULL | | select,insert,update,references | |
| birthday | date | NULL | YES | | NULL | | select,insert,update,references | |
+----------+--------------+-------------+------+------+---------+-------+---------------------------------+---------+
3 rows in set (0.00 sec)
MySQL [samp_db]> DROP TABLE IF EXISTS person;
Query OK, 0 rows affected (0.20 sec)
MySQL [samp_db]> CREATE TABLE IF NOT EXISTS person (
-> number INT(11),
-> name VARCHAR(255),
-> birthday DATE
-> );
Query OK, 0 rows affected (0.08 sec)
# 創(chuàng)建索引,對于值不唯一的列,可使用 CREATE INDEX 或 ALTER TABLE 語句
MySQL [samp_db]> CREATE INDEX person_num ON person (number);
Query OK, 0 rows affected (2.76 sec)
# 查看表內(nèi)所有索引
MySQL [samp_db]> SHOW INDEX from person;
+--------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+-----------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | Clustered |
+--------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+-----------+
| person | 1 | person_num | 1 | number | A | 0 | NULL | NULL | YES | BTREE | | | YES | NULL | NO |
+--------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+-----------+
1 row in set (0.01 sec)
# 刪除索引
MySQL [samp_db]> DROP INDEX person_num ON person;
Query OK, 0 rows affected (0.26 sec)
# 創(chuàng)建唯一索引
MySQL [samp_db]> CREATE UNIQUE INDEX person_num ON person (number);
Query OK, 0 rows affected (2.76 sec)
# 插入數(shù)據(jù)
MySQL [samp_db]> INSERT INTO person VALUES("1","tom","20170912");
Query OK, 1 row affected (0.01 sec)
# 查數(shù)據(jù)
MySQL [samp_db]> SELECT * FROM person;
+--------+------+------------+
| number | name | birthday |
+--------+------+------------+
| 1 | tom | 2017-09-12 |
+--------+------+------------+
1 row in set (0.01 sec)
# 修改表內(nèi)數(shù)據(jù)
MySQL [samp_db]> UPDATE person SET birthday='20200202' WHERE name='tom';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
MySQL [samp_db]> SELECT * FROM person;
+--------+------+------------+
| number | name | birthday |
+--------+------+------------+
| 1 | tom | 2020-02-02 |
+--------+------+------------+
1 row in set (0.00 sec)
# 刪除表內(nèi)數(shù)據(jù)
MySQL [samp_db]> DELETE FROM person WHERE number=1;
Query OK, 1 row affected (0.01 sec)
MySQL [samp_db]> SELECT * FROM person;
Empty set (0.01 sec)
# 創(chuàng)建一個(gè)用戶 tiuser,登錄為本地localhost訪問
MySQL [samp_db]> CREATE USER 'tiuser'@'localhost' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.02 sec)
# 本地驗(yàn)證
[root@iZ0jlfl8zktqzyt15o1o16Z ~]# mysql -u tiuser -P 4000 -h 127.0.0.1 -p"123456"
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 99
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| INFORMATION_SCHEMA |
+--------------------+
1 row in set (0.00 sec)
MySQL [(none)]>
遠(yuǎn)程不能訪問
授權(quán)普通用戶遠(yuǎn)程訪問:
MySQL [mysql]> update user set host='%' where user='tiuser';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
MySQL [mysql]> flush privileges;
Query OK, 0 rows affected (0.00 sec)
授權(quán)普通用戶訪問庫權(quán)限:
MySQL [mysql]> GRANT SELECT ON samp_db.* TO 'tiuser'@'%';
Query OK, 0 rows affected (0.01 sec)
MySQL [mysql]> SHOW GRANTS for 'tiuser'@'%';
+-------------------------------------------+
| Grants for tiuser@% |
+-------------------------------------------+
| GRANT USAGE ON *.* TO 'tiuser'@'%' |
| GRANT SELECT ON samp_db.* TO 'tiuser'@'%' |
+-------------------------------------------+
2 rows in set (0.00 sec)
刪除用戶
# 刪除用戶 tiuser
MySQL [samp_db]> DROP USER 'tiuser'@'localhost';
Query OK, 0 rows affected (0.03 sec)
# 查看所有權(quán)限
MySQL [samp_db]> SHOW GRANTS;
+-------------------------------------------------------------+
| Grants for User |
+-------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION |
+-------------------------------------------------------------+
1 row in set (0.00 sec)
MySQL [samp_db]>
讀取歷史數(shù)據(jù)
功能說明
? TiDB 實(shí)現(xiàn)了通過標(biāo)準(zhǔn) SQL 接口讀取歷史數(shù)據(jù)功能,無需特殊的 client 或者 driver。當(dāng)數(shù)據(jù)被更新、刪除后,依然可以通過 SQL 接口將更新/刪除前的數(shù)據(jù)讀取出來。
? 另外即使在更新數(shù)據(jù)之后,表結(jié)構(gòu)發(fā)生了變化,TiDB 依舊能用舊的表結(jié)構(gòu)將數(shù)據(jù)讀取出來
操作流程
? 為支持讀取歷史版本數(shù)據(jù), 引入了一個(gè)新的 system variable: tidb_snapshot ,這個(gè)變量是 Session 范圍有效,可以通過標(biāo)準(zhǔn)的 Set 語句修改其值。其值為文本,能夠存儲 TSO 和日期時(shí)間。TSO 即是全局授時(shí)的時(shí)間戳,是從 PD 端獲取的; 日期時(shí)間的格式可以為: “2020-10-08 16:45:26.999”,一般來說可以只寫到秒,比如”2020-10-08 16:45:26”。 當(dāng)這個(gè)變量被設(shè)置時(shí),TiDB 會用這個(gè)時(shí)間戳建立 Snapshot(沒有開銷,只是創(chuàng)建數(shù)據(jù)結(jié)構(gòu)),隨后所有的 Select 操作都會在這個(gè) Snapshot 上讀取數(shù)據(jù)。
注意:
? TiDB 的事務(wù)是通過 PD 進(jìn)行全局授時(shí),所以存儲的數(shù)據(jù)版本也是以 PD 所授時(shí)間戳作為版本號。在生成 Snapshot 時(shí),是以 tidb_snapshot 變量的值作為版本號,如果 TiDB Server 所在機(jī)器和 PD Server 所在機(jī)器的本地時(shí)間相差較大,需要以 PD 的時(shí)間為準(zhǔn)。
? 當(dāng)讀取歷史版本操作結(jié)束后,可以結(jié)束當(dāng)前 Session 或者是通過 Set 語句將 tidb_snapshot 變量的值設(shè)為 “",即可讀取最新版本的數(shù)據(jù)
歷史數(shù)據(jù)保留策略
? TiDB 使用 MVCC 管理版本,當(dāng)更新/刪除數(shù)據(jù)時(shí),不會做真正的數(shù)據(jù)刪除,只會添加一個(gè)新版本數(shù)據(jù),所以可以保留歷史數(shù)據(jù)。歷史數(shù)據(jù)不會全部保留,超過一定時(shí)間的歷史數(shù)據(jù)會被徹底刪除,以減小空間占用以及避免歷史版本過多引入的性能開銷。
TiDB 使用周期性運(yùn)行的 GC(Garbage Collection,垃圾回收)來進(jìn)行清理,關(guān)于 GC 的詳細(xì)介紹參見 TiDB 垃圾回收 (GC)。
? 這里需要重點(diǎn)關(guān)注的是 tikv_gc_life_time 和 tikv_gc_safe_point 這條。tikv_gc_life_time 用于配置歷史版本保留時(shí)間,可以手動(dòng)修改;tikv_gc_safe_point 記錄了當(dāng)前的 safePoint,用戶可以安全地使用大于 safePoint 的時(shí)間戳創(chuàng)建 snapshot 讀取歷史版本。safePoint 在每次 GC 開始運(yùn)行時(shí)自動(dòng)更新
讀取歷史數(shù)據(jù)操作示例
# 創(chuàng)建一個(gè)表,并插入幾行測試數(shù)據(jù)
MySQL [mysql]> create table t (c int);
Query OK, 0 rows affected (0.08 sec)
MySQL [mysql]> insert into t values (1), (2), (3);
Query OK, 3 rows affected (0.04 sec)
Records: 3 Duplicates: 0 Warnings: 0
# 查看表中的數(shù)據(jù)
MySQL [mysql]> select * from t;
+------+
| c |
+------+
| 1 |
| 2 |
| 3 |
+------+
3 rows in set (0.00 sec)
# 查看當(dāng)前時(shí)間,一般這個(gè)時(shí)間可以理解成數(shù)據(jù)的版本和時(shí)間掛鉤,在某個(gè)時(shí)間點(diǎn)前和后有變動(dòng),查詢時(shí)可以以時(shí)間為參照點(diǎn)
MySQL [mysql]> select now();
+---------------------+
| now() |
+---------------------+
| 2022-08-22 10:17:26 |
+---------------------+
1 row in set (0.01 sec)
# 更新某一行數(shù)據(jù)
MySQL [mysql]> update t set c=222222 where c=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
MySQL [mysql]> select * from t;
+--------+
| c |
+--------+
| 1 |
| 222222 |
| 3 |
+--------+
3 rows in set (0.00 sec)
# 設(shè)置一個(gè)特殊的環(huán)境變量,這個(gè)是一個(gè) session scope 的變量,其意義為讀取這個(gè)時(shí)間之前的最新的一個(gè)版本,也就時(shí)查詢數(shù)據(jù)時(shí)不再是實(shí)時(shí)數(shù)據(jù),而是截至到2022-08-22 10:17:26時(shí)間的數(shù)據(jù),這個(gè)時(shí)間之后的數(shù)據(jù)修改不體現(xiàn),從而實(shí)現(xiàn)查詢歷史數(shù)據(jù)
MySQL [mysql]> set @@tidb_snapshot="2022-08-22 10:17:26";
Query OK, 0 rows affected (0.00 sec)
#注意:
#這里的時(shí)間設(shè)置的是 update 語句之前的那個(gè)時(shí)間。
#在 tidb_snapshot 前須使用 @@ 而非 @,因?yàn)?@@ 表示系統(tǒng)變量,@ 表示用戶變量
MySQL [mysql]> select * from t;
+------+
| c |
+------+
| 1 |
| 2 |
| 3 |
+------+
3 rows in set (0.00 sec)
# 清空這個(gè)變量后,即可讀取最新版本數(shù)據(jù)
MySQL [mysql]> set @@tidb_snapshot="";
Query OK, 0 rows affected (0.00 sec)
MySQL [mysql]> select * from t;
+--------+
| c |
+--------+
| 1 |
| 222222 |
| 3 |
+--------+
3 rows in set (0.00 sec)
TiDB技術(shù)原理
? 數(shù)據(jù)庫、操作系統(tǒng)和編譯器并稱為三大系統(tǒng),可以說是整個(gè)計(jì)算機(jī)軟件的基石。其中數(shù)據(jù)庫更靠近應(yīng)用層,是很多業(yè)務(wù)的支撐。這一領(lǐng)域經(jīng)過了幾十年的發(fā)展,不斷的有新的進(jìn)展。
? 很多人用過數(shù)據(jù)庫,但是很少有人實(shí)現(xiàn)過一個(gè)數(shù)據(jù)庫,特別是實(shí)現(xiàn)一個(gè)分布式數(shù)據(jù)庫。了解數(shù)據(jù)庫的實(shí)現(xiàn)原理和細(xì)節(jié),一方面可以提高個(gè)人技術(shù),對構(gòu)建其他系統(tǒng)有幫助,另一方面也有利于用好數(shù)據(jù)庫。
? 研究一門技術(shù)最好的方法是研究其中一個(gè)開源項(xiàng)目,數(shù)據(jù)庫也不例外。單機(jī)數(shù)據(jù)庫領(lǐng)域有很多很好的開源項(xiàng)目,其中 MySQL 和 PostgreSQL 是其中知名度最高的兩個(gè),不少同學(xué)都看過這兩個(gè)項(xiàng)目的代碼。但是分布式數(shù)據(jù)庫方面,好的開源項(xiàng)目并不多。 TiDB 目前獲得了廣泛的關(guān)注,特別是一些技術(shù)愛好者,希望能夠參與這個(gè)項(xiàng)目。由于分布式數(shù)據(jù)庫自身的復(fù)雜性,很多人并不能很好的理解整個(gè)項(xiàng)目,所以我希望能寫一些文章,自頂向下,由淺入深,講述 TiDB 的一些技術(shù)原理,包括用戶可見的技術(shù)以及大量隱藏在 SQL 界面后用戶不可見的技術(shù)點(diǎn)
數(shù)據(jù)存儲
數(shù)據(jù)庫最根本的功能是能把數(shù)據(jù)存下來,所以我們從這里開始。
保存數(shù)據(jù)的方法很多,最簡單的方法是直接在內(nèi)存中建一個(gè)數(shù)據(jù)結(jié)構(gòu),保存用戶發(fā)來的數(shù)據(jù)。比如用一個(gè)數(shù)組,每當(dāng)收到一條數(shù)據(jù)就向數(shù)組中追加一條記錄。這個(gè)方案十分簡單,能滿足最基本,并且性能肯定會很好,但是除此之外卻是漏洞百出,其中最大的問題是數(shù)據(jù)完全在內(nèi)存中,一旦停機(jī)或者是服務(wù)重啟,數(shù)據(jù)就會永久丟失。
為了解決數(shù)據(jù)丟失問題,我們可以把數(shù)據(jù)放在非易失存儲介質(zhì)(比如硬盤)中。改進(jìn)的方案是在磁盤上創(chuàng)建一個(gè)文件,收到一條數(shù)據(jù),就在文件中 Append 一行。OK,我們現(xiàn)在有了一個(gè)能持久化存儲數(shù)據(jù)的方案。但是還不夠好,假設(shè)這塊磁盤出現(xiàn)了壞道呢?我們可以做 RAID (Redundant Array of Independent Disks),提供單機(jī)冗余存儲。如果整臺機(jī)器都掛了呢?比如出現(xiàn)了火災(zāi),RAID 也保不住這些數(shù)據(jù)。我們還可以將存儲改用網(wǎng)絡(luò)存儲,或者是通過硬件或者軟件進(jìn)行存儲復(fù)制。到這里似乎我們已經(jīng)解決了數(shù)據(jù)安全問題,可以松一口氣了。But,做復(fù)制過程中是否能保證副本之間的一致性?也就是在保證數(shù)據(jù)不丟的前提下,還要保證數(shù)據(jù)不錯(cuò)。保證數(shù)據(jù)不丟不錯(cuò)只是一項(xiàng)最基本的要求,還有更多令人頭疼的問題等待解決:
? 能否支持跨數(shù)據(jù)中心的容災(zāi)?
? 寫入速度是否夠快?
? 數(shù)據(jù)保存下來后,是否方便讀???
? 保存的數(shù)據(jù)如何修改?如何支持并發(fā)的修改?
? 如何原子地修改多條記錄?
這些問題每一項(xiàng)都非常難,但是要做一個(gè)優(yōu)秀的數(shù)據(jù)存儲系統(tǒng),必須要解決上述的每一個(gè)難題。 為了解決數(shù)據(jù)存儲問題,我們開發(fā)了 TiKV 這個(gè)項(xiàng)目。接下來我向大家介紹一下 TiKV 的一些設(shè)計(jì)思想和基本概念
Key-Value
? 作為保存數(shù)據(jù)的系統(tǒng),首先要決定的是數(shù)據(jù)的存儲模型,也就是數(shù)據(jù)以什么樣的形式保存下來。TiKV 的選擇是 Key-Value 模型,并且提供有序遍歷方法。簡單來講,可以將 TiKV 看做一個(gè)巨大的 Map,其中 Key 和 Value 都是原始的 Byte 數(shù)組,在這個(gè) Map 中,Key 按照 Byte 數(shù)組總的原始二進(jìn)制比特位比較順序排列。
-
這是一個(gè)巨大的 Map,也就是存儲的是 Key-Value pair
-
這個(gè) Map 中的 Key-Value pair 按照 Key 的二進(jìn)制順序有序,也就是我們可以 Seek 到某一個(gè) Key 的位置,然后不斷的調(diào)用 Next 方法以遞增的順序獲取比這個(gè) Key 大的 Key-Value
現(xiàn)在讓我們忘記 SQL 中的任何概念,專注于討論如何實(shí)現(xiàn) TiKV 這樣一個(gè)高性能高可靠性的巨大的(分布式的) Map
RocksDB
? 任何持久化的存儲引擎,數(shù)據(jù)終歸要保存在磁盤上,TiKV 也不例外。但是 TiKV 沒有選擇直接向磁盤上寫數(shù)據(jù),而是把數(shù)據(jù)保存在 RocksDB 中,具體的數(shù)據(jù)落地由 RocksDB 負(fù)責(zé)。這個(gè)選擇的原因是開發(fā)一個(gè)單機(jī)存儲引擎工作量很大,特別是要做一個(gè)高性能的單機(jī)引擎,需要做各種細(xì)致的優(yōu)化,而 RocksDB 是一個(gè)非常優(yōu)秀的開源的單機(jī)存儲引擎,可以滿足我們對單機(jī)引擎的各種要求,而且還有 Facebook 的團(tuán)隊(duì)在做持續(xù)的優(yōu)化,這樣我們只投入很少的精力,就能享受到一個(gè)十分強(qiáng)大且在不斷進(jìn)步的單機(jī)引擎。當(dāng)然,我們也為 RocksDB 貢獻(xiàn)了一些代碼,希望這個(gè)項(xiàng)目能越做越好。這里可以簡單的認(rèn)為 RocksDB 是一個(gè)單機(jī)的 Key-Value Map。
? 底層LSM樹將對數(shù)據(jù)的修改增量保存在內(nèi)存中,達(dá)到指定大小限制之后批量把數(shù)據(jù)flush到磁盤中,磁盤中樹定期可以做merge操作,合并成一棵大樹,以優(yōu)化性能。
Raft
? 如何保證單機(jī)失效的情況下,數(shù)據(jù)不丟失,不出錯(cuò)?簡單來說,我們需要想辦法把數(shù)據(jù)復(fù)制到多臺機(jī)器上,這樣一臺機(jī)器掛了,我們還有其他的機(jī)器上的副本;復(fù)雜來說,我們還需要這個(gè)復(fù)制方案是可靠、高效并且能處理副本失效的情況。聽上去比較難,但是好在我們有 Raft 協(xié)議。Raft 是一個(gè)一致性算法,它和 Paxos 等價(jià),但是更加易于理解。Raft 的論文,感興趣的可以看一下。本文只會對 Raft 做一個(gè)簡要的介紹,細(xì)節(jié)問題可以參考論文。另外提一點(diǎn),Raft 論文只是一個(gè)基本方案,嚴(yán)格按照論文實(shí)現(xiàn),性能會很差,我們對 Raft 協(xié)議的實(shí)現(xiàn)做了大量的優(yōu)化,具體的優(yōu)化細(xì)節(jié)可參考tangliu 同學(xué)的《TiKV 源碼解析系列 - Raft 的優(yōu)化》這篇文章。
Raft 是一個(gè)一致性協(xié)議,提供幾個(gè)重要的功能:
1.Leader 選舉
2.成員變更
3.日志復(fù)制
? TiKV 利用 Raft 來做數(shù)據(jù)復(fù)制,每個(gè)數(shù)據(jù)變更都會落地為一條 Raft 日志,通過 Raft 的日志復(fù)制功能,將數(shù)據(jù)安全可靠地同步到 Group 的多數(shù)節(jié)點(diǎn)中
? 通過單機(jī)的 RocksDB,我們可以將數(shù)據(jù)快速地存儲在磁盤上;通過 Raft,我們可以將數(shù)據(jù)復(fù)制到多臺機(jī)器上,以防單機(jī)失效。數(shù)據(jù)的寫入是通過 Raft 這一層的接口寫入,而不是直接寫 RocksDB。通過實(shí)現(xiàn) Raft,我們擁有了一個(gè)分布式的 KV,現(xiàn)在再也不用擔(dān)心某臺機(jī)器掛掉了
Region
? 講到這里,我們可以提到一個(gè) 非常重要的概念:Region。這個(gè)概念是理解后續(xù)一系列機(jī)制的基礎(chǔ),請仔細(xì)閱讀這一節(jié)。
? 前面提到,我們將 TiKV 看做一個(gè)巨大的有序的 KV Map,那么為了實(shí)現(xiàn)存儲的水平擴(kuò)展,我們需要將數(shù)據(jù)分散在多臺機(jī)器上。這里提到的數(shù)據(jù)分散在多臺機(jī)器上和 Raft 的數(shù)據(jù)復(fù)制不是一個(gè)概念,在這一節(jié)我們先忘記 Raft,假設(shè)所有的數(shù)據(jù)都只有一個(gè)副本,這樣更容易理解。
? 對于一個(gè) KV 系統(tǒng),將數(shù)據(jù)分散在多臺機(jī)器上有兩種比較典型的方案:一種是按照 Key 做 Hash,根據(jù) Hash 值選擇對應(yīng)的存儲節(jié)點(diǎn);另一種是分 Range,某一段連續(xù)的 Key 都保存在一個(gè)存儲節(jié)點(diǎn)上。TiKV 選擇了第二種方式,將整個(gè) Key-Value 空間分成很多段,每一段是一系列連續(xù)的 Key,我們將每一段叫做一個(gè) Region,并且我們會盡量保持每個(gè) Region 中保存的數(shù)據(jù)不超過一定的大小(這個(gè)大小可以配置,目前默認(rèn)是 64mb)。每一個(gè) Region 都可以用 StartKey 到 EndKey 這樣一個(gè)左閉右開區(qū)間來描述。
注意,這里的 Region 還是和 SQL 中的表沒什么關(guān)系! 請各位繼續(xù)忘記 SQL,只談 KV。 將數(shù)據(jù)劃分成 Region 后,我們將會做 兩件重要的事情:
- 以 Region 為單位,將數(shù)據(jù)分散在集群中所有的節(jié)點(diǎn)上,并且盡量保證每個(gè)節(jié)點(diǎn)上服務(wù)的 Region 數(shù)量差不多
- 以 Region 為單位做 Raft 的復(fù)制和成員管理
? 先看第一點(diǎn),數(shù)據(jù)按照 Key 切分成很多 Region,每個(gè) Region 的數(shù)據(jù)只會保存在一個(gè)節(jié)點(diǎn)上面。我們的系統(tǒng)會有一個(gè)組件來負(fù)責(zé)將 Region 盡可能均勻的散布在集群中所有的節(jié)點(diǎn)上,這樣一方面實(shí)現(xiàn)了存儲容量的水平擴(kuò)展(增加新的結(jié)點(diǎn)后,會自動(dòng)將其他節(jié)點(diǎn)上的 Region 調(diào)度過來),另一方面也實(shí)現(xiàn)了負(fù)載均衡(不會出現(xiàn)某個(gè)節(jié)點(diǎn)有很多數(shù)據(jù),其他節(jié)點(diǎn)上沒什么數(shù)據(jù)的情況)。同時(shí)為了保證上層客戶端能夠訪問所需要的數(shù)據(jù),我們的系統(tǒng)中也會有一個(gè)組件記錄 Region 在節(jié)點(diǎn)上面的分布情況,也就是通過任意一個(gè) Key 就能查詢到這個(gè) Key 在哪個(gè) Region 中,以及這個(gè) Region 目前在哪個(gè)節(jié)點(diǎn)上。
? 對于第二點(diǎn),TiKV 是以 Region 為單位做數(shù)據(jù)的復(fù)制,也就是一個(gè) Region 的數(shù)據(jù)會保存多個(gè)副本,我們將每一個(gè)副本叫做一個(gè) Replica。Replica 之間是通過 Raft 來保持?jǐn)?shù)據(jù)的一致,一個(gè) Region 的多個(gè) Replica 會保存在不同的節(jié)點(diǎn)上,構(gòu)成一個(gè) Raft Group。其中一個(gè) Replica 會作為這個(gè) Group 的 Leader,其他的 Replica 作為 Follower。所有的讀和寫都是通過 Leader 進(jìn)行,再由 Leader 復(fù)制給 Follower。
? 我們以 Region 為單位做數(shù)據(jù)的分散和復(fù)制,就有了一個(gè)分布式的具備一定容災(zāi)能力的 KeyValue 系統(tǒng),不用再擔(dān)心數(shù)據(jù)存不下,或者是磁盤故障丟失數(shù)據(jù)的問題。這已經(jīng)很 Cool,但是還不夠完美,我們需要更多的功能。
MVCC
? 很多數(shù)據(jù)庫都會實(shí)現(xiàn)多版本控制(MVCC),TiKV 也不例外。設(shè)想這樣的場景,兩個(gè) Client 同時(shí)去修改一個(gè) Key 的 Value,如果沒有 MVCC,就需要對數(shù)據(jù)上鎖,在分布式場景下,可能會帶來性能以及死鎖問題。 TiKV 的 MVCC 實(shí)現(xiàn)是通過在 Key 后面添加 Version 來實(shí)現(xiàn),簡單來說,沒有 MVCC 之前,可以把 TiKV 看做這樣的:
? Key1 -> Value
? Key2 -> Value
? ……
? KeyN -> Value
有了 MVCC 之后,TiKV 的 Key 排列是這樣的:
? Key1-Version3 -> Value
? Key1-Version2 -> Value
? Key1-Version1 -> Value
? ……
? Key2-Version4 -> Value
? Key2-Version3 -> Value
? Key2-Version2 -> Value
? Key2-Version1 -> Value
? ……
? KeyN-Version2 -> Value
? KeyN-Version1 -> Value
? ……
注意,對于同一個(gè) Key 的多個(gè)版本,我們把版本號較大的放在前面,版本號小的放在后面(回憶一下 Key-Value 一節(jié)我們介紹過的 Key 是有序的排列),這樣當(dāng)用戶通過一個(gè) Key + Version 來獲取 Value 的時(shí)候,可以將 Key 和 Version 構(gòu)造出 MVCC 的 Key,也就是 Key-Version。然后可以直接 Seek(Key-Version),定位到第一個(gè)大于等于這個(gè) Key-Version 的位置
事務(wù)
? TiKV 的事務(wù)采用的是 Percolator 模型,并且做了大量的優(yōu)化。TiKV 的事務(wù)采用樂觀鎖,事務(wù)的執(zhí)行過程中,不會檢測寫寫沖突,只有在提交過程中,才會做沖突檢測,沖突的雙方中比較早完成提交的會寫入成功,另一方會嘗試重新執(zhí)行整個(gè)事務(wù)。當(dāng)業(yè)務(wù)的寫入沖突不嚴(yán)重的情況下,這種模型性能會很好,比如隨機(jī)更新表中某一行的數(shù)據(jù),并且表很大。但是如果業(yè)務(wù)的寫入沖突嚴(yán)重,性能就會很差,舉一個(gè)極端的例子,就是計(jì)數(shù)器,多個(gè)客戶端同時(shí)修改少量行,導(dǎo)致沖突嚴(yán)重的,造成大量的無效重試。
數(shù)據(jù)計(jì)算
關(guān)系模型到 Key-Value 模型的映射
? 在這我們將關(guān)系模型簡單理解為 Table 和 SQL 語句,那么問題變?yōu)槿绾卧?KV 結(jié)構(gòu)上保存 Table 以及如何在 KV 結(jié)構(gòu)上運(yùn)行 SQL 語句。 假設(shè)我們有這樣一個(gè)表的定義:
CREATE TABLE User {
? ID int,
? Name varchar(20),
? Role varchar(20),
? Age int,
? PRIMARY KEY (ID),
? Key idxAge (age)
};
SQL 和 KV 結(jié)構(gòu)之間存在巨大的區(qū)別,那么如何能夠方便高效地進(jìn)行映射,就成為一個(gè)很重要的問題。一個(gè)好的映射方案必須有利于對數(shù)據(jù)操作的需求。那么我們先看一下對數(shù)據(jù)的操作有哪些需求,分別有哪些特點(diǎn)。
對于一個(gè) Table 來說,需要存儲的數(shù)據(jù)包括三部分:
1.表的元信息
2.Table 中的 Row
3.索引數(shù)據(jù)
表的元信息我們暫時(shí)不討論,后面介紹。
對于 Row,可以選擇行存或者列存,這兩種各有優(yōu)缺點(diǎn)。TiDB 面向的首要目標(biāo)是 OLTP 業(yè)務(wù),這類業(yè)務(wù)需要支持快速地讀取、保存、修改、刪除一行數(shù)據(jù),所以采用行存是比較合適的。
對于 Index,TiDB 不止需要支持 Primary Index,還需要支持 Secondary Index。Index 的作用的輔助查詢,提升查詢性能,以及保證某些 Constraint。
查詢的時(shí)候有兩種模式,一種是點(diǎn)查,比如通過 Primary Key 或者 Unique Key 的等值條件進(jìn)行查詢,如 select name from user where id=1; ,這種需要通過索引快速定位到某一行數(shù)據(jù);另一種是 Range 查詢,如 select name from user where age > 30 and age < 35;,這個(gè)時(shí)候需要通過idxAge索引查詢 age 在 30 和 35 之間的那些數(shù)據(jù)。Index 還分為 Unique Index 和 非 Unique Index,這兩種都需要支持。
分析完需要存儲的數(shù)據(jù)的特點(diǎn),我們再看看對這些數(shù)據(jù)的操作需求,主要考慮 Insert/Update/Delete/Select 這四種語句。
對于 Insert 語句,需要將 Row 寫入 KV,并且建立好索引數(shù)據(jù)。
對于 Update 語句,需要將 Row 更新的同時(shí),更新索引數(shù)據(jù)(如果有必要)。
對于 Delete 語句,需要在刪除 Row 的同時(shí),將索引也刪除。
上面三個(gè)語句處理起來都很簡單。對于 Select 語句,情況會復(fù)雜一些。首先我們需要能夠簡單快速地讀取一行數(shù)據(jù),所以每個(gè) Row 需要有一個(gè) ID (顯示或隱式的 ID)。其次可能會讀取連續(xù)多行數(shù)據(jù),比如 Select * from user;。最后還有通過索引讀取數(shù)據(jù)的需求,對索引的使用可能是點(diǎn)查或者是范圍查詢。
大致的需求已經(jīng)分析完了,現(xiàn)在讓我們看看手里有什么可以用的:一個(gè)全局有序的分布式 Key-Value 引擎。全局有序這一點(diǎn)重要,可以幫助我們解決不少問題。比如對于快速獲取一行數(shù)據(jù),假設(shè)我們能夠構(gòu)造出某一個(gè)或者某幾個(gè) Key,定位到這一行,我們就能利用 TiKV 提供的 Seek 方法快速定位到這一行數(shù)據(jù)所在位置。再比如對于掃描全表的需求,如果能夠映射為一個(gè) Key 的 Range,從 StartKey 掃描到 EndKey,那么就可以簡單的通過這種方式獲得全表數(shù)據(jù)。操作 Index 數(shù)據(jù)也是類似的思路。接下來讓我們看看 TiDB 是如何做的。
TiDB 對每個(gè)表分配一個(gè) TableID,每一個(gè)索引都會分配一個(gè) IndexID,每一行分配一個(gè) RowID(如果表有整數(shù)型的 Primary Key,那么會用 Primary Key 的值當(dāng)做 RowID),其中 TableID 在整個(gè)集群內(nèi)唯一,IndexID/RowID 在表內(nèi)唯一,這些 ID 都是 int64 類型。
每行數(shù)據(jù)按照如下規(guī)則進(jìn)行編碼成 Key-Value pair:
Key: tablePrefix{tableID}_recordPrefixSep{rowID}
Value: [col1, col2, col3, col4]
其中 Key 的 tablePrefix/recordPrefixSep 都是特定的字符串常量,用于在 KV 空間內(nèi)區(qū)分其他數(shù)據(jù)。
對于 Index 數(shù)據(jù),會按照如下規(guī)則編碼成 Key-Value pair:
Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue
Value: rowID
Index 數(shù)據(jù)還需要考慮 Unique Index 和非 Unique Index 兩種情況,對于 Unique Index,可以按照上述編碼規(guī)則。但是對于非 Unique Index,通過這種編碼并不能構(gòu)造出唯一的 Key,因?yàn)橥粋€(gè) Index 的 tablePrefix{tableID}_indexPrefixSep{indexID} 都一樣,可能有多行數(shù)據(jù)的 ColumnsValue 是一樣的,所以對于非 Unique Index 的編碼做了一點(diǎn)調(diào)整:
Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue_rowID
Value: null
這樣能夠?qū)λ饕械拿啃袛?shù)據(jù)構(gòu)造出唯一的 Key。
注意上述編碼規(guī)則中的 Key 里面的各種 xxPrefix 都是字符串常量,作用都是區(qū)分命名空間,以免不同類型的數(shù)據(jù)之間相互沖突,定義如下:
var(
? tablePrefix = []byte{‘t’}
? recordPrefixSep = []byte(“_r”)
? indexPrefixSep = []byte(“_i”)
)
另外請大家注意,上述方案中,無論是 Row 還是 Index 的 Key 編碼方案,一個(gè) Table 內(nèi)部所有的 Row 都有相同的前綴,一個(gè) Index 的數(shù)據(jù)也都有相同的前綴。這樣具體相同的前綴的數(shù)據(jù),在 TiKV 的 Key 空間內(nèi),是排列在一起。
同時(shí)只要我們小心地設(shè)計(jì)后綴部分的編碼方案,保證編碼前和編碼后的比較關(guān)系不變,那么就可以將 Row 或者 Index 數(shù)據(jù)有序地保存在 TiKV 中。這種保證編碼前和編碼后的比較關(guān)系不變 的方案我們稱為 Memcomparable,對于任何類型的值,兩個(gè)對象編碼前的原始類型比較結(jié)果,和編碼成 byte 數(shù)組后(注意,TiKV 中的 Key 和 Value 都是原始的 byte 數(shù)組)的比較結(jié)果保持一致。采用這種編碼后,一個(gè)表的所有 Row 數(shù)據(jù)就會按照 RowID 的順序排列在 TiKV 的 Key 空間中,某一個(gè) Index 的數(shù)據(jù)也會按照 Index 的 ColumnValue 順序排列在 Key 空間內(nèi)。
現(xiàn)在我們結(jié)合開始提到的需求以及 TiDB 的映射方案來看一下,這個(gè)方案是否能滿足需求。
首先我們通過這個(gè)映射方案,將 Row 和 Index 數(shù)據(jù)都轉(zhuǎn)換為 Key-Value 數(shù)據(jù),且每一行、每一條索引數(shù)據(jù)都是有唯一的 Key。
其次,這種映射方案對于點(diǎn)查、范圍查詢都很友好,我們可以很容易地構(gòu)造出某行、某條索引所對應(yīng)的 Key,或者是某一塊相鄰的行、相鄰的索引值所對應(yīng)的 Key 范圍。
最后,在保證表中的一些 Constraint 的時(shí)候,可以通過構(gòu)造并檢查某個(gè) Key 是否存在來判斷是否能夠滿足相應(yīng)的 Constraint。
至此我們已經(jīng)聊完了如何將 Table 映射到 KV 上面,這里再舉個(gè)簡單的例子,便于大家理解,還是以上面的表結(jié)構(gòu)為例。假設(shè)表中有 3 行數(shù)據(jù):
1, “TiDB”, “SQL Layer”, 10
2, “TiKV”, “KV Engine”, 20
3, “PD”, “Manager”, 30
那么首先每行數(shù)據(jù)都會映射為一個(gè) Key-Value pair,注意這個(gè)表有一個(gè) Int 類型的 Primary Key,所以 RowID 的值即為這個(gè) Primary Key 的值。假設(shè)這個(gè)表的 Table ID 為 10,其 Row 的數(shù)據(jù)為:
t10_r1 --> [“TiDB”, “SQL Layer”, 10]
t10_r2 --> [“TiKV”, “KV Engine”, 20]
t10_r3 --> [“PD”, “Manager”, 30]
除了 Primary Key 之外,這個(gè)表還有一個(gè) Index,假設(shè)這個(gè) Index 的 ID 為 1,則其數(shù)據(jù)為:
t10_i1_10_1 --> null
t10_i1_20_2 --> null
t10_i1_30_3 --> null
元信息管理
? 上節(jié)介紹了表中的數(shù)據(jù)和索引是如何映射為 KV,本節(jié)介紹一下元信息的存儲。Database/Table 都有元信息,也就是其定義以及各項(xiàng)屬性,這些信息也需要持久化,我們也將這些信息存儲在 TiKV 中。每個(gè) Database/Table 都被分配了一個(gè)唯一的 ID,這個(gè) ID 作為唯一標(biāo)識,并且在編碼為 Key-Value 時(shí),這個(gè) ID 都會編碼到 Key 中,再加上 m_ 前綴。這樣可以構(gòu)造出一個(gè) Key,Value 中存儲的是序列化后的元信息。
? 除此之外,還有一個(gè)專門的 Key-Value 存儲當(dāng)前 Schema 信息的版本。TiDB 使用 Google F1 的 Online Schema 變更算法,有一個(gè)后臺線程在不斷的檢查 TiKV 上面存儲的 Schema 版本是否發(fā)生變化,并且保證在一定時(shí)間內(nèi)一定能夠獲取版本的變化(如果確實(shí)發(fā)生了變化)
SQL on KV 架構(gòu)
? TiKV Cluster 主要作用是作為 KV 引擎存儲數(shù)據(jù),前面已經(jīng)介紹過了細(xì)節(jié),這里不再敷述。這里主要介紹 SQL 層,也就是 TiDB Servers 這一層,這一層的節(jié)點(diǎn)都是無狀態(tài)的節(jié)點(diǎn),本身并不存儲數(shù)據(jù),節(jié)點(diǎn)之間完全對等。TiDB Server 這一層最重要的工作是處理用戶請求,執(zhí)行 SQL 運(yùn)算邏輯,接下來我們做一些簡單的介紹
SQL運(yùn)算
? 理解了 SQL 到 KV 的映射方案之后,我們可以理解關(guān)系數(shù)據(jù)是如何保存的,接下來我們要理解如何使用這些數(shù)據(jù)來滿足用戶的查詢需求,也就是一個(gè)查詢語句是如何操作底層存儲的數(shù)據(jù)。
? 能想到的最簡單的方案就是通過上一節(jié)所述的映射方案,將 SQL 查詢映射為對 KV 的查詢,再通過 KV 接口獲取對應(yīng)的數(shù)據(jù),最后執(zhí)行各種計(jì)算。
比如 Select count(*) from user where name=“TiDB”; 這樣一個(gè)語句,我們需要讀取表中所有的數(shù)據(jù),然后檢查 Name 字段是否是 TiDB,如果是的話,則返回這一行。這樣一個(gè)操作流程轉(zhuǎn)換為 KV 操作流程:
- 構(gòu)造出 Key Range:一個(gè)表中所有的 RowID 都在 [0, MaxInt64) 這個(gè)范圍內(nèi),那么我們用 0 和 MaxInt64 根據(jù) Row 的 Key 編碼規(guī)則,就能構(gòu)造出一個(gè) [StartKey, EndKey) 的左閉右開區(qū)間
- 掃描 Key Range:根據(jù)上面構(gòu)造出的 Key Range,讀取 TiKV 中的數(shù)據(jù)
- 過濾數(shù)據(jù):對于讀到的每一行數(shù)據(jù),計(jì)算 name=“TiDB” 這個(gè)表達(dá)式,如果為真,則向上返回這一行,否則丟棄這一行數(shù)據(jù)
- 計(jì)算 Count:對符合要求的每一行,累計(jì)到 Count 值上面 這個(gè)方案肯定是可以 Work 的,但是并不能 Work 的很好,原因是顯而易見的:
- 在掃描數(shù)據(jù)的時(shí)候,每一行都要通過 KV 操作同 TiKV 中讀取出來,至少有一次 RPC 開銷,如果需要掃描的數(shù)據(jù)很多,那么這個(gè)開銷會非常大
- 并不是所有的行都有用,如果不滿足條件,其實(shí)可以不讀取出來
- 符合要求的行的值并沒有什么意義,實(shí)際上這里只需要有幾行數(shù)據(jù)這個(gè)信息就行
分布式SQL運(yùn)算
如何避免上述缺陷也是顯而易見的,
首先我們需要將計(jì)算盡量靠近存儲節(jié)點(diǎn),以避免大量的 RPC 調(diào)用。
其次,我們需要將 Filter 也下推到存儲節(jié)點(diǎn)進(jìn)行計(jì)算,這樣只需要返回有效的行,避免無意義的網(wǎng)絡(luò)傳輸。
最后,我們可以將聚合函數(shù)、GroupBy 也下推到存儲節(jié)點(diǎn),進(jìn)行預(yù)聚合,每個(gè)節(jié)點(diǎn)只需要返回一個(gè) Count 值即可,再由 tidb-server 將 Count 值 Sum 起來。
這里有一個(gè)數(shù)據(jù)逐層返回的示意圖:
SQL層架構(gòu)
? 上面幾節(jié)簡要介紹了 SQL 層的一些功能,希望大家對 SQL 語句的處理有一個(gè)基本的了解。實(shí)際上 TiDB 的 SQL 層要復(fù)雜的多,模塊以及層次非常多,下面這個(gè)圖列出了重要的模塊以及調(diào)用關(guān)系:
? 用戶的 SQL 請求會直接或者通過 Load Balancer 發(fā)送到 tidb-server,tidb-server 會解析 MySQL Protocol Packet,獲取請求內(nèi)容,然后做語法解析、查詢計(jì)劃制定和優(yōu)化、執(zhí)行查詢計(jì)劃獲取和處理數(shù)據(jù)。
數(shù)據(jù)全部存儲在 TiKV 集群中,所以在這個(gè)過程中 tidb-server 需要和 tikv-server 交互,獲取數(shù)據(jù)。
最后 tidb-server 需要將查詢結(jié)果返回給用戶
任務(wù)調(diào)度
為什么要進(jìn)行調(diào)度
先回憶一下TiDB 技術(shù)內(nèi)幕 - 存儲提到的一些信息,TiKV 集群是 TiDB 數(shù)據(jù)庫的分布式 KV 存儲引擎,數(shù)據(jù)以 Region 為單位進(jìn)行復(fù)制和管理,每個(gè) Region 會有多個(gè) Replica(副本),這些 Replica 會分布在不同的 TiKV 節(jié)點(diǎn)上,其中 Leader 負(fù)責(zé)讀/寫,F(xiàn)ollower 負(fù)責(zé)同步 Leader 發(fā)來的 raft log。了解了這些信息后,請思考下面這些問題:
- 如何保證同一個(gè) Region 的多個(gè) Replica 分布在不同的節(jié)點(diǎn)上?更進(jìn)一步,如果在一臺機(jī)器上啟動(dòng)多個(gè) TiKV 實(shí)例,會有什么問題?
- TiKV 集群進(jìn)行跨機(jī)房部署用于容災(zāi)的時(shí)候,如何保證一個(gè)機(jī)房掉線,不會丟失 Raft Group 的多個(gè) Replica?
- 添加一個(gè)節(jié)點(diǎn)進(jìn)入 TiKV 集群之后,如何將集群中其他節(jié)點(diǎn)上的數(shù)據(jù)搬過來?
- 當(dāng)一個(gè)節(jié)點(diǎn)掉線時(shí),會出現(xiàn)什么問題?整個(gè)集群需要做什么事情?如果節(jié)點(diǎn)只是短暫掉線(重啟服務(wù)),那么如何處理?如果節(jié)點(diǎn)是長時(shí)間掉線(磁盤故障,數(shù)據(jù)全部丟失),需要如何處理?
- 假設(shè)集群需要每個(gè) Raft Group 有 N 個(gè)副本,那么對于單個(gè) Raft Group 來說,Replica 數(shù)量可能會不夠多(例如節(jié)點(diǎn)掉線,失去副本),也可能會過于多(例如掉線的節(jié)點(diǎn)又回復(fù)正常,自動(dòng)加入集群)。那么如何調(diào)節(jié) Replica 個(gè)數(shù)?
- 讀/寫都是通過 Leader 進(jìn)行,如果 Leader 只集中在少量節(jié)點(diǎn)上,會對集群有什么影響?
- 并不是所有的 Region 都被頻繁的訪問,可能訪問熱點(diǎn)只在少數(shù)幾個(gè) Region,這個(gè)時(shí)候我們需要做什么?
- 集群在做負(fù)載均衡的時(shí)候,往往需要搬遷數(shù)據(jù),這種數(shù)據(jù)的遷移會不會占用大量的網(wǎng)絡(luò)帶寬、磁盤 IO 以及 CPU?進(jìn)而影響在線服務(wù)?
? 這些問題單獨(dú)拿出可能都能找到簡單的解決方案,但是混雜在一起,就不太好解決。有的問題貌似只需要考慮單個(gè) Raft Group 內(nèi)部的情況,比如根據(jù)副本數(shù)量是否足夠多來決定是否需要添加副本。但是實(shí)際上這個(gè)副本添加在哪里,是需要考慮全局的信息。整個(gè)系統(tǒng)也是在動(dòng)態(tài)變化,Region 分裂、節(jié)點(diǎn)加入、節(jié)點(diǎn)失效、訪問熱點(diǎn)變化等情況會不斷發(fā)生,整個(gè)調(diào)度系統(tǒng)也需要在動(dòng)態(tài)中不斷向最優(yōu)狀態(tài)前進(jìn),如果沒有一個(gè)掌握全局信息,可以對全局進(jìn)行調(diào)度,并且可以配置的組件,就很難滿足這些需求。因此我們需要一個(gè)中心節(jié)點(diǎn),來對系統(tǒng)的整體狀況進(jìn)行把控和調(diào)整,所以有了 PD 這個(gè)模塊。
調(diào)度的需求
上面羅列了一大堆問題,我們先進(jìn)行分類和整理??傮w來看,問題有兩大類:
1.作為一個(gè)分布式高可用存儲系統(tǒng),必須滿足的需求,包括四種:
-
副本數(shù)量不能多也不能少
-
副本需要分布在不同的機(jī)器上
-
新加節(jié)點(diǎn)后,可以將其他節(jié)點(diǎn)上的副本遷移過來
-
節(jié)點(diǎn)下線后,需要將該節(jié)點(diǎn)的數(shù)據(jù)遷移走
2.作為一個(gè)良好的分布式系統(tǒng),需要優(yōu)化的地方,包括: -
維持整個(gè)集群的 Leader 分布均勻
-
維持每個(gè)節(jié)點(diǎn)的儲存容量均勻
-
維持訪問熱點(diǎn)分布均勻
-
控制 Balance 的速度,避免影響在線服務(wù)
-
管理節(jié)點(diǎn)狀態(tài),包括手動(dòng)上線/下線節(jié)點(diǎn),以及自動(dòng)下線失效節(jié)點(diǎn)
滿足第一類需求后,整個(gè)系統(tǒng)將具備多副本容錯(cuò)、動(dòng)態(tài)擴(kuò)容/縮容、容忍節(jié)點(diǎn)掉線以及自動(dòng)錯(cuò)誤恢復(fù)的功能。
滿足第二類需求后,可以使得整體系統(tǒng)的負(fù)載更加均勻、且可以方便的管理。
為了滿足這些需求,首先我們需要收集足夠的信息,比如每個(gè)節(jié)點(diǎn)的狀態(tài)、每個(gè) Raft Group 的信息、業(yè)務(wù)訪問操作的統(tǒng)計(jì)等;
其次需要設(shè)置一些策略,PD 根據(jù)這些信息以及調(diào)度的策略,制定出盡量滿足前面所述需求的調(diào)度計(jì)劃;最后需要一些基本的操作,來完成調(diào)度計(jì)劃。
調(diào)度的基本操作
我們先來介紹最簡單的一點(diǎn),也就是調(diào)度的基本操作,也就是為了滿足調(diào)度的策略,我們有哪些功能可以用。這是整個(gè)調(diào)度的基礎(chǔ),了解了手里有什么樣的錘子,才知道用什么樣的姿勢去砸釘子。
上述調(diào)度需求看似復(fù)雜,但是整理下來最終落地的無非是下面三件事:
-
增加一個(gè) Replica
-
刪除一個(gè) Replica
-
將 Leader 角色在一個(gè) Raft Group 的不同 Replica 之間 transfer
剛好 Raft 協(xié)議能夠滿足這三種需求,通過 AddReplica、RemoveReplica、TransferLeader 這三個(gè)命令,可以支撐上述三種基本操作。
信息收集
調(diào)度依賴于整個(gè)集群信息的收集,簡單來說,我們需要知道每個(gè) TiKV 節(jié)點(diǎn)的狀態(tài)以及每個(gè) Region 的狀態(tài)。TiKV 集群會向 PD 匯報(bào)兩類消息:
每個(gè) TiKV 節(jié)點(diǎn)會定期向 PD 匯報(bào)節(jié)點(diǎn)的整體信息
TiKV 節(jié)點(diǎn)(Store)與 PD 之間存在心跳包,一方面 PD 通過心跳包檢測每個(gè) Store 是否存活,以及是否有新加入的 Store;另一方面,心跳包中也會攜帶這個(gè) Store 的狀態(tài)信息,主要包括:
- 總磁盤容量
- 可用磁盤容量
- 承載的 Region 數(shù)量
- 數(shù)據(jù)寫入速度
- 發(fā)送/接受的 Snapshot 數(shù)量(Replica 之間可能會通過 Snapshot 同步數(shù)據(jù))
- 是否過載
- 標(biāo)簽信息(標(biāo)簽是具備層級關(guān)系的一系列 Tag)
每個(gè) Raft Group 的 Leader 會定期向 PD 匯報(bào)信息
每個(gè) Raft Group 的 Leader 和 PD 之間存在心跳包,用于匯報(bào)這個(gè) Region 的狀態(tài),主要包括下面幾點(diǎn)信息:
- Leader 的位置
- Followers 的位置
- 掉線 Replica 的個(gè)數(shù)
- 數(shù)據(jù)寫入/讀取的速度
PD 不斷的通過這兩類心跳消息收集整個(gè)集群的信息,再以這些信息作為決策的依據(jù)。除此之外,PD 還可以通過管理接口接受額外的信息,用來做更準(zhǔn)確的決策。比如當(dāng)某個(gè) Store 的心跳包中斷的時(shí)候,PD 并不能判斷這個(gè)節(jié)點(diǎn)是臨時(shí)失效還是永久失效,只能經(jīng)過一段時(shí)間的等待(默認(rèn)是 30 分鐘),如果一直沒有心跳包,就認(rèn)為是 Store 已經(jīng)下線,再決定需要將這個(gè) Store 上面的 Region 都調(diào)度走。但是有的時(shí)候,是運(yùn)維人員主動(dòng)將某臺機(jī)器下線,這個(gè)時(shí)候,可以通過 PD 的管理接口通知 PD 該 Store 不可用,PD 就可以馬上判斷需要將這個(gè) Store 上面的 Region 都調(diào)度走。
調(diào)度的策略
PD 收集了這些信息后,還需要一些策略來制定具體的調(diào)度計(jì)劃。
- 一個(gè) Region 的 Replica 數(shù)量正確
當(dāng) PD 通過某個(gè) Region Leader 的心跳包發(fā)現(xiàn)這個(gè) Region 的 Replica 數(shù)量不滿足要求時(shí),需要通過 Add/Remove Replica 操作調(diào)整 Replica 數(shù)量。出現(xiàn)這種情況的可能原因是:-
某個(gè)節(jié)點(diǎn)掉線,上面的數(shù)據(jù)全部丟失,導(dǎo)致一些 Region 的 Replica 數(shù)量不足
-
某個(gè)掉線節(jié)點(diǎn)又恢復(fù)服務(wù),自動(dòng)接入集群,這樣之前已經(jīng)補(bǔ)足了 Replica 的 Region 的 Replica 數(shù)量多過,需要?jiǎng)h除某個(gè) Replic
-
- 管理員調(diào)整了副本策略,修改了 max-replicas 的配置
2.一個(gè) Raft Group 中的多個(gè) Replica 不在同一個(gè)位置
注意第二點(diǎn),『一個(gè) Raft Group 中的多個(gè) Replica 不在同一個(gè)位置』,這里用的是『同一個(gè)位置』而不是『同一個(gè)節(jié)點(diǎn)』。在一般情況下,PD 只會保證多個(gè) Replica 不落在一個(gè)節(jié)點(diǎn)上,以避免單個(gè)節(jié)點(diǎn)失效導(dǎo)致多個(gè) Replica 丟失。在實(shí)際部署中,還可能出現(xiàn)下面這些需求:
-
多個(gè)節(jié)點(diǎn)部署在同一臺物理機(jī)器上
-
TiKV 節(jié)點(diǎn)分布在多個(gè)機(jī)架上,希望單個(gè)機(jī)架掉電時(shí),也能保證系統(tǒng)可用性
-
TiKV 節(jié)點(diǎn)分布在多個(gè) IDC 中,希望單個(gè)機(jī)房掉電時(shí),也能保證系統(tǒng)可用
這些需求本質(zhì)上都是某一個(gè)節(jié)點(diǎn)具備共同的位置屬性,構(gòu)成一個(gè)最小的容錯(cuò)單元,我們希望這個(gè)單元內(nèi)部不會存在一個(gè) Region 的多個(gè) Replica。這個(gè)時(shí)候,可以給節(jié)點(diǎn)配置 lables 并且通過在 PD 上配置 location-labels 來指明哪些 lable 是位置標(biāo)識,需要在 Replica 分配的時(shí)候盡量保證不會有一個(gè) Region 的多個(gè) Replica 所在結(jié)點(diǎn)有相同的位置標(biāo)識。
3.副本在 Store 之間的分布均勻分配
前面說過,每個(gè)副本中存儲的數(shù)據(jù)容量上限是固定的,所以我們維持每個(gè)節(jié)點(diǎn)上面,副本數(shù)量的均衡,會使得總體的負(fù)載更均衡。
4.Leader 數(shù)量在 Store 之間均勻分配
Raft 協(xié)議要讀取和寫入都通過 Leader 進(jìn)行,所以計(jì)算的負(fù)載主要在 Leader 上面,PD 會盡可能將 Leader 在節(jié)點(diǎn)間分散開。
5.訪問熱點(diǎn)數(shù)量在 Store 之間均勻分配
每個(gè) Store 以及 Region Leader 在上報(bào)信息時(shí)攜帶了當(dāng)前訪問負(fù)載的信息,比如 Key 的讀取/寫入速度。PD 會檢測出訪問熱點(diǎn),且將其在節(jié)點(diǎn)之間分散開。
6.各個(gè) Store 的存儲空間占用大致相等
每個(gè) Store 啟動(dòng)的時(shí)候都會指定一個(gè) Capacity 參數(shù),表明這個(gè) Store 的存儲空間上限,PD 在做調(diào)度的時(shí)候,會考慮節(jié)點(diǎn)的存儲空間剩余量。
7.控制調(diào)度速度,避免影響在線服務(wù)
調(diào)度操作需要耗費(fèi) CPU、內(nèi)存、磁盤 IO 以及網(wǎng)絡(luò)帶寬,我們需要避免對線上服務(wù)造成太大影響。PD 會對當(dāng)前正在進(jìn)行的操作數(shù)量進(jìn)行控制,默認(rèn)的速度控制是比較保守的,如果希望加快調(diào)度(比如已經(jīng)停服務(wù)升級,增加新節(jié)點(diǎn),希望盡快調(diào)度),那么可以通過 pd-ctl 手動(dòng)加快調(diào)度速度。
8.支持手動(dòng)下線節(jié)點(diǎn)
當(dāng)通過 pd-ctl 手動(dòng)下線節(jié)點(diǎn)后,PD 會在一定的速率控制下,將節(jié)點(diǎn)上的數(shù)據(jù)調(diào)度走。當(dāng)調(diào)度完成后,就會將這個(gè)節(jié)點(diǎn)置為下線狀態(tài)。
調(diào)度的實(shí)現(xiàn)
了解了上面這些信息后,接下來我們看一下整個(gè)調(diào)度的流程。文章來源:http://www.zghlxwxcb.cn/news/detail-418817.html
? PD 不斷的通過 Store 或者 Leader 的心跳包收集信息,獲得整個(gè)集群的詳細(xì)數(shù)據(jù),并且根據(jù)這些信息以及調(diào)度策略生成調(diào)度操作序列,每次收到 Region Leader 發(fā)來的心跳包時(shí),PD 都會檢查是否有對這個(gè) Region 待進(jìn)行的操作,通過心跳包的回復(fù)消息,將需要進(jìn)行的操作返回給 Region Leader,并在后面的心跳包中監(jiān)測執(zhí)行結(jié)果。注意這里的操作只是給 Region Leader 的建議,并不保證一定能得到執(zhí)行,具體是否會執(zhí)行以及什么時(shí)候執(zhí)行,由 Region Leader 自己根據(jù)當(dāng)前自身狀態(tài)來定文章來源地址http://www.zghlxwxcb.cn/news/detail-418817.html
到了這里,關(guān)于TIDB簡介及TIDB部署、原理和使用介紹的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!