Golang In PingCAP

qiuyesuifeng發表於2016-10-11

隨著 Golang 在後端領域越來越流行,有越來越多的公司選擇 Golang 作為主力開發語言。本次 GopherChina Beijing 2016 大會上,看到 Golang 在各家公司從人工智慧到自動運維,從 Web 應用到基礎架構都發揮著越來越多的作用。可以說 Golang 在這幾年間,獲得了長足的進步。 PingCAP 是一家由幾名 Golang 粉絲建立的資料庫公司。在我們的日常工作中,除了對效能有苛刻要求的最底層儲存引擎外,大部分都是使用 Golang,算是 Golang 的重度使用者。我們從 Golang 語言以及社群中收益頗多,TiDB 在短短半年的時間內,從無到有,從默默無聞到廣泛關注,已經成長為 Golang 社群的明星專案。我們在這個過程中也積累了不少工程實踐經驗,這裡想和大家分享一下。

Why Golang?

網上已經有無數的文章描述 Golang 的優點,所以沒有必要一一列舉。我們選擇 Golang 並不是因為跟風或者是我們是 Golang 的粉絲,而是經過理性的分析和討論,認為 Golang 最適合我們的業務場景。

開發效率高

作為技術創業公司,我們期望維護一個精英技術團隊,人數不多,但是交付速度快、程式碼質量高。這樣我們需要一門高效的語言,Golang 在這方面令我們非常滿意。Golang 易於上手,有過其他語言經驗的人,很容易轉到 Golang。超強的表達能力、完備的標準庫以及大量成熟的第三方庫,使得我們可以專心於核心業務。自動記憶體管理,避免了 c/c++ 中的指標亂飛的情況,易於寫出正確的程式。從15年6月寫下第一行程式碼開始,到15年9月我們已經完成第一版的產品,並且達到可開源的要求。開源後我們從社群中獲得了不少有價值的反饋以及大量的第三方 Contributor。從 GopherChina 大會上,我們注意到除了大公司處理海量併發時會採用 Golang 外,越來越多的創業型公司也在使用 Golang,我想這和 Golang 的易於上手、開發效率高有很大關係。

併發友好

對於一個分散式資料庫,相比較延遲而言吞吐量是一個更關鍵指標。當然這裡並不是說延遲可以無限大,而是在保證延遲相對較低的情況下,儘可能的提高吞吐。TiDB 的設計目標是能響應海量的使用者請求,我們期望有一種低成本的方式同時處理多個使用者連線。同時資料庫內部的一些邏輯也要求在處理使用者請求的同時,還有大量的後臺執行緒在做自己的工作。 Golang 在這方面有天然的優勢,甚至可以說 Golang 就是一門為了併發而生語言。goroutine 和 channel 使得編寫併發的程式變得相當容易且自然,很多情況下完全不需要考慮鎖機制以及由此帶來的各種問題。單個 Go 應用也能有效的利用多個 CPU 核,並行執行的效能好。與此同時,Golang 執行的效能雖然不如 C/C++,但是還沒有數量級的差別,可以滿足對延遲的要求。

部署簡單

我們把系統部署簡單易用作為 TiDB 的一個重要的設計目標。我想部署和維護過其他分散式系統(比如 Hbase)的同學,對這一點一定深有感觸。 Golang 編譯生成的是一個靜態連結的可執行檔案,除了 glibc 外沒有其他外部依賴。這讓部署變得很方便。目標機器上只需要一個基礎的系統和必要的管理、監控工具,完全不需要操心應用所需的各種包、庫的依賴關係,大大減輕了維護的負擔。

Good Practice

在使用 Golang 的過程中,我們也獲得了一些很好的實踐經驗,包括語言使用上的,以及工程上的經驗。

重視單元測試

Golang 帶有一個簡單好用的單元測試框架,包括功能測試和效能測試。每個模組都能以非常簡單的方式進行測試,以驗證功能的正確性,並且避免後續被別人改錯。在做 Code Review 時,我們強制要求所有的改動必須有 test case,否則 PR 會被拒絕。對於效能關鍵的模組,我們會加上 bench test,每次改動後會觀察效能的變化。

重視 CI

資料庫是一個複雜的系統,單靠單元測試無法保證系統的正確性,我們需要大量的整合測試。受益於 MySQL 的生態,我們可以獲得大量可以直接用的測試資源,包括各種 ORM 自帶的測試、MySQL 自帶的測試、各種 MySQL 應用的測試。 TiDB 除了在提交 PR 時會做最基本的測試之外,還有十幾個整合測試隨時待命。我們在內部搭建了 jenkins 系統,每次程式碼有變動,都會自動構建這十幾個測試集。如果有任何一個 Fail 了,相關人員必須停下手中的工作,馬上去 Fix。另外 jenkins 也可以作為效能監測工具,每次提交後都會記錄下執行時間,可以和歷史記錄中的時間作比較,如果執行時間突然變長,需要立即解決。

重視程式碼質量

程式碼是技術型公司最重要的產品,而且我們又是一家以開源方式運作的技術公司,程式碼的質量相當於公司的招牌,我們在這方面花了很大的力氣。 我們制定了嚴格的 Code Review 制度。任何 PR 都需要有至少兩個maintainer 看過,並且認為改動 OK,給出 LGTM 後,才能合併進主幹。這兩個做 review 工作的人要保證看過、理解每一行程式碼,並且要到能獨立修改。否則提交 PR 的人需要給 reviewer 進行詳細的介紹,直到講懂為止。 另外我們還利用一些第三方工具來檢測程式碼質量。比如 GoReportCard,這個工具會分析程式碼中的潛在問題,如賦值過的變數在作用域內沒有被使用、函式過長、switch 分支過長、typo。專案在這裡面的排名在一定程度上反映了程式碼的質量。目前 TiDB 的程式碼質量被評為A+級別。

一切自動化

Go 自帶完善的工具鏈,大大提高了團隊協作的一致性。比如 gofmt 自動排版 Go 程式碼,很大程度上杜絕了不同人寫的程式碼排版風格不一致的問題。把編輯器配置成在編輯存檔的時候自動執行 gofmt,這樣在編寫程式碼的時候可以隨意擺放位置,存檔的時候自動變成正確排版的程式碼。此外還有 golint, govet 等非常有用的工具。TiDB 將 golint、govet 的檢查加入 Makefile,每次構建時,都會自動測試,這樣可以防止一些低階的錯誤被提交。

善於利用 Pprof

在系統效能調優或者是死鎖監測方面,一個 Inspector 機制能極大的提高效率。幸運的是 Golang 自帶 profile 工具,簡單的幾行程式碼就能方便地提供一個 HTTP 介面,展現當前系統的所有狀態。目前在開發過程中,我們會預設開啟 pprof,這個機制也不止一次地幫助我們發現系統中的問題。

那些年我們踩過的坑

Golang 是一門很好的語言,但並不是一門完美無缺的語言,我們在實踐中也踩過不少坑。

interface{} 的效能問題

資料庫中有大量的資料型別,所以我們需要一個統一的結構來處理所有的型別。我們最初的方案是選擇 interface{},這也是 Golang 中比較自由的選擇。所有的資料型別都可以賦值給 interface{},所有的資料型別相關的函式也都以 interface{} 作為引數,然後在內部用 switch 語句判斷型別,這樣程式寫起來比較簡單。但是很快我們發現大量的 type assert 拖慢了我們的程式,比如下面這段程式碼: var val interface{} val = int64(100) 經過我們測試,把一個整數賦值給一個 interface{} 型別的變數,會觸發一次記憶體分配,通常要耗時幾十到上百納秒。在執行 SQL 語句時,會有大量的類似操作,對效能的損耗嚴重。為了解決這個問題,我們調研了其他資料庫的解決方案,最終採用自定的資料包裝型別 Datum 取代 interface{},這個 Datum 需要能存放各種型別, 實現 value 對 value 賦值。同時為了減少空間佔用, Datum 內部的屬性會在多種資料型別之間重用。上面的程式碼重構後變成: var d Datum d.SetInt64(100) 重構後,在我們的 bench 結果中,表示式計算相關操作的效能,提升 10 倍以上。

包依賴問題

Golang 的包依賴問題一直被人詬病,可以說到目前為止,也沒有完美的解決方案。

Golang 中隱藏的一些 Bug

相比 C/C++/Java/Python 等語言,Golang 算是一門年輕的語言,還是存在一些 bug。上週我們遇到一個詭異的問題,呼叫 atomic.AddInt64 時,在64位系統上 OK, 但是在 i386 系統上,會導致 crash。我們通過內部的 CI 發現問題後,經過研究發現這是 Golang 的一個 bug,對於 32 位系統,需要自己來保證記憶體對齊。

Conclusion

相比 C++/Java/Python 等語言,Golang 不支援許多高階的語言特性,但從工程的角度講,Go 的設計是非常優秀的:規範足夠簡單靈活,有其他語言基礎的程式設計師都能迅速上手。 TiDB 設計之初,我們定了一個原則: Make it run. Make it right. Make it fast. Golang 很好的滿足了我們的原則。高效的開發使得我們很快能做出能 run 的產品,自動的 GC 以及內建的測試框架有利於我們寫出正確的程式,方便的 Profile 工具幫助我們進行系統調優。 除此之外,Golang 還有一個成熟友好的社群,Gopher 們在從社群獲得收益的同時,很願意向社群做貢獻,大量高質量的第三方庫就是最明顯的體現。在平時開發遇到 Golang 相關的問題時,很容易借鑑到別人的經驗,節省了我們大量的時間。

最近整個 TiDB 團隊都在做穩定性和效能相關的事情,也在積極地和國外優秀的開源團隊交流協作,在工程和實踐方面,有蠻多可以借鑑的經驗,等我們11月份忙完 GA 版本的釋出之後,會和大家進一步分享。另外非常感謝謝大對整個 golang 社群的貢獻,讓 PingCAP 從社群中汲取了很多的養分和鼓勵,希望大家一起加油,共同推動社群的發展。

相關文章