TiDB Lab 誕生記 | TiDB Hackathon 優秀專案分享
本文由 紅鳳凰粉鳳凰粉紅鳳凰隊 的成員主筆,他們的專案 TiDB Lab 在本屆 TiDB Hackathon 2018 中獲得了二等獎。TiDB Lab 為 TiDB 培訓體系增加了一個可以動態觀測 TiDB / TiKV / PD 細節的動畫教學 Lab,讓使用者可以一邊進行真實操作一邊觀察元件之間的變化,例如 SQL 的解析,Region 的變更等等,從而生動地理解 TiDB 的工作原理。

專案簡介
1. 簡介
TiDB Lab,全稱 TiDB Laboratory ,是一個集 TiDB 叢集狀態的線上實時視覺化與互動式教學的平臺。使用者可以一邊對 TiDB 叢集各個元件 TiKV、TiDB、PD 進行各種操作,包括上下線、啟動關閉、遷移資料、插入查詢資料等,一邊在 TiDB Lab 上以動畫形式觀察操作對叢集的影響,例如資料是怎麼流動的,Region 副本在什麼情況下發生了變更等等。通過 TiDB Lab 這種對操作進行視覺化反饋的互動模式,使用者可以快速且生動地理解 TiDB 內部原理。
2.功能
- 實時動態展示 TiDB、TiKV 節點的新增、啟動與關閉。
- 實時動態展示 TiDB 收到 SQL 後,物理運算元將具體請求傳送給某些 TiKV Region Leader 並獲取資料的過程。
- 實時動態展示各個 TiKV 例項上 Region 副本狀態的變化,例如新增、刪除、分裂。
- 實時動態展示各個 TiKV 例項上 Region 副本內的資料量情況。
- 瀏覽叢集事件歷史(事件指上述四條功能所展示的各項內容)並檢視事件的詳細情況,包括事件的具體資料內容、SQL Plan 等等。
- 按 TiDB、TiKV 或 Region 過濾事件歷史。
- 對事件歷史進行時間穿梭:回到任意事件發生時刻重新觀察當時的叢集狀態,或按事件單步重放觀察叢集狀態的變化。
- 線上獲取常用運維操作的操作指南。
3.願景
我們其實為 TiDB Lab 規劃了更大的願景,但由於 Hackathon 時間關係,還來不及實現。我們希望能實現 TiDB Lab + TiDB 生態元件的沙盒,從而在 TiDB Lab 線上平臺上直接提供命令執行與 SQL 執行功能。這樣使用者無需離開平臺,無需自行準備機器下載部署,就可以直接在平臺上根據提供的操作指南進行各類操作,並能觀察操作帶來的具體影響,形成操作與反饋的閉環,真正地實現零門檻瀏覽器線上教學。我們期望平臺能提供使用者以下的操作流程:
- 使用者獲得一個 TiDB Lab 賬戶並登入(考慮到沙盒是佔用實際資源的,需要通過賬戶許可來限制避免資源快速耗盡)。
- 使用者在 TiDB Lab 上獲得若干虛擬機器器的訪問許可權,每個機器處於同一內網並具有獨立 IP。這些虛擬機器器的實際實現是資源受限的虛擬機器或沙盒,因為作為教學實驗不需要佔用很多資源。
例如:平臺為使用者自動分配了 5 個 IP 獨立的沙盒的訪問許可權,地址為 192.168.0.1 ~ 192.168.0.5。
- 使用者在平臺上進行第零章「架構原理」的學習。平臺提供了一個預設的拓撲部署,使用者可以在平臺提供的線上 SQL Shell 中進行資料的插入、刪除、更新等操作。使用者通過平臺觀察到 SQL 是如何對應到 TiKV 儲存節點上的,以及資料是怎麼切分到不同 Region 的等等。
- 使用者在平臺上進行第一章「TiDB 部署」的學習,瞭解到可以通過 ansible 進行部署。教學樣例是一個典型的 TiDB + TiKV 三副本部署。對於這個教學樣例,平臺告知使用者 inventory.ini 具體內容應當寫成什麼樣子。使用者可以在平臺提供的線上 Terminal 上修改 inventory 檔案,並執行部署與叢集啟動命令。部署和啟動均能在平臺上實時反饋視覺化。
- 使用者繼續進行後續章節學習,例如「TiDB 單一服務啟動與關閉」。使用者在視覺化介面上點選某個剛才已經部署出來的節點,可以瞭解啟動或關閉單個 TiDB 的命令。使用者可以在平臺提供的線上 Terminal 上執行這些命令,嘗試啟動或關閉單一 TiDB。
- 使用者繼續學習基礎運維操作,例如「TiKV 擴容」。平臺告知 inventory.ini 應當如何進行修改,使用者可以根據指南在線上 Terminal 上進行實踐,並通過視覺化介面觀察擴容的過程,例如其他節點上的副本被逐漸搬遷到新節點上。

前期準備
- 團隊
我們團隊有三個人,是一個 PingCAP 同學與 TiDB 社群小夥伴錢同學的混合組隊,其中 PingCAP 成員分別來自 TiKV 組與 OLAP 組。我們本著搞事情的想法,團隊取名叫「 紅鳳凰粉鳳凰粉紅鳳凰」 ,想圍觀主持人念團隊名稱(然而機智的主持人小姐姐讓我們自報團隊名稱)。
2. 原計劃:幹掉 gRPC
鑑於從報名開始直到 Hackathon 正式開始前幾小時,我們都在為原計劃做準備,因此值得詳細說一下…
我們一開始規劃的 Hackathon 專案是換掉 TiKV、TiDB 之間的 RPC 框架 gRPC,原因有幾個:
- 一是發現 TiKV、TiDB 中 gRPC 經常佔用了大量 CPU,尤其是在請求較多但很簡單的 benchmark 場景中經常比 Coprocessor 這塊兒還高,這在客戶機器 CPU 資源比較少的情況下是效能瓶頸;
- 二是發現 gRPC 效能一般,在各類常用 RPC 框架的效能測試中 gRPC 經常是墊底水平;
- 三是 gRPC 主要設計用於使用者產品與 Google 服務進行通訊,因而考慮到了包括負載均衡友好、流量控制等方面,但對 TiKV 與 TiDB 這類內部通訊來說這些都是用不上的功能,為此犧牲的效能是無謂的開銷;
- 另外近期 TiKV 內部有一個實驗是將多個 RPC 請求 batch 到一起再發送(當然處理時候再拆開一個一個執行原來的 handler),效能可以瞬間提高一倍以上,這也從側面說明了 gRPC 框架自身開銷很大,因為使用者側請求總量是一致的,處理模式也是一致的,唯一的區別就是 RPC 框架批量傳送或一條一條傳送。
我們早在幾周前就開始寫簡單 Echo Server 進行可行性驗證和效能測試,是以下三個方面的正交:
其中 TiKV 側調研了 Rust 和 C++ 的服務端實現,原因是 Rust 可以通過 binding 方式呼叫 C++ 服務端。而 TiDB 側客戶端實現不包含 C++ 的原因是 Golang 進行 C / C++ FFI 效能很差,因此可以直接放棄 C/C++ 包裝一層 binding 用於 TiDB 的想法。最後測試下來,有以下結果:
- CapnProto:序列化效能很高,但其 RPC 效能沒有很突出。最重要的是,CapnProto 的 Golang Client 實現有 bug,並不能穩定地與 Rust 或 C++ 的 Server 進行 RPC 互動。作者回復說這是一個已知缺陷,涉及重構。這對於 Hackathon 來說是一個致命的問題,我們並沒有充足的時間解決這個問題,直接導致我們放棄這個方案。
- brpc over gRPC:可以實現使用 brpc C++ 客戶端 & gRPC Golang 服務端配合(注:brpc 沒有 Golang 的實現)。但這個方案本質只是替換服務端的實現,並沒有替換協議,並不徹底,我們不是特別喜歡。另外在這個換湯不換藥的方案下,測試下來效能的提升有限,且隨著 payload 越大會越小。我們最終覺得作為一個 Hackathon 專案如果僅有有限的效能提升(雖然可以在展示的時候掩蓋缺陷只展示優點),那麼意義不是很大,最終無法用於產品,因而放棄。
- 裸寫:我們三個成員都不是這方面的老司機,裸寫大概是寫不完的。。
3. 新計劃:做一個用於培訓的視覺化
在 Hackathon 開始的前一個晚上,我們決定推翻重來,於是 brain storm 了幾個想法,最後覺得做一個用於教學的視覺化比較可行,並且具有比較大的實際意義。另外,這個新專案「叢集視覺化」相比原專案「換 RPC」來說更適合 Hackathon,主要在於:
- 具有圖形化介面,容易拿獎可以直觀地展現成果。
- 並不是一個「非零即一」的任務。新專案有很多子功能,可以逐一進行實現,很穩妥,且不像老專案那樣只有換完才知道效果。
我們繼續在這個「視覺化」的想法上進行了進一步的思考,想到如果可以做成線上教學的模式,則可以進一步擴充套件其專案意義,形成一個完整的線上教學體系,因此最終決定了專案的 scope。
具體實現
在 TiKV、TiDB 與 PD 中各個關鍵路徑上將發生的具體「事件」記錄下來,並在前端進行一一視覺化。
- 事件
我們將 TiDB Lab 進行視覺化所需要的訊號稱為「事件」,並規劃了以下「事件」:
- Ansible 事件:Ansible 部署 TiDB
- Ansible 事件:Ansible 部署 TiKV
- TiDB 事件:TiDB 啟動
- TiDB 事件:TiDB 關閉
- TiDB 事件:TiDB 收到一條 SQL
- TiKV 事件:TiKV 啟動
- TiKV 事件:TiKV 關閉
- TiKV 事件:TiKV 收到一條 KvGet 請求
- TiKV 事件:TiKV 收到一條 PreWrite / Commit 請求
- TiKV 事件:TiKV 收到一條 Coprocessor 請求
- TiKV 事件:TiKV Region Peer 建立
- TiKV 事件:TiKV Region Peer 刪除
- TiKV 事件:TiKV Region 分裂
- TiKV 事件:TiKV Region Snapshot 複製
- TiKV 事件:TiKV Region 資料量發生顯著變化
最後,由於時間關係、技術難度和視覺化需要,實際實現的是以下事件:
- TiDB 事件:TiDB 啟動,若首次啟動認為是新部署
- TiDB 事件:TiDB 關閉
- TiDB 事件:TiDB 收到 SQL 併發起 KvGet 讀請求
- TiDB 事件:TiDB 收到 SQL 併發起 PreWrite / Commit 寫入請求
- TiDB 事件:TiDB 收到 SQL 併發起 Coprocessor 讀請求
- TiKV 事件:TiKV 啟動,若首次啟動認為是新部署
- TiKV 事件:TiKV 關閉
- PD 事件:TiKV Region Peer 建立(通過 Region 心跳實現)
- PD 事件:TiKV Region Peer 刪除(通過 Region 心跳實現)
- PD 事件:TiKV Region 分裂(通過 Region 心跳實現)
- PD 事件:TiKV Region 資料量發生顯著變化(通過 Region 心跳實現)
2. 視覺化
視覺化部分由前端(lab-frontend)和事件收集服務(lab-gateway)組成。
事件收集服務是一個簡單的 HTTP Server,各個元件通過 HTTP Post 方式告知事件,事件收集服務將其通過 WebSocket 協議實時傳送給前端。事件收集服務非常簡單,使用的是 Node.js 開發,基於 ExpressJs 啟動 HTTP Server 並基於 SocketIO 實現與瀏覽器的實時通訊。ExpressJs 收到事件 JSON 後將其通過 SocketIO 進行廣播,總程式碼僅僅十幾行。
視覺化前端採用 Vue 實現,動畫使用 animejs 和 CSS3。
- 通過模板實現的視覺化
一部分事件通過「由事件更新叢集狀態資料 – 由叢集狀態通過 Vue 渲染模板」進行視覺化。
這類視覺化是最簡單的,以 TiDB 啟動與否為例,TiDB 的啟動與否在介面上呈現為一個標籤顯示為「Started」或「Stopped」,那麼就是一個傳統的 Vue MVVM 流程:
- 資料變數 instances.x.online 代表 TiDB x 是否已啟動。
- 收到 TiDB Started 事件後,更新 instances.x.online = true 。
- 收到 TiDB Stopped 事件後,更新 instances.x.online = false 。
- 前端模板上,根據 instances.x.online 渲染成 Started 或 Stopped 對應的介面。
這類視覺化的動畫採用的是 CSS3 動畫。由於 Region Peer 位置是由 left, top CSS 屬性給出的,因此為其加上 transition,即可實現 Region Peer 在螢幕上顯示的位置改變的動畫。位置改變會主要發生在分裂時,分裂時 Region 列表中按順序會新增一個,那麼後面各個 Region 都要向後移動(或換到下一行等)。

最後,使用 Vue Group Transition 功能,即可為 Region Peer 的新增與刪除也加入動畫效果。
- 通過動畫實現的視覺化
另一部分事件並不反應為一個持久化 DOM 的變化,例如 TiDB 收到 SQL 併發請求到某個具體 TiKV 上 Region peer 的事件,在前端展示為一個 TiDB 節點到 TiKV Region Peer 的過渡動畫。動畫開始前和動畫結束後,DOM 沒有什麼變化,動畫是一個臨時的可視元素。這類動畫通過 animejs 實現。


- 時間穿梭
時間穿梭是一個在目前前端框架中提供的很時髦的功能,我們準備借鑑一波。主要包括:1. 回退到任意一個歷史事件發生的時刻展示叢集的狀態;2. 從當前事件開始往後進行單步視覺化重現。
時間穿梭的本質是需要實現兩個基礎操作:
- 對於單一事件實現正向執行,即事件發生後,更新對應叢集資料資訊(如果採用「通過模板實現的視覺化」),或建立臨時動畫 DOM(如果採用「通過動畫實現的視覺化」)。另外允許跳過「通過動畫實現的視覺化」這一步。
- 對於單一事件實現反向執行,即撤銷這個事件造成的影響。對於「通過模板實現的視覺化」,我們需要根據事件內容反向撤銷它對叢集資料資訊的修改。對於「通過動畫實現的視覺化」,我們什麼都不用做。
時間穿梭的功能可以通過組合這兩個基礎操作實現。
- 回退到任意歷史事件發生時刻:若想要前往的事件早於當前呈現的事件,則對於這期間的事件逐一進行反向執行。若想要前往的事件晚於當前呈現的事件,則進行無動畫的正向執行。
- 從當前事件開始單步視覺化:執行一次有動畫的正向執行。
- 實時展示新事件:執行一次有動畫的正向執行。

3. TiDB 事件收集
TiDB 的歷史事件收集略為 Hack。由於需要過濾任何非使用者發起的查詢(類似 GC 或者 meta 查詢會由背景協程頻繁發起打擾使用體驗),因此在使用者連結入口處添加了 context 標記一路攜帶到執行層,再修改相應的協程同步資料結構新增需要轉發的標記資訊。比較麻煩的是類似 Point Get 這樣介面允許攜帶資訊非常少的呼叫,只好將標記位編碼進 Key 本身了。Plan 的視覺化其實並沒有花多少功夫,因為找到 TiDB 本身已經做了類似的功能,我們無非只是將這塊程式碼直接偷來了。
4. PD 事件收集
原本不少事件希望在 TiKV 端完成偵聽,不過顯然 KV 一小時編譯一次的效率無法滿足 Hackathon 中多次試錯的需要。因此我們改為在 PD 中偵測心跳和彙報事件。其實並沒有什麼神祕,在原本 PD 自己檢查 Region 變更的程式碼拆分成 Region 和 Peer 變更:每次 PD 接到 Region 心跳會在 PD Cache 中進行 Version 和 confVer 變更的檢測,主要涉及 Peer 的增加和減少等。而 Region Split 會單獨由 RegionSplitReport 進行彙報,這裡也會做一次 Hook。另外就是每次心跳會檢查是否有未上報給 Lab 的 Region 資訊,如果有就轉換成 Peer 資訊進行補發。
5. TiKV 事件收集
如上,原本我們計劃了很多 TiKV 事件,但由於開發機器配置不佳,每次修改都要等待一小時進行一次編譯,考慮到 Hackathon 上時間緊迫,因此最終大大縮減了 TiKV 上收集的事件數量,改為只收集啟動和停止。基本架構是事件發生的時候,事件非同步傳送給一個 Channel,Channel 的另一端有一個非同步的 worker 負責不斷處理各個事件並通過 Hyper HTTP Client 傳送出去。這個流程其實與 TiKV 中彙報 PD 事件有些類似,只是事件內容和彙報目標不一樣。
感悟
以下文字 by 馬曉宇 @ OLAP Team, PingCAP
“由於比較能調侃乾的活相對少一些,所以大王要我來巡山寫感悟。”
就像隊長說的,這個專案原本是希望做一個 bRPC 替換 gRPC 的試驗,只是由於種種原因臨到 Hackathon 前夜我們才確定這是個大概率翻車的點子。於是乎我們只好在酒店裡抓耳撓腮,一邊討論可能的補救措施。
參加過、圍觀過幾次 Hackathon,見過現場 Demo 效果最震撼的一次其實並不是一個技術上最優秀的作品,但是它的確贏得了大獎。那是一個腦洞奇大的點子:用 Kinect 體感加上 DirectX 的全 3D 展示做的網路流量實時展示;程式根據實時資料(舉辦方提供了實時網路測量統計資訊)聚合顯示不同粗細的炫酷弧光特效,而演示者則用手勢操控地球模型的旋轉。
於是在完全不瞭解大家是否能搞定前端的情況下,我們還是很輕率地決定了要做個重前端的專案。至於展示什麼?既然時間不夠,那麼秀一個需要生產上線的視覺化工具,翻車的概率就大的多,不如直接定位為 For Educational Purpose Only。而且大概是過度解讀了炫酷對於成功的重要性,因此隊長也輕率地決定了這個專案的基調是「極盡浮誇」。就在這樣友好且不靠譜的討論氛圍下,這個專案的策劃出爐了。
之後就是艱苦卓絕連綿無休的程式碼過程了。
龍毛二哥,曉峰和胡家屬的 ofollow,noindex">TiNiuB Team 就在我們對面開工。講道理這其實是我個人最喜歡的專案之一。第一天放學的時候,看著他們的進度其實我心裡虛得不行:看看別人家的專案,我們的絕對主力還在折騰 TiDB Logo 動畫,是我敢怒不敢言的狀態。

原來我想偷一下懶回家睡個覺啥的,就臨時改變主意繼續配合隊長努力幹活;錢同學也抱著「TiKV 一天只能 Build 24 次必須珍惜」的態度寫著人生的第一個 Rust 專案。
因此,這裡必須鳴謝龍哥他們對我們的鞭策。
事實證明,誤打誤撞但又深謀遠慮的浮誇戰略是有效的。Demo 的時候我很認真地盯著評委:在本專案最浮誇的 Logo 展示環節,大家的眼睛是發著光的,一如目睹了摩西分開紅海。我個人認為這個動畫 Logo 生生拉高了專案 50% 的評分。
只是具體要說感悟的話(似乎好多年沒寫感悟了呢),首先這次我們三個組員雖然有兩個是 PingCAP 員工,不過由於技能的缺口大於人力,因此負責的任務都不是自己所屬的模組:隊長是 TiKV 組的但是在寫前端,我是 OLAP 組的但是在改 PD 和 DB,錢同學也在做自己從來沒寫過的 Rust(參賽前一週簡單入門了一下),因此其實還蠻有挑戰的(笑)。然後偶爾寫寫自己不熟悉的語言,搞搞自己不熟悉的模組,會有一種別樣的新鮮刺激感,這大概就是所謂的路邊野花更香吧(嗯)?除此之外,Hackathon 更像一個大型社交活動,滿足了貓一樣孤僻的程式設計師群體被隱藏的社交欲,有利於碼農的身心發展,因此可以多搞搞 :) 。
回到我們專案本身的話,其實 TiDB 的原始碼閱讀或者其他介紹類文章其實並不能非常直觀地幫助一頭霧水的初學者理解這個系統。我們做這個專案的最大目的是能降低學習門檻,讓所有人能以非常直觀,互動的方式近距離理解她。所以希望這個專案能給大家帶來方便吧。
TiDB Hackathon 2018 共評選出六個優秀專案,本系列文章將由這六個專案成員主筆,分享他們的參賽經驗和成果。我們非常希望本屆 Hackathon 誕生的優秀專案能夠在社群中延續下去,感興趣的小夥伴們可以加入進來哦。