我司 Engineering VP 申礫在 TiDB DevCon 2019 上分享了 TiDB 產品進化過程當中的思考與將來規劃。本文爲演講實錄上篇,重點回顧了 TiDB 2.1 的特性,並分享了咱們對「如何作一個好的數據庫」的見解。
<center>我司 Engineering VP 申礫</center>數據庫
感謝這麼多朋友的到場,今天我會從咱們的一些思考的角度來回顧過去一段時間作了什麼事情,以及將來的半年到一年時間內將會作什麼事情,特別是「咱們爲何要作這些事情」。安全
TiDB 這個產品,咱們從 2015 年年中開始作,作到如今,三年半,將近四年了,從最先期的 Beta 版的時候就開始上線,到後來 RC 版本,最後在 2017 年終於發了 1.0,開始鋪了一部分用戶,到 2.0 的時候,用戶數量就開始漲的很是快。而後咱們最近發了 2.1,在 2.1 以後,咱們也和各類用戶去聊,跟他們聊一些使用的體驗,有什麼樣的問題,包括對咱們進行吐嘈。咱們就在這些實踐經驗基礎之上,設計了 3.0 的一些特性,以及咱們的一些工做的重點。如今咱們正在朝 3.0 這個版本去演進,到今天早上已經發了 3.0 Beta 版本。網絡
首先咱們來說 2.1,2.1 是一個很是重要的版本,這個版本咱們吸收了不少用戶的使用場景中看到的問題,以及特別多用戶的建議。在這裏我跟你們聊一聊它有哪些比較重要的特性。併發
<center>圖 1 TiDB 2.1 新增重要功能</center>負載均衡
首先咱們兩個核心組件:存儲引擎和計算引擎,在這兩方面,咱們作了一些很是重要的改進,固然這些改進有多是用戶看不到的。或者說這些改進其實咱們是不但願用戶能看到的,一旦你看到了,注意到這些改進的話,說明你的系統遇到這些問題了。框架
你們都知道 Raft 會有 Leader 和 Follower 這兩個概念,Leader 來負責讀寫,Follower 來做爲 Backup,而後隨時找機會成爲新的 Leader。若是你想加一個新的節點,好比說在擴容或者故障恢復,新加了一個 Follower 進來,這個時候 Raft Group 有 4 個成員, Leader、Follower 都是 Voter,都可以在寫入數據時候對日誌進行投票,或者是要在成員變動的時候投票的。這時一旦發生意外狀況,好比網絡變動或者出現網絡分區,假設 2 個被隔離掉的節點都在一個物理位置上,就會致使 4 個 Voter 中 2 個不可用,那這時這個 Raft Group 就不可用了。分佈式
你們可能以爲這個場景並不常見,可是若是咱們正在作負載均衡調度或者擴容時,一旦出現這種狀況,就頗有可能影響業務。因此咱們加了 Learner 這個角色,Learner 的功能也是咱們貢獻給 etcd 這個項目的。有了 Learner 以後,咱們在擴容時不會先去加一個 Follower(也就是一個 Voter),而是增長一個 Learner 的角色,它不是 Voter,因此它只會同步數據不會投票,因此不管在作數據寫入仍是成員變動的時候都不會算上它。當同步完全部數據時(由於數據量大的時候同步時間會比較長),拿到全部數據以後,再把它變成一個 Voter,同時再把另外一個咱們想下線的 Follower 下掉就行了。這樣就能極大的縮短同時存在 4 個 Voter 的時間,整個 Raft Group 的可用性就獲得了提高。工具
<center>圖 2 TiDB 2.1 - Raft Learner</center>測試
其實增長 Learner 功能不僅是出於提高 Raft Group 可用性,或者說出於安全角度考慮,實際上咱們也在用 Learner 來作更多的事情。好比,咱們能夠隨便去加 Learner,而後把 Learner 變成一個只讀副本,不少很重的分析任務就能夠在 Learner 上去作。TiFlash 這個項目其實就是用 Learner 這個特性來增長只讀副本,同時保證不會影響線上寫入的延遲,由於它並不參與寫入的時候投票。這樣的好處是第一不影響寫入延遲,第二有 Raft 實時同步數據,第三咱們還能在上面快速地作很複雜的分析,同時線上 OLTP 業務有物理上的隔離。優化
除了 Learner 以外,咱們 2.1 中默認開啓了 PreVote 這個功能。
咱們考慮一種意外狀況,就是在 Raft group 中出現了網絡隔離,有 1 個節點和另外 2 個節點隔離掉了,而後它如今發現「我找不到 Leader 了,Leader 可能已經掛掉了」,而後就開始投票,不斷投票,可是由於它和其餘節點是隔離開的,因此沒有辦法選舉成功。它每次失敗,都會把本身的 term 加 1,每次失敗加 1,網絡隔離發生一段時間以後,它的 term 就會很高。當網絡分區恢復以後,它的選舉消息就能發出去了,而且這個選舉消息裏面的 term 是比較高的。根據 Raft 的協議,當遇到一個 term 比較高的時候,可能就會贊成發起選舉,當前的 Leader 就會下臺來參與選舉。可是由於發生網絡隔離這段時間他是沒有辦法同步數據的,此時它的 Raft Log 必定是落後的,因此即便它的 term 很高,也不可能被選成新的 Leader。因此這個時候通過一次選舉以後,它不會成爲新 Leader,只有另外兩個有機會成爲新的 Leader。
你們能夠看到,這個選舉是對整個 Raft Group 形成了危害:首先它不可能成爲新的 Leader,第二它把原有的 Leader 趕下臺了,而且在這個選舉過程當中是沒有 Leader 的,這時的 Raft Group 是不能對外提供服務的。雖然這個時間會很短,但也可能會形成比較大的抖動。
<center>圖 3 TiDB 2.1 - Raft PreVoter</center>
因此咱們有了 PreVote 這個特性。具體是這樣作的(如圖 3):在進行選舉以前,先用 PreVote 這套機制來進行預選舉,每一個成員把本身的信息,包括 term,Raft Log Index 放進去,發給其它成員,其它成員有這個信息以後,認爲「我能夠選你爲 Leader」,纔會發起真正的選舉。
有了 PreVote 以後,咱們就能夠避免這種大規模的一個節點上不少數據、不少 Raft Group、不少 Peer 的狀況下忽然出現網絡分區,在恢復以後形成大量的 Region 出現選舉,致使整個服務有抖動。 所以 PreVote 能極大的提高穩定性。
固然除了 Raft 這幾個改進以外,TiDB 2.1 中還有一個比較大的改進,就是在 DDL 模塊。這是咱們 2.1 中一個比較顯著的特性。
<center>圖 4 TiDB 2.1 以前的 DDL 機制</center>
在 2.1 以前的 DDL 整套機制是這樣的(如圖 4):用戶將 DDL 提交到任何一個 TiDB Server,發過來一個 DDL 語句,TiDB Server 通過一些初期的檢查以後會打包成一個 DDL Job,扔到 TiKV 上一個封裝好的隊列中,整個集羣只有一個 TiDB Server 會執行 DDL,並且只有一個線程在作這個事情。這個線程會去隊列中拿到隊列頭的一個 Job,拿到以後就開始作,直到這個 Job 作完,即 DDL 操做執行完畢後,會再把這個 Job 扔到歷史隊列中,而且標記已經成功,這時 TiDB Sever 能感知到這個 DDL 操做是已經結束了,而後對外返回。前面的 Job 在執行完以前,後面的 DDL 操做是不會執行的,於是會形成一個狀況: 假設前面有一個 AddIndex,好比在一個百億行表上去 AddIndex,這個時間是很是長的,後面的 Create Table 是很是快的,但這時 Create Table 操做會被 AddIndex 阻塞,只有等到 AddIndex 執行完了,纔會執行 Create Table,這個給有些用戶形成了困擾,因此咱們在 TiDB 2.1 中作了改進。
<center>圖 5 TiDB 2.1 - DDL 機制</center>
在 TiDB 2.1 中 DDL 從執行層面分爲兩種(如圖 5)。一種是 AddIndex 操做,即回填數據(就是把全部的數據掃出來,而後再填回去),這個操做耗時是很是長的,並且一些用戶是線上場景,併發度不可能調得很高,由於在回寫數據的時候,可能會對集羣的寫入形成壓力。
另一種是全部其餘 DDL 操做,由於無論是 Create Table 仍是加一個 Column 都是很是快的,只會修改 metadata 剩下的交給後臺來作。因此咱們將 AddIndex 的操做和其餘有 DDL 的操做分紅兩個隊列,每種 DDL 語句按照分類,進到不一樣隊列中,在 DDL 的處理節點上會啓用多個線程來分別處理這些隊列,將比較慢的 AddIndex 的操做交給單獨的一個線程來作,這樣就不會出現一個 AddIndex 操做阻塞其餘全部 Create Table 語句的問題了。
這樣就提高了系統的易用性,固然咱們下一步還會作進一步的並行, 好比在 AddIndex 時,能夠在多個表上同時 AddIndex,或者一個表上同時 Add 多個 Index。咱們也但願可以作成真正並行的一個 DDL 操做。
除了剛剛提到的穩定性和易用性的提高,咱們在 TiDB 2.1 中,也對分析能力作了提高。咱們在聚合的算子上作了兩點改進。 第一點是對整個聚合框架作了優化,就是從一行一行處理的聚合模式,變成了一批一批數據處理的聚合模式,另外咱們還在哈希聚合算子上作了並行。
<center>圖 6 TiDB 2.1 - Parallel Hash Aggregation</center>
爲何咱們要優化聚合算子?由於在分析場景下,有兩類算子是很是重要的,是 Join 和聚合。Join 算子咱們以前已經作了並行處理,而 TiDB 2.1 中咱們進一步對聚合算子作了並行處理。在哈希聚合中,咱們在一個聚合算子裏啓用多個線程,分兩個階段進行聚合。這樣就可以極大的提高聚合的速度。
<center>圖 7 TiDB 2.0 與 TiDB 2.1 TPC-H Benchmark 對比</center>
圖 7 是 TiDB 2.1 發佈的時候,咱們作了一個 TPC-H Benchmark。實際上全部的 Query 都有提高,其中 Q17 和 Q18 提高最大。由於在 TiDB 2.0 測試時,Q1七、Q18 仍是一個長尾的 Query,分析以後發現瓶頸就在於聚合算子的執行。整個機器的 CPU 並不忙,但就是時間很長,咱們作了 Profile 發現就是聚合的時間太長了,因此在 TiDB 2.1 中,對聚合算子作了並行,而且這個並行度能夠調節。
TiDB 2.1 發佈的時咱們還發布了兩個工具,分別叫 TiDB-DM 和 TiDB-Lightning。
TiDB-DM 全稱是 TiDB Data Migration,這個工具主要用來把咱們以前的 Loader 和以及 Syncer 作了產品化改造,讓你們更好用,它可以作分庫分表的合併,可以只同步一些表中的數據,而且它還可以對數據作一些改寫,由於分庫分表合併的時候,數據合到一個表中可能會衝突,這時咱們就須要一種很是方便、可配置的工具來操做,而不是讓用戶手動的去調各類參數。
TiDB-Lightning 這個工具是用來作全量的數據導入。以前的 Loader 也能夠作全量數據導入,可是它是走的最標準的那套 SQL 的流程,須要作 SQL 的解析優化、 兩階段提交、Raft 複製等等一系列操做。可是咱們以爲這個過程能夠更快。由於不少用戶想遷移到 TiDB 的數據不是幾十 G 或者幾百 G,而是幾 T、幾十 T、上百 T 的數據,經過傳統導入的方式會很是慢。如今 TiDB-Lightning 能夠直接將本地從 MySQL 或者其餘庫中導出的 SQL 文本,或者是 CSV 格式的文件,直接轉成 RocksDB 底層的 SST file ,而後再注入到 TiKV 中,加載進去就導入成功了,可以極大的提高導入速度。固然咱們還在不斷的優化,但願這個速度能不斷提高,將 1TB 數據的導入,壓縮到一兩個小時。這兩個工具,有一部分用戶已經用到了(而且已經正式開源)。
咱們有至關多的用戶正在使用 TiDB,咱們在不少的場景中見到了各類各樣的 Case,甚至包括機器壞掉甚至連續壞掉的狀況。見了不少場景以後,咱們就在想以後如何去改進產品,如何去避免在各類場景中遇到的「坑」,因而咱們在更深刻地思考一個問題:如何作一個好的數據庫。由於作一個產品其實挺容易的,一我的兩三個月也能搞一套數據庫,無論是分庫分表,仍是相似於在 KV 上作一個 SQL,甚至作一個分佈式數據庫,都是可能在一個季度甚至半年以內作出來的。可是要真正作一個好的數據庫,作一個成熟的數據庫,作一個能在生產系統中大規模使用,而且可以讓用戶本身玩起來的數據庫,其實裏面有很是多工做要作。
首先數據庫最基本的是要「有用」,就是能解決用戶問題。而要解決用戶問題,第一點就是要知道用戶有什麼樣的問題,咱們就須要跟各種用戶去聊,看用戶的場景,一塊兒來分析,一塊兒來得到使用場景中真實存在的問題。因此最近咱們有大量的同事,無論是交付的同事仍是研發的同事,都與用戶作了比較深刻的訪談,聊聊用戶在使用過程當中有什麼的問題,有什麼樣的需求,用戶也提供各類各樣的建議。咱們但願 TiDB 可以很好的解決用戶場景中存在的問題,甚至是用戶本身暫時尚未察覺到的問題,進一步的知足用戶的各類需求。
第二點是「易用性」。就好像一輛車,手動擋的你們也能開,但其實你們如今都想開自動擋。咱們但願咱們的數據庫是一輛自動擋的車,甚至將來是一輛無人駕駛的車,讓用戶不須要關心這些事情,只須要專一本身的業務就行了。因此咱們會不斷的優化現有的解決方案,給用戶更多更好的解決方案,下一步再將這些方案自動化,讓用戶用更低的成本使用咱們的數據庫。
最後一點「穩定性」也很是重要,就是讓用戶不會用着用着擔驚受怕,好比半夜報警之類的事情。並且咱們但願 TiDB 能在大規模數據集上、在大流量上也能保持穩定。
未完待續...
下篇將於明日推送,重點介紹 TiDB 3.0 Beta 在穩定性、易用性和功能性上的提高,以及 TiDB 在 Storage Layer 和 SQL Layer 方面的規劃。