(轉載 --- 上篇) 分散式系統測試那些事兒 - 理念

清風發表於2020-12-16

注:你所看到的、所做的只是冰山一角。

本話題系列文章整理自 PingCAP NewSQL Meetup 第 26 期劉奇分享的《深度探索分散式系統測試》議題現場實錄。文章較長,為方便大家閱讀,會分為上中下三篇,本文為上篇。

今天主要是介紹分散式系統測試。對於 PingCAP 目前的現狀來說,我們是覺得做好分散式系統測試比做一個分散式系統更難。就是你把它寫出來不是最難的,把它測好才是最難的。大家肯定會覺得有這麼誇張嗎?那我們先從一個最簡單的、每個人都會寫的 Hello world 開始。

A simple “Hello world” is a miracle
We should walk through all of the bugs in:

  1. Compiler
  2. Linker
  3. VM (maybe)
  4. OS

其實這個 Hello world 能夠每次都正確執行已經是一個奇蹟了,為什麼呢?首先,編譯器得沒 bug,連結器得沒 bug ;然後我們可能跑在 VM 上,那 VM 還得沒 bug;並且 Hello world 那還有一個 syscall,那我們還得保證作業系統沒有 bug;到這還不算吧,我們還得要硬體沒有 bug。所以一個最簡單程式它能正常執行起來,我們要穿越巨長的一條路徑,然後這個路徑裡面所有的東西都不能出問題,我們才能看到一個最簡單的 Hello world。

但是分散式系統裡面呢,就更加複雜了。比如大家現在用的很典型的微服務。假設你提供了一個微服務,然後在微服務提供的功能就是輸出一個 Hello world ,然後讓別人來 Call。

A RPC “Hello world” is a miracle
We should walk through all of the bugs in:

  1. Coordinator (zookeeper, etcd)
  2. RPC implementation
  3. Network stack
  4. Encoding/Decoding library
  5. Compiler for programming languages or [protocol buffers, avro, msgpack, capn]

那麼我們可以看一下它的路徑。我們起碼需要依賴 Coordinator 去做這種服務發現,比如用 zookeeper,etcd ,大家會感覺是這東西應該很穩定了吧?但大家可以去查一下他們每一次 release notes,裡邊說我們 fix 了哪些 bug,就是所有大家印象中非常穩定的這些東西,一直都在升級,每一次升級都會有 bug fix。但換個思路來看,其實我們也很幸運,因為大部分時候我們沒有碰到那個 bug,然後 RPC 的這個實現不能有問題。當然如果大家深度使用 RPC,比如說 gRPC,你會發現其實 bug 還是挺多的,用的深一點,基本上就會發現它有 bug。還有系統網路協議棧,去年 TCP 被爆出有一個 checksum 問題,就是 Linux 的 TCP 協議棧,這都是印象中永遠不會出問題的。再有,編解碼,大家如果有 Go 的經驗的話,可以看一下 Go 的 JSON 歷史上從釋出以來更新的記錄,也會發現一些 bug。還有更多的大家喜歡的編解碼,比如說你用 Protocol buffers、Avro、Msgpack、Cap'n 等等,那它們本身還需要 compiler 去生成一個程式碼,然後我們還需要那個 compiler 生成的程式碼是沒有 bug 的。然後這一整套下來,我們這個程式差不多是能執行的,當然我們沒有考慮硬體本身的 bug。

其實一個正確的執行程式從概率上來講(不考慮宇宙射線什麼的這種),已經是非常幸運的了。當然每一個系統都不是完善的,那通常情況下,為什麼我們這個就執行的順利呢?因為我們的測試永遠都測到了正確的路徑,我們跑一個簡單的測試一定是把正確的路徑測到了,但是這中間有很多錯誤路徑其實我們都沒有碰到。然後我不知道大家有沒有印象,如果寫 Go 程式的時候,錯誤處理通常寫成 if err != nil,然後 return error ,不知道大家寫了多少。那其它程式、其它的語言裡就是 try.catch,然後裡面各種 error 處理。就是一個真正完善的系統,最終的錯誤處理程式碼實際上通常會比你寫正常邏輯程式碼還要多的,但是我們的測試通常 cover 的是正確的邏輯,就是實際上我們測試的 cover 是一小部分。

那先糾正幾個觀念,關於測試的。就是到底怎麼樣才能得到一個好的、高質量的程式,或者說得到一個高質量的系統?

Who is the tester ?
Quality comes from solid engineering.
Stop talking and go build things.
Don’t hire too many testers.
Testing is owned by the entire team. It is a culture, not a process.
Are testers software engineers? Yes.
Hiring good people is the first step. And then keep them challenged.

我們的觀念是說先有 solid engineering 。我覺得這個幾乎是勿庸置疑的吧,不知道大家的經驗是什麼?然後還有一個就是不扯淡,儘快去把東西 build 起來,然後讓東西去運轉起來。我前一段時間也寫了一個段子,就是:“你是寫 Rust 的,他是寫 Java 的,你們這聊了這麼久,人家 Rust (編譯速度慢) 的程式已經編譯過了,你 Java 還沒開始寫。”原版是這樣的:“你是砍柴的,他是放羊的,你們聊了一天,他的羊吃飽了,你的柴呢?”然後最近還有一個特別有爭議的話題:CTO 應該幹嘛。就是 CTO 到底該不該寫程式碼,這個也是眾說紛紜。因為每一個人都受到自己環境的侷限,所以每個人的看法都是不一樣的。那我覺得有點像,就是同樣是聊天,然後不同人有不同的看法。

Test automation
Allow developers to get a unit test results immediately.
Allow developers to run all unit tests in one go.
Allow code coverage calculations.
Show the testing evolution on the dashboards.
Automate everything.

我們現在很有意思的一個事情是,迄今為止 PingCAP 沒有一個測試人員,這是在所有的公司看來可能都是覺得不可思議的事情,那為什麼我們要這麼幹?因為我們現在的測試已經不可能由人去測了。究竟複雜到什麼程度呢?我說幾個基本數字大家感受一下:我們現在有六百多萬個 Test,這是完全自動化去跑的。然後我們還有大量從社群收集到的各種 ORM Test,一會我會提到這一點。就是這麼多 Test 已經不可能是由人寫出來的了,以前的概念裡面是 Test 是由人寫的,但實際上 Test 不一定是人寫的,Test 也是可以由機器生成的。舉個例子,如果給你一個合法的語法樹,你按照這個語法樹去做一個輸出,比如說你可以更換變數名,可以更換它的表示式等等,你可以生成很多的這種 SQL 出來。

Google Spanner 就用到這個特性,它會有專門的程式自動生成符合 SQL 語法的語句,然後再交給系統去執行。如果執行過程中 crash 了,那說明這個系統肯定有 bug。但是這地方又蹦出另外一個問題,就是你生成了合法的 SQL 語句,但是你不知道它語句執行的結構,那你怎麼去判斷它是不是對的?當然業界有很聰明的人。我把它扔給幾個資料庫同時跑一下,然後取幾個大家一致的結果,那我就認為這個結果基本上是對的。如果一個語句過來,然後在我這邊執行的結果和另外幾個都不一樣,那說明我這邊肯定錯了。就算你是對的,可能也是錯的,因為別人執行下來都是這個結果,你不一樣,那大家都會認為你是錯的。

所以說在測試的時候,怎麼去自動生成測試很重要。去年,在美國那邊開始流行一個新的說法,叫做 “怎麼在你睡覺的時候發現 bug”。那麼實際上測試乾的很重要的事情就是這個,就是自動化測試是可以在你睡覺的時候發現 bug。好像剛才我們還提到 fault injection ,好像還有 fuzz testing。然後所有測試的人都是工程師,因為只有這樣你才不會甩鍋。

這是我們現在堅信的一個事情,就是所有的測試必須要高度的自動化,完全不由人去幹預。然後很重要的一個就是僱最優秀的人才,同時給他們挑戰,就是如果沒有挑戰,這些人才會很閒,精力分散,然後很難合力出成績。因為以現在這個社會而言,很重要一個特性是什麼?就是對於複雜性工程需要大量的優秀人才,如果優秀的人才力不往一處使力的話,這個複雜性工程是做不出來的。我今天看了一下龍芯做了十年了,差不多是做到英特爾凌動處理器的水平。他們肯定是有很優秀的人才,但是目前還得承認,我們在硬體上面和國外的差距還比較大,其實軟體上面的差距也比較大,比如說我們和 Spanner 起碼差了七年,2012 年 Spanner 就已經大規模在 Google 使用了,對這些優秀的作品,我們一直心存敬仰。

我剛才已經反覆強調過自動化這個事情。不知道大家平時寫程式碼 cover 已經到多少了?如果 cover 一直低於 50%,那就是說你有一半的程式碼沒有被測到,那它線上上什麼時候都有可能出現問題。當然我們還需要更好的方法去在上線之前能夠把線上的 case 回放。理論上你對線上這個回放的越久你就越安全,但是前提是線上程式碼永遠不更新,如果業務方更新了,那就又相當於埋下了一個定時炸彈。比如說你在上面跑兩個月,然後業務現在有一點修改,然而那兩個又沒有 cover 住修改,那這時候可能有新的問題。所以要把所有的一切都自動化,包括剛才的監控。比如說你一個系統一過去,然後自動發現有哪些項需要監控,然後自動設定報警。大家覺得這事是不是很神奇?其實這在 Google 裡面是司空見慣的事情,PingCAP 現在也正在做。

Well… still not enough ?
Each layer can be tested independently.
Make sure you are building the right tests.
Don’t bother great people unless the testing fails.
Write unit tests for every bug.

這麼多還是不夠的,就是對於整個系統測試來講,你可以分成很多層、分成很多模組,然後一個一個的去測。還有很重要的一點,就是早期的時候我們發現一個很有意思的事情。就是我們 build 了大量 Test,然後我們的程式都輕鬆的 pass 了大量的 Test,後來發現我們一個 Test 是錯的,那意味著什麼?意味著我們的程式一直是錯的,因為 Test 會把你這個 cover 住。所以直到後來我們有一次覺得自己寫了一個正確的程式碼,但是跑出來的結果不對,我們這時候再去查,發現以前有一個 Test 寫錯了。所以一個正確的 Test 是非常重要的,否則你永遠被埋在錯誤裡面,然後埋在錯誤裡面感覺還特別好,因為它告訴你是正確的。

還有,為什麼要自動化呢?就是你不要去打擾這些聰明人。他們本身很聰明,你沒事別去打擾他們,說“來,你過來給我做個測試”,那這時候不斷去打擾他們,是影響他們的發揮,影響他們做自己的挑戰。

這一條非常重要,所有出現過的 bug,歷史上只要出現過一次,你一定要寫一個 Test 去 cover 它,那這個法則大家應該已經都清楚了。我看今天所在的人的年齡,應該《聖鬥士星矢》是看過的,對吧?這個聖鬥士是有一個特點的,所有對他們有效的招數只能用一次,那這個也是一樣的,就保證你不會被再次咬到,就不會再次被坑到。我印象中應該有很多人 fix bug 是這樣的:有一個 bug 我 fix 了,但沒有 Test,後來又出現了,然後這時候就覺得很奇怪,然後積累的越多,最後就被坑的越慘。

這個是目前主流開源社群都在堅持的做法,基本沒有例外。就是如果有一個開源社群說我發現一個 bug,我沒有 Test 去 cover 它,這個東西以後別人是不敢用的。

Code review
At least two LGTMs (Looks good to me) from the maintainers.
Address comments.
Squash commit logs.
Travis CI/Circle CI for PRs.

簡單說一下 code review 的事情,它和 Test 還是有一點關係,為什麼?因為在 code review 的時候你會提一個新的 pr,然後這個 pr 一定要通過這個 Test。比如說典型的 Travis CI,或者 CircleCI 的這種 Test。為什麼要這樣做呢?因為要保證它被 merge 到 master 之前你一定要發現這個問題,如果已經 merge 到 master 了,首先這不好看,因為你要 revert 掉,這個在 commit 記錄上是特別不好看的一個事情。另外一個就是它出現問題之前,你就先把它發現其實是最好的,因為有很多工具會根據 master 自動去 build。比如說我們會根據 master 去自動 build docker 映象,一旦你程式碼被 commit 到 master,然後 docker 映象就出來了。那你的使用者就發現,你有新的更新,我要馬上使用新的,但是如果你之前的 CI 沒有過,這時候就麻煩了,所以 CI 沒過,一定不能進入到 CD 階段。

Who to blame in case of bugs?
The entire team.

另外一個觀念糾正一下,就是出現 bug 的時候,責任是誰的?通常我見過的很多人都是這樣,就說“這個 bug 跟我沒關係,他的模組的 bug”。那 PingCAP 這邊的看法不一樣,就是一旦出現 bug,這應該是整個 team 的責任,因為你有自己的 code review 機制,至少有兩個以上的人會去看它這個程式碼,然後如果這個還出現問題,那一定不是一個人的問題。

除了剛才說的發現一些 bug,還有一些你很難定義,說這是不是 bug,怎麼系統跑的慢,這算不算 bug,怎麼對 bug 做界定呢?我們現在的界定方式是使用者說了算。雖然我們覺得這不是 bug,這不就慢一點嗎,但是使用者說了這個東西太慢了,我們不能忍,這就是 bug,你就是該優化的就優化。然後我們團隊裡面出現過這樣的事情,說“我們這個已經跑的很快了,已經夠快了”,對不起,使用者說慢,使用者說慢就得改,你就得去提升。總而言之,標準不能自己定,當然如果你自己去定這個標準,那這個事就變成“我這個很 OK 了,我不需要改了,可以了。”這樣是不行的。

Profiling
Profile everything, even on production
once-in-a-lifetime chance
Bench testing

另外,在 Profile 這個事情上面,我們強調一個,即使是線上上,也需要能做 Profile,其實 Profile 的開銷是很小的。然後很有可能是這樣的,有一次線上系統特別卡,如果你把那個重啟了,你可能再也沒有機會復現它了,那麼對於這些情況它很可能是一輩子發生一次的,那一次你沒有抓住它,你可能再也沒有機會抓住它了。當然我們後面會介紹一些方法,可以讓這個能復現,但是有一些確實是和業務相關性極強的,那麼可能剛好又碰到一個特別的環境才能讓它出現,那真的可能是一輩子就那麼一次的,你一定要這次抓住它,這次抓不住,你可能永遠就抓不住了。因為有些犯罪它一輩子只犯一次,它犯完之後你再也沒有機會抓住它了。

Embed testing to your design
Design for testing or Die without good tests
Tests may make your code less beautiful

再說測試和設計的關係。測試是一定要融入到你的設計裡面,就是在你設計的時候就一定要想這個東西到底應該怎麼去測。如果在設計的時候想不到這個東西應該怎麼測,那這個東西就是正確性實際上是沒法驗證的,這是非常恐怖的一件事情。我們把測試的重要程度看成這樣的:你要麼就設計好的測試,要麼就掛了,就沒什麼其它的容你選擇。就是說在這一塊我們把它的重要性放到一個最高的程度。

未完待續…

原文地址:
https://pingcap.com/blog-cn/distributed-system-test-1/
https://pingcap.com/blog-cn/distributed-system-test-2/
https://pingcap.com/blog-cn/distributed-system-test-3/

轉載 - 中篇地址:https://testerhome.com/topics/27169
轉載 - 下篇地址:https://testerhome.com/topics/27177

後記:
三篇文章字數頗多,如果靜下心細細品讀,會有不同的收穫。

相關文章