捕獲和加強原生系統的可觀測性來發現錯誤

做者:唐劉git

在對 TiDB 進行 Chaos 實踐的時候,我一直在思考如何更好的發現 TiDB 整個系統的故障。最開始,咱們參考的就是 Chaos Engineering 裏面的方式,觀察系統的穩定狀態,注入一個錯誤,而後看 metrics 上面有啥異常,這樣等實際環境中出現相似的 metrics,咱們就知道發現了什麼故障。github

但這套機制其實依賴於如何去注入錯誤,雖然如今咱們已經有了不少種錯誤注入的方式,但總有一些實際的狀況咱們沒有料到。因此後來咱們又考慮了另外的一種方式,也就是直接對 metrics 歷史進行學習,若是某一段時間 metrics 出現了不正常的波動,那麼咱們就能報警。但這個對咱們現階段來講難度仍是有點大,只使用了幾種策略,對 QPS,Latency 這些進行了學習,並不能很好的定位到具體出了什麼樣的問題。算法

因此我一直在思考如何更好的去發現系統的故障。最近,恰好看到了 OSDI 2018 一篇 Paper,Capturing and Enhancing In Situ System Observability for Failure Detection,眼睛一亮,以爲這種方式也是能夠來實踐的。跨域

你們都知道,在生產環境中,故障是無處不在,隨時可能發生的,譬如硬件問題,軟件自身的 bug,或者運維使用了一個錯誤的配置這些。雖然多數時候,咱們的系統都作了容錯保護,但咱們仍是須要能儘快的發現故障,纔好進行故障轉移。網絡

但現實世界並無那麼美好,不少時候,故障並非很明顯的,譬如整個進程掛掉,機器壞掉這些,它們處於一種時好時壞的狀態,咱們一般稱爲「Gray Failure」,譬如磁盤變慢了,網絡時不時丟包。這些故障都很是隱蔽,很難被發現。若是單純的依賴外部的工具,其實很難檢測出來。架構

上面是做者舉的一個 ZooKeeper 的例子,client 已經徹底不能跟 Leader 進行交互了,可是 Leader 卻仍然可以給 Follower 發送心跳,同時也能響應外面 Monitor 發過來的探活命令。運維

若是從外面的 Monitor 看來,這個 ZooKeeper 集羣仍是正常的,但其實它已經有故障了。而這個故障其實 client 是知道的,因此故障檢測的原理很簡單,從發起請求的這一端來觀察,若是發現有問題,那就是有故障了。而這也是這篇論文的中心思想。異步

在論文裏面,做者認爲,任何嚴重的 Gray Failure 都是可以被觀察到的,若是發起請求的這邊遇到了錯誤,天然下一件事情就是將這個錯誤給彙報出去,這樣咱們就知道某個地方出現了故障。因而做者開發了 Panorama 這套系統,來對故障進行檢測。socket

總體架構

先來講說 Panorama 一些專業術語。分佈式

Panorama 總體結構以下:

Panorama 經過一些方式,譬如靜態分析代碼進行代碼注入等,將 Observer 跟要觀察的 Subject 進行綁定,Observer 會將 Subject 的一些信息記錄而且彙報給本地的一個 Local Observation Store(LOS)。本地一個決策引擎就會分析 LOS 裏面的數據來判斷這個組件的狀態。若是多個 LOS 裏面都有對某個 Subject 的 observation,那麼 LOS 會相互交換,用來讓中央的 verdict 更好的去判斷這個 component 的狀態。

故障斷定

而用來判斷一個 component 是否是有故障也比較容易,採用的是一種大多數 bounded-look-back 算法。對於一個 subject,它可能會有不少 observations,首先咱們會對這些 observations 按照 observer 進行分組,對每組單獨進行分析。在每一個組裏面,observations 會按照時間從後往前檢查,而且按照 context 進行聚合。若是一個被觀察的 observation 的 status 跟記錄前面相同 context 的 observation status 狀態不同,就繼續 loop-back,直到遇到一個新的 status。對於一個 context,若是最後的狀態是 unhealthy 或者 healthy 的狀態沒有達到多數,就會被認爲是 unhealthy 的。

經過這種方式,咱們在每組裏面獲得了每一個 context 的狀態,而後又會在多個組裏面進行決策,也就是最經常使用的大多數原則,哪一個狀態最多,那麼這個 context 對應的狀態就是哪個。這裏咱們須要額外處理下 PENDING 這個狀態,若是當前狀態是 HEALTHY 而以前老的狀態是 PENDING,那麼 PENDING 就會變成 HEALTHY,而若是一直是 PENDING 狀態並超過了某個閾值,就會退化成 UNHEALTHY。

Observability

這裏再來講說 Observability 的模式。對於分佈式系統來講,不一樣 component 之間的交互並非同步的,咱們會面臨以下幾種狀況:

若是兩個組件 C1 和 C2 是同步交互,那麼當 C1 給 C2 發送請求,咱們就徹底能在 C1 這一端知道此次請求成功仍是失敗了,可是對於非同步的狀況,咱們可能面臨一個問題,就是 C1 給 C2 發了請求,但其實這個請求是放到了異步消息隊列裏面,但 C1 以爲是成功了,但是後面的異步隊列卻失敗了。因此 Panorama 須要有機制能正確處理上面多種狀況。

爲了能更好的從 component 上面獲得有用的 observations,Panorama 會用一個離線工具對代碼進行靜態分析,發現一些關鍵的地方,注入鉤子,這樣就能去彙報 observations 了。

一般運行時錯誤是很是有用的能證實有故障的證據,可是,並非全部的錯誤都須要彙報,Panorama 僅僅會關係跨 component 邊界產生的錯誤,由於這也是經過發起請求端能觀察到的。Panorama 對於這種跨域的函數調用稱爲 observation boundaries。對於 Panorama 來講,第一件事情就是定位 observation boundaries。一般有兩種 boundaries,進程間交互和線程間交互。進程間交互一般就是 socket I/O,RPC,而線程間則是在一個進程裏面跨越線程的調用。這些 Panorama 都須要分析出來。

當定位了 observation boundaries 以後,下一件事情就是肯定 observer 和 subject 的標識。譬如對於進程間交互的 boundaries,observer 的標識就多是這個進程在系統裏面的惟一標識,而對於 subject,咱們能夠用 method 名字,或者是函數的一個參數,類裏面的一個字段來標識。

而後咱們須要去肯定 observation points,也就是觀測點。一般這些點就是代碼處理異常的地方,另外可能就是一些正常處理返回結果但會對外報錯的地方。

上面就是一個簡單分析代碼獲得 observation points 的例子,但這個仍然是同步的,對於 indirection 的,還須要額外處理。

對於異步請求,咱們知道,經過發出去以後,會異步的處理結果,因此這裏分爲了兩步,叫作 ob-origin 和 ob-sink。以下:

對於 ob-origin,代碼分析的時候會先給這個 observation 設置成 PENDING 狀態,只有對應的 ob-sink 調用而且返回了正確的結果,纔會設置成 HEALTHY。由於 ob-origin 和 ob-sink 是異步的,因此代碼分析的時候會加上一個特殊的字段,包含 subject 的標識和 context,這樣就能讓 ob-origin 和 ob-sink 對應起來。

小結

上面大概介紹了 Panorama 的架構以及一些關鍵的知識點是如何實現的,簡單來講,就是在一些關鍵代碼路徑上面注入 hook,而後經過 hook 對外將相關的狀態給彙報出去,在外面會有其餘的分析程序對拿到的數據進行分析從而斷定系統是否在正常工做。它其實跟加 metrics 很像,但 metrics 只能看出哪裏出現了問題,對於想更細緻定位具體的某一個問題以及它的上下文環境,倒不是特別的方便。這點來講 Panorama 的價值仍是挺大的。

Panorama 的代碼已經開源,總的來講仍是挺簡單的,但我沒找到核心的代碼分析,注入 hook 這些,有點遺憾。但理解了大概原理,其實先強制在代碼寫死也何嘗不可。另外一個比較可行的辦法就是進行在代碼裏面把日誌添加詳細,這樣就不用代碼注入了,而是在外面寫一個程序來分析日誌,其實 Panorama 代碼裏面提供了日誌分析的功能,爲 ZooKeeper 來設計的,但做者本身也說到,分析日誌的效果比不上直接在代碼裏面進行注入。

那對咱們來講,有啥能夠參考的呢?首先固然是這一套故障檢查的理念,既然 Panorama 已經作出來而且能發現故障量,天然咱們也能夠在 TiDB 裏面實施。由於咱們已經有在 Go 和 Rust 代碼裏面使用 fail 來進行錯誤注入的經驗,因此早期手寫監控代碼也何嘗不可,但也能夠直接完善日誌,提供一個程序來分析日誌就成。若是你對這塊感興趣,想把 Panorama 相關的東西應用到 TiDB 中來,歡迎聯繫我 tl@pingcap.com。

相關文章
相關標籤/搜索