一、前言之前跟朋友聊天也會(huì)聊到,基于現(xiàn)有的微服務(wù)架構(gòu),絕大多數(shù)的性能瓶頸都不在服務(wù),因?yàn)槲覀兊姆?wù)是可以橫向擴(kuò)展的。 在很多的 case 下,這個(gè)瓶頸就是「數(shù)據(jù)庫(kù)」。例如,我們?yōu)榱藴p輕 MySQL 的負(fù)擔(dān),會(huì)引入消息隊(duì)列來(lái)對(duì)流量進(jìn)行削峰;再例如會(huì)引入 Redis 來(lái)緩存一些不太常變的數(shù)據(jù),來(lái)減少對(duì) MySQL 的請(qǐng)求。 另一方面,如果業(yè)務(wù)往 MySQL 中灌入了海量的數(shù)據(jù),不做優(yōu)化的話,會(huì)影響 MySQL 的性能。而對(duì)于這種情況,就需要進(jìn)行分庫(kù)分表,落地起來(lái)還是較為麻煩的。 聊著聊著,就聊到了分布式數(shù)據(jù)庫(kù)。其對(duì)數(shù)據(jù)的存儲(chǔ)方式就類似于 Redis Cluster 這種,不管你給我灌多少的數(shù)據(jù),理論上我都能夠吞下去。這樣一來(lái)也不用擔(dān)心后期數(shù)據(jù)量大了需要進(jìn)行分庫(kù)分表。 剛好,之前閑逛的時(shí)候看到了 PingCAP 的 TiDB,正好就來(lái)聊一聊。 二、正文
1.TiDB Server還是從一個(gè)黑盒子講起,在沒(méi)有了解之前,我們對(duì) TiDB 的認(rèn)識(shí)就是,我們往里面丟數(shù)據(jù),TiDB 負(fù)責(zé)存儲(chǔ)數(shù)據(jù)。并且由于是分布式的,所以理論上只要存儲(chǔ)資源夠,多大的數(shù)據(jù)都能夠存下。 我們知道,TiDB 支持 MySQL,或者說(shuō)兼容大多數(shù) MySQL 的語(yǔ)法。那我們就還是拿一個(gè) Insert 語(yǔ)句來(lái)當(dāng)作切入點(diǎn),探索數(shù)據(jù)在 TiDB 中到底是如何存儲(chǔ)的。 首先要執(zhí)行語(yǔ)句,必然要先建立連接。 在 MySQL 中,負(fù)責(zé)處理客戶端連接的是 MySQL Server,在 TiDB 中也有同樣的角色 —— TiDB Server,雖角色類似,但兩者有著很多的不同。 TiDB Server 對(duì)外暴露 MySQL 協(xié)議,負(fù)責(zé) SQL 的解析、優(yōu)化,并最終生成分布式執(zhí)行計(jì)劃,MySQL 的 Server 層也會(huì)涉及到 SQL 的解析、優(yōu)化,但與 MySQL 最大的不同在于,TiDB Server 是無(wú)狀態(tài)的。 而 MySQL Server 由于和底層存儲(chǔ)引擎的耦合部署在同一個(gè)節(jié)點(diǎn),并且在內(nèi)存中緩存了頁(yè)的數(shù)據(jù),是有狀態(tài)的。
而由于 TiDB Server 的無(wú)狀態(tài)特性,在生產(chǎn)中可以啟動(dòng)多個(gè)實(shí)例,并通過(guò)負(fù)載均衡的策略來(lái)對(duì)外提供統(tǒng)一服務(wù)。
總結(jié)下來(lái),TiDB Server 只干一件事:負(fù)責(zé)解析 SQL,將實(shí)際的數(shù)據(jù)操作轉(zhuǎn)發(fā)給存儲(chǔ)節(jié)點(diǎn)。 2.TiKV我們知道,對(duì)于 MySQL,其存儲(chǔ)引擎(絕大多數(shù)情況)是 InnoDB,其存儲(chǔ)采用的數(shù)據(jù)結(jié)構(gòu)是 B+ 樹(shù),最終以 .ibd 文件的形式存儲(chǔ)在磁盤上。那 TiDB 呢? TiDB 的存儲(chǔ)是由 TiKV 來(lái)負(fù)責(zé)的,這是一個(gè)分布式、支持事務(wù)的 KV 存儲(chǔ)引擎。說(shuō)到底,它就是個(gè) KV 存儲(chǔ)引擎。 用大白話說(shuō),這就是個(gè)巨大的、有序的 Map。但說(shuō)到 KV 存儲(chǔ),很多人可能會(huì)聯(lián)想到 Redis,數(shù)據(jù)大多數(shù)時(shí)候是放在內(nèi)存,就算是 Redis,也會(huì)有像 RDB 和 AOF 這樣的持久化方式。那 TiKV 作為一個(gè)分布式的數(shù)據(jù)庫(kù),也不例外。它采用 RocksDB 引擎來(lái)實(shí)現(xiàn)持久化,具體的數(shù)據(jù)落地由其全權(quán)負(fù)責(zé)。
3.索引數(shù)據(jù)直接拿官網(wǎng)的例子給大家看看,話說(shuō) TiDB 中有這樣的一張表: 然后表里有三行數(shù)據(jù): 這三行數(shù)據(jù),每一行都會(huì)被映射成為一個(gè)鍵值對(duì): 其中,Key 中的 t10 代表 ID 為 10 的表,r1 代表 RowID 為 1 的行,由于我們建表時(shí)制定了主鍵,所以 RowID 就為主鍵的值。Value 就是該行除了主鍵之外的其他字段的值,上圖表示的就是主鍵索引。 那如果是非聚簇索引(二級(jí)索引)呢?就比如索引 idxAge,建表語(yǔ)句里對(duì) Age 這一列建立了二級(jí)索引: i1 代表 ID 為 1 的索引,即當(dāng)前這個(gè)二級(jí)索引,10、20、30 則是索引列 Age 的值,最后的 1、2、3 則是對(duì)應(yīng)的行的主鍵 ID。從建索引的語(yǔ)句部分可以看出來(lái),idxAge 是個(gè)普通的二級(jí)索引,不是唯一索引。所以索引中允許存在多個(gè) Age 為 30 的列。 但如果我們是唯一索引呢? 只拿表 ID、索引 ID 和索引值來(lái)組成 Key,這樣一來(lái)如果再次插入 Age 為 30 的數(shù)據(jù),TiKV 就會(huì)發(fā)現(xiàn)該 Key 已經(jīng)存在了,就能做到唯一鍵檢測(cè)。 4.存儲(chǔ)細(xì)節(jié)知道了列數(shù)據(jù)是如何映射成 Map 的,我們就可以繼續(xù)了解存儲(chǔ)相關(guān)的細(xì)節(jié)了。 從圖中,我們可以看出個(gè)問(wèn)題:如果某個(gè) TiKV 節(jié)點(diǎn)掛了,那么該節(jié)點(diǎn)上的所有數(shù)據(jù)是不是都沒(méi)了? 當(dāng)然不是的,TiDB 可以是一款金融級(jí)高可用的分布式關(guān)系型數(shù)據(jù)庫(kù),怎么可能會(huì)讓這種事發(fā)生。 TiKV 在存儲(chǔ)數(shù)據(jù)時(shí),會(huì)將同一份數(shù)據(jù)想辦法存儲(chǔ)到多個(gè) TiKV 節(jié)點(diǎn)上,并且使用 Raft 協(xié)議來(lái)保證同一份數(shù)據(jù)在多個(gè) TiKV 節(jié)點(diǎn)上的數(shù)據(jù)一致性。
簡(jiǎn)單來(lái)說(shuō),就是會(huì)選擇其中一份數(shù)據(jù)作為 Leader 對(duì)外提供讀、寫服務(wù),其余的作為 Follower 僅僅只同步 Leader 的數(shù)據(jù)。當(dāng) Leader 掛掉之后,可以自動(dòng)的進(jìn)行故障轉(zhuǎn)移,從 Follower 中重新選舉新的 Leader 出來(lái)。 看到這,是不是覺(jué)得跟 Kafka 有那么點(diǎn)神似了。Kafka 中一個(gè) Topic 是邏輯概念,實(shí)際上會(huì)分成多個(gè) Partition,分散到多個(gè) Broker 上,并且會(huì)選舉一個(gè) Leader Partition 對(duì)外提供服務(wù),當(dāng) Leader Partition 出現(xiàn)故障時(shí),會(huì)從 Follower Partiiton 中重新再選舉一個(gè) Leader 出來(lái)。 那么,Kafka 中選舉、提供服務(wù)的單位是 Partition,TiDB 中的是什么呢? 5.Region答案是 Region。剛剛講過(guò),TiKV 可以理解為一個(gè)巨大的 Map,而 Map 中某一段連續(xù)的 Key 就是一個(gè) Region。不同的 Region 會(huì)保存在不同的 TiKV 上。 一個(gè) Region 有多個(gè)副本,每個(gè)副本也叫 Replica,多個(gè) Replica 組成了一個(gè) Raft Group。按照上面介紹的邏輯,某個(gè) Replica 會(huì)被選作 Leader,其余 Replica 作為 Follower。 并且,在數(shù)據(jù)寫入時(shí),TiDB 會(huì)盡量保證 Region 不會(huì)超過(guò)一定的大小,目前這個(gè)值是 96M。當(dāng)然,還是可能會(huì)超過(guò)這個(gè)大小限制。
但不可能讓它無(wú)限增長(zhǎng)是吧?所以 TiDB 做了一個(gè)最大值的限制,當(dāng) Region 的大小超過(guò)144M(默認(rèn)) 后,TiKV 會(huì)將其分裂成兩個(gè)或更多個(gè) Region,以保證數(shù)據(jù)在各個(gè) Region 中的均勻分布;同理,當(dāng)某個(gè) Region 由于短時(shí)間刪除了大量的數(shù)據(jù)之后,會(huì)變的比其他 Region 小很多,TiKV 會(huì)將比較小的兩個(gè)相鄰的 Region 合并。 大致的存儲(chǔ)機(jī)制、高可用機(jī)制上面已經(jīng)簡(jiǎn)單介紹了。 但其實(shí)上面還遺留一了比較大的問(wèn)題。大家可以結(jié)合上面的圖思考,一條查詢語(yǔ)句過(guò)來(lái),TiDB Server 解析了之后,它是怎么知道自己要找的數(shù)據(jù)在哪個(gè) Region 里?這個(gè) Region 又在哪個(gè) TiKV 上? 難道要遍歷所有的 TiKV 節(jié)點(diǎn)?用腳想想都不可能這么完。剛剛講到多副本,除了要知道提供讀、寫服務(wù)的 Leader Replica 所在的 TiKV,還需要知道其余的 Follower Replica 都分別在哪個(gè)實(shí)例等等。 6.PD這就需要引入 PD 了,有了 PD 「存儲(chǔ)相關(guān)的細(xì)節(jié)」那幅圖就會(huì)變成這樣: PD 是個(gè)啥?其全名叫 Placement Driver,用于管理整個(gè)集群的元數(shù)據(jù),你可以把它當(dāng)成是整個(gè)集群的控制節(jié)點(diǎn)也行。PD 集群本身也支持高可用,至少由 3 個(gè)節(jié)點(diǎn)組成。舉個(gè)對(duì)等的例子應(yīng)該就好理解了,你可以把 PD 大概理解成 Zookeeper,或者 RocketMQ 里的 NameServer。Zookeeper 不必多說(shuō),NameServer 是負(fù)責(zé)管理整個(gè) RocketMQ 集群的元數(shù)據(jù)的組件。
這個(gè)根據(jù)數(shù)據(jù)狀態(tài)進(jìn)行調(diào)度,具體是指啥呢? 7.調(diào)度舉個(gè)例子,假設(shè)每個(gè) Raft Group 需要始終保持 3 個(gè)副本,那么當(dāng)某個(gè) Raft Group 的 Replica 由于網(wǎng)絡(luò)、機(jī)器實(shí)例等原因不可用了,Replica 數(shù)量下降到了 1 個(gè),此時(shí) PD 檢測(cè)到了就會(huì)進(jìn)行調(diào)度,選擇適當(dāng)?shù)臋C(jī)器補(bǔ)充 Replica;Replica 補(bǔ)充完后,掉線的又恢復(fù)了就會(huì)導(dǎo)致 Raft Group 數(shù)量多于預(yù)期,此時(shí) PD 會(huì)合理的刪除掉多余的副本。 一句話概括上面描述的特性:PD 會(huì)讓任何時(shí)候集群內(nèi)的 Raft Group 副本數(shù)量保持預(yù)期值。
或者,當(dāng) TiDB 集群進(jìn)行存儲(chǔ)擴(kuò)容,向存儲(chǔ)集群新增 TiKV 節(jié)點(diǎn)時(shí),PD 會(huì)將其他 TiKV 節(jié)點(diǎn)上的 Region 遷移到新增的節(jié)點(diǎn)上來(lái)。 或者,Leader Replica 掛了,PD 會(huì)從 Raft Group 的 Replica 中選舉出一個(gè)新的 Leader。 再比如,熱點(diǎn) Region 的情況,并不是所有的 Region 都會(huì)被頻繁的訪問(wèn)到,PD 就需要對(duì)這些熱點(diǎn) Region 進(jìn)行負(fù)載均衡的調(diào)度。 總結(jié)一下 PD 的調(diào)度行為會(huì)發(fā)現(xiàn),就 3 個(gè)操作:
了解完了調(diào)度的操作,我們?cè)僬w的理解一下調(diào)度的需求,這點(diǎn) TiDB 的官網(wǎng)有很好的總結(jié),我把它們整理成腦圖供大家參考: 大多數(shù)點(diǎn)都還好,只是可能會(huì)對(duì)「控制負(fù)載均衡的速度」有點(diǎn)問(wèn)題。因?yàn)?TiDB 集群在進(jìn)行負(fù)載均衡時(shí),會(huì)進(jìn)行 Region 的遷移,可以理解為跟 Redis 的 Rehash 比較耗時(shí)是類似的問(wèn)題,可能會(huì)影響線上的服務(wù)。 8.心跳PD 而要做到調(diào)度這些決策,必然需要掌控整個(gè)集群的相關(guān)數(shù)據(jù),比如現(xiàn)在有多少個(gè) TiKV?多少個(gè) Raft Group?每個(gè) Raft Group 的 Leader 在哪里等等,這些其實(shí)都是通過(guò)心跳機(jī)制來(lái)收集的。 在 NameServer 中,所有的 RocketMQ Broker 都會(huì)將自己注冊(cè)到 NameServer 中,并且定時(shí)發(fā)送心跳,Broker 中保存的相關(guān)數(shù)據(jù)也會(huì)隨心跳一起發(fā)送到 NameServer 中,以此來(lái)更新集群的元數(shù)據(jù)。 PD 和 TiKV 也是類似的操作,TiKV 中有兩個(gè)組件會(huì)和 PD 交互,分別是:
PD 通過(guò)心跳來(lái)收集數(shù)據(jù),更新維護(hù)整個(gè)集群的元數(shù)據(jù),并且在心跳返回時(shí),將對(duì)應(yīng)的「調(diào)度指令」返回。
Store(即 TiKV 節(jié)點(diǎn)本身)心跳會(huì)帶上當(dāng)前節(jié)點(diǎn)存儲(chǔ)的相關(guān)數(shù)據(jù),例如磁盤的使用狀況、Region 的數(shù)量等等。通過(guò)上報(bào)的數(shù)據(jù),PD 會(huì)維護(hù)、更新 TiKV 的狀態(tài),PD 用 5 種狀態(tài)來(lái)標(biāo)識(shí) TiKV 的存儲(chǔ),分別是:
Raft Leader 則更多的是上報(bào)當(dāng)前某個(gè) Region 的狀態(tài),比如當(dāng)前 Leader 的位置、Followers Region 的數(shù)量、掉線 Follower 的個(gè)數(shù)、讀寫速度等,這樣 TiDB Server 層在解析的時(shí)候才知道對(duì)應(yīng)的 Leader Region 的位置。 |
|