(轉載 --- 下篇) 分散式系統測試那些事兒 - 信心的毀滅與重建

清風發表於2020-12-16

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

-接中篇-
ScyllaDB 有一個開源的東西,是專門用來給檔案系統做 Failure Injection 的, 名字叫做 CharybdeFS。如果你想測試你的系統,就是檔案系統在哪不斷出問題,比如說寫磁碟失敗了,驅動程式分配記憶體失敗了,檔案已經存在等等,它都可以測模擬出來。

CharybdeFS: A new fault-injecting file system for software testing

Simulate the following errors:

  • disk IO error (EIO)
  • driver out of memory error (ENOMEM)
  • file already exists (EEXIST)
  • disk quota exceeded (EDQUOT) 再來看看 Cloudera,下圖是整個 Cloudera 的一個 Failure Injection 的結構。

一邊是 Tools,一邊是它的整個的 Level 劃分。比如說整個 Cluster, Cluster 上面有很多 Host,Host 上面又跑了各種 Service,整個系統主要用於測試 HDFS, HDFS 也是很努力的在做有效的測試。然後每個機器上部署一個 AgenTEST,就用來注射那些可能出現的錯誤。

看一下它們作用有多強大。

Cloudera: Simulate the following errors:

  • Packets loss/corrupt/reorder/duplicate/delay
  • Bandwidth limit: Limit the network bandwidth for the specified address and port.
  • DNSFail: Apply an injection to let the DNS fail.
  • FLOOD: Starts a DoS attack on the specified port.
  • BLOCK: Blocks all the packets directed to 10.0.0.0/8 (used internally by EC2).
  • SIGSTOP: Pause a given process in its current state.
  • BurnCPU/BurnIO/FillDISK/RONLY/FIllMEM/CorruptHDFS
  • HANG: Hang a host running a fork bomb.
  • PANIC: Force a kernel panic.
  • Suicide: Shut down the machine.

資料包是可以丟的,可以壞的,可以 reorder 的,比如說你發一個 A,再發一個 B,它可以給你 reorder,變成先發了 B 再發了 A,然後看你應用程式有沒有正確的處理這種行為。接著發完一次後面再給你重發,然後可以延遲,這個就比較簡單。目前這個裡面的大部分,TiKV 都有實現,還有頻寬的限制,就比如說把你頻寬壓縮成 1M。以前我們遇到一個問題很有意思,發現有人把檔案存到 Redis 裡面,但 Redis 是帶多個使用者共享的,一個使用者就能把整個 Redis 頻寬給打滿了,這樣其他人的頻寬就很卡,那這種很卡的時候 Redis 可能出現的行為是什麼呢?我們並不需要一個使用者真的去把它打滿,只要用這種工具,瞬間就能出現我把你的頻寬限制到原來的 1%,假設別人在跟你搶頻寬,你的程式行為是什麼?馬上就能出來,也不需要配很複雜的環境。這極大的提高了測試效率,同時能測試到很多 corner case。

然後 DNS fail。那 DNS fail 會有什麼樣的結果?有測過嗎?可能都沒有想過這個問題,但是在一個真正的分散式系統裡面,每一點都是有可能出錯的。還有 FLOOD,假設你現在被攻擊了,整個系統的行為是什麼樣的?然後一不小心被這個 IP table 給 block 了,該怎麼辦。這種情況我們確實出現過。我們一上來併發,兩萬個連線一打出去,然後發現大部分都連不上,後來一看 IP table 自動啟用了一個機制,然後把你們都 block。當然我們後面查了半個小時左右,才把問題查出來。但這種實際上應該是在最開始設計的時候就應該考慮的東西。

如果你的程式被暫停了,比如說大家在雲上跑在 VM 裡面,整個 VM 為了升級,先把你整個暫停了,升級完之後再把你恢復的時候會怎麼樣?那簡單來講,就是如果假設你程式是有 GC 的,GC 現在把我們的程式卡了五秒,程式行為是正常的嗎?五十秒呢?這個很有意思的就是,BurnCPU,就是再寫一個程式,把 CPU 全佔了,然後讓你這個現在的程式只能使用一小部分的 CPU 的時候,你程式的行為是不是正常的。正常來講,你可能說我 CPU 不是瓶頸啊,我瓶頸在 IO,當別人跟你搶 CPU,把你這個 CPU 壓的很低的時候,到 CPU 是瓶頸的時候,正常你的程式的這個行為是不是正常的?還有 IO,跟你搶讀的資源,跟你搶寫的資源,然後 filedisk 把磁碟寫滿,寫的空間很少。比如說對資料庫而言,你建立你的 redo log 的時候,都已經滿了會怎麼樣?然後我突然把磁碟設為只讀,就你突然一個寫入會出錯,但是你接下來正常的讀寫行為是不是對的?很典型的一個例子,如果一個資料庫你現在寫入,磁碟滿了,那外面讀請求是否就能正常響應。 Fill memory,就是瞬間把這個 memory 給壓縮下來,讓你下次 malloc 的時候可能分佈不到記憶體。這個就和業務比較相關了,就是破壞 HDFS 的檔案。其它的就是 Hang、Panic,然後還有自殺,直接關掉機器,整個系統的行為是什麼樣的?

現在比較痛苦的一點是大家各自為政,每一家都做一套,但是沒有辦法做成一個通用的東西給所有的人去用。包括我們自己也做了一套,但是確實沒有辦法和其他的語言之間去 share,最早提到的那個 libfu 庫實際上是在 C 語言寫的,那所有 C 相關的都可以去 call 那個庫。

Distributed testing

  • Namazu
    • ZooKeeper:
      • Found ZOOKEEPER-2212, ZOOKEEPER-2080 (race): (blog article)
    • Etcd:
      • Found etcdctl bug #3517 (timing specification), fixed in #3530. The fix also resulted a hint of #3611, Reproduced flaky tests {#4006, #4039}
    • YARN: Found YARN-4301 (fault tolerance), Reproduced flaky tests{1978, 4168, 4543, 4548, 4556}

然後 Namazu。大家肯定覺得 ZooKeeper 很穩定呀, Facebook 在用、阿里在用、京東在用。大家都覺得這個東西也是很穩定的,直到這個工具出現了,然後輕輕鬆鬆就找到 bug 了,所有的大家認為的這種特別穩定的系統,其實 bug 都還挺多的,這是一個毀三觀的事情,就是你覺得東西都很穩定,都很 stable,其實不是的。從上面,我們能看到 Namazu 找到的 Etcd 的幾個 bug,然後 YARN 的幾個 bug,其實還有一些別的。

How TiKV use namazu

  • Use nmz container / non-container mode to disturb cluster.
    • Run container mode in CI for each commit. (1 hour)
    • Run non-container mode for a stable version. (1 week+)
  • Use extreme policy for process inspector
    • Pick up some processes and execute them with SCHED_RR scheduler. others are executed with SCHED_BATCH scheduler
  • Use [0, 30s] delay for filesystem inspector

接下來說一下 TiKV 用 Namazu 的一些經驗。因為我們曾經在系統上、在雲上面出現過一次寫入磁碟花了五十幾秒才完成的情況,所以我們需要專門的工具模擬這個磁碟的抖動。有時候一次寫入可能確實耗時比較久,那這種時候是不是 OK 的。大家如果能把這種東西統統用上,我覺得還能為很多開源系統找出一堆 bug。

稍微介紹一下我們現在執行的基本策略,比如說我們會用 0 到 30 秒的這個 delay (就是每一次你往檔案系統的互動,比如說讀或者寫,那麼我們會給你產生隨機的 0 到 30 秒的 delay ),但我們正常應該還是需要去測三十秒到幾分鐘的延遲的情況,是否會讓整個系統崩掉了。

How TiKV simulate network transport

  • Drop/Delay messages randomly
  • Isolate Node
  • Partition [1, 2, 3, 4, 5] -> [1, 2, 3] + [4, 5]
  • Out of order messages
  • Filter messages
  • Duplicate and send redundant messages

怎麼模擬網路呢?假設你有網路,裡面有五臺機器,那我現在想做一個腦裂怎麼做?不能靠拔網線對吧?比如在 TiKV 的測試框架中,我們就可以直接通過 API 把 5 個節點腦裂成兩部分,讓 1, 2, 3 號節點互相聯通,4, 5 號節點也能聯通,這兩個分割槽彼此是隔離的,非常的方便。其實原理很簡單,這種情況是用程式自己去模擬,假如是你發的包,自動給你丟掉,或者直接告訴你 unreachable,那這個時候你就知道這個網路就腦裂了,然後你怎麼做?就是隻允許特定型別的訊息進來,把其他的都丟掉,這樣一來你可以保證有些 bug 是必然重現的。這個框架給了我們極大的信心用來模擬並重現各種 corner case,確保這些 corner case 在單元測試中每次都能被覆蓋到。

How to test Rocksdb

  • Treat storage as a black box.
  • Three steps(7*24):
    • Fill data, Random kill -9
    • Restart
    • Consistent check.
  • Results:
    • Found 2 bugs. Both fixed

然後說說我們怎麼測 RocksDB。 RocksDB 在大家印象中是很穩定的,但我們最近發現了兩個 bug。測的方法是這樣的:我們往 RocksDB 裡面填資料,然後隨機的一段時間去把它 kill 掉,kill 掉之後我們重啟,重新啟動之後去檢測我們剛才 fail 的 data 是不是一致的,然後我們發現兩個可能造成資料丟失的 bug,但是官方的響應速度非常快,幾天就都 fix 了。可是大家普遍執行的是這麼 stable 的系統,為什麼還會這麼容易找到 bug?就說這個測試,如果是一直有這個測試的 cover,那麼這兩個 bug 可能很快就能夠被發現。

這是我們一個基本的,也就是當成一個純黑盒的測。大家在測資料庫的時候,基本也是當黑盒測。比如說 MySQL 寫入資料,kill 掉,比如說我 commit 一個事務,資料庫告訴我們 commit 成功,我把資料庫 kill 掉,我再去查我剛才提交的資料一樣能查到。這是一個正常的行為,如果查不到,說明整個系統有問題。

More tools

american fuzzy lop

其實還有一些更加先進的工具,大家平時覺得特別穩定的東西,都被摧殘的不行。Nginx 、NGPD、tcpdump 、LibreOffice ,如果有用 Linux 的同學可能知道,還有 Flash、sqlite。這個東西一出來,當時大家很興奮,說怎麼一下子找了這麼多 bug,為什麼以前那麼穩定的系統這麼不堪一擊,會覺得這個東西它還挺智慧的。就比如說你程式裡面有個 if 分支,它是這樣的,假如你程式有一百條指令,它先從前面一直走,走到某條分支指令的時候,它是一直持續探索,一個分支走不下去,它會一直在這兒持續探索,再給你隨機的輸入,直到我探索進去了,我記下來了下次我知道我用這個輸入可以進去特定的分支。那我可以再往下走,比如說你 if 分支進去之後裡面還有 if ,那你傳統手段可能探測不進去了但它可以,它記錄一下,我這個可以進去,然後我重來,反正我繼續輸入這個,我再往裡面走,一旦我探測到一個新的分支,我再記住,我再往裡面走。所以它一出來的時候大家都說這個真厲害,一下發現這麼多 bug。但最激動的不是這些人,最激動的是黑客,為什麼?因為突然有很多棧溢位、堆溢位漏洞被發現了,然後就可以寫一堆工具去攻擊線上的這麼多系統。所以很多的技術的推進在早期的時候是黑客做出來,但是他們的目的當然不一定是為了測試 bug,而是為了怎麼黑一個系統進去,這是他們當時做的,所以這個工具也是非常強大、非常有意思的,大家可以拿去研究一下自己的系統。

大家印象裡面各種檔案系統是很穩定的,可是當用 American fuzzy lop 來測試的時候,被驚呆了。 Btrfs 連 5 秒都沒有堅持到就跪了,大家用的最多的 Ext4 是最堅挺的,也才抗了兩個小時!!!

再來說說 Google,Google 怎麼做測試對外講的不多,最近 Chrome team 開源了他們的 Fuzz 測試工具 OSS-Fuzz,這個工具強大的地方在於自動化做的極好:

  • 發現 bug 後自動建立 issue
  • bug 解決後自動 verify 更驚人的是 OSS-Fuzz 叢集一週可以跑 ~4 trillion test cases 更多細節大家可以看這篇文章:Announcing OSS-Fuzz: Continuous Fuzzing for Open Source Software

另外有些工具能讓分散式系統開發人員的生活變得更美好一點。

Tracing tools may help you

  • Google Dapper
  • Zipkin
  • OpenTracing

還有 Tracing,比如說我一個 query 過來,然後經過這麼多層,經過這麼多機器,然後在不同的地方,不同環節耗時多久,實際上這個在分散式系統裡面,有個專門的東西做 Tracing ,就是 distribute tracing tools。它可以用一條線來表達你的請求在各個階段耗時多長,如果有幾段,那麼分到幾個機器,分別並行的時候好了多長時間。大體的結構是這樣的:

這裡是一個具體的例子:

很清晰,一看就知道了,不用去看 log,這事其實一點也不新鮮,Google 十幾年前就做了一個分散式追蹤的工具。然後開源社群要做一個實現叫做 Zipkin,好像是 java 還是什麼寫的,又出了新的叫 OpenTracing,是 Go 寫的。我們現在正準備上這個系統,用來追蹤 TiDB 的請求在各個階段的響應時間。

最後想說一下,大家研究系統發現 bug 多了之後,不要對系統就喪失了信心,畢竟bug 一直在那裡,只是從前沒有發現,現在發現得多了,總體上新的測試方法讓系統的質量比以前好了很多。好像有點超時了,先聊到這裡吧,還有好多細節沒法展開,下次再聊。

-本系列完結-

相關文章