在對 TiDB 進(jìn)行 Chaos 實踐的時候撮慨,我一直在思考如何更好的發(fā)現(xiàn) TiDB 整個系統(tǒng)的故障竿痰。最開始,我們參考的就是 Chaos Engineering 里面的方式砌溺,觀察系統(tǒng)的穩(wěn)定狀態(tài)影涉,注入一個錯誤,然后看 metrics 上面有啥異常规伐,這樣等實際環(huán)境中出現(xiàn)類似的 metrics蟹倾,我們就知道發(fā)現(xiàn)了什么故障。
但這套機(jī)制其實依賴于如何去注入錯誤猖闪,雖然現(xiàn)在我們已經(jīng)有了很多種錯誤注入的方式鲜棠,但總有一些實際的情況我們沒有料到。所以后來我們又考慮了另外的一種方式培慌,也就是直接對 metrics 歷史進(jìn)行學(xué)習(xí)豁陆,如果某一段時間 metrics 出現(xiàn)了不正常的波動,那么我們就能報警吵护。但這個對我們現(xiàn)階段來說難度還是有點大盒音,只使用了幾種策略,對 QPS馅而,Latency 這些進(jìn)行了學(xué)習(xí)祥诽,并不能很好的定位到具體出了什么樣的問題。
所以我一直在思考如何更好的去發(fā)現(xiàn)系統(tǒng)的故障瓮恭。最近雄坪,剛好看到了OSDI 2018 一篇 Paper, Capturing and Enhancing In Situ System Observability for Failure Detection偎血,眼睛一亮,覺得這種方式也是可以來實踐的盯漂。
大家都知道颇玷,在生產(chǎn)環(huán)境中,故障是無處不在就缆,隨時可能發(fā)生的帖渠,譬如硬件問題,軟件自身的 bug竭宰,或者運維使用了一個錯誤的配置這些空郊。雖然多數(shù)時候,我們的系統(tǒng)都做了容錯保護(hù)切揭,但我們還是需要能盡快的發(fā)現(xiàn)故障狞甚,才好進(jìn)行故障轉(zhuǎn)移。
但現(xiàn)實世界并沒有那么美好廓旬,很多時候哼审,故障并不是很明顯的,譬如整個進(jìn)程掛掉,機(jī)器壞掉這些涩盾,它們處于一種時好時壞的狀態(tài)十气,我們通常稱為『Gray Failure』,譬如磁盤變慢了春霍,網(wǎng)絡(luò)時不時丟包砸西。這些故障都非常隱蔽,很難被發(fā)現(xiàn)址儒。如果單純的依賴外部的工具芹枷,其實很難檢測出來。
上面是作者舉得一個 Zookeeper 的例子离福,client 已經(jīng)完全不能跟 Leader 進(jìn)行交互了杖狼,但是 Leader 卻仍然能夠給 Follower 發(fā)送心跳,同時也能響應(yīng)外面 Monitor 發(fā)過來的探活命令妖爷。
如果從外面的 Monitor 看來蝶涩,這個 Zookeeper 集群還是正常的,但其實它已經(jīng)有故障了絮识。而這個故障其實 client 是知道的绿聘,所以故障檢測的原理很簡單,從發(fā)起請求的這一端來觀察次舌,如果發(fā)現(xiàn)有問題熄攘,那就是有故障了。而這也是這篇論文的中心思想彼念。
在論文里面挪圾,作者認(rèn)為,任何嚴(yán)重的 Gray Failure 都是能夠被觀察到的逐沙,如果發(fā)起請求的這邊遇到了錯誤哲思,自然下一件事情就是將這個錯誤給匯報出去,這樣我們就知道某個地方出現(xiàn)了故障吩案。于是作者開發(fā)了 Panorama 這套系統(tǒng)棚赔,來對故障進(jìn)行檢測。
整體架構(gòu)
先來說說 Panorama 一些專業(yè)術(shù)語
Name | 解釋 |
---|---|
Component | 要觀察的組件徘郭,可以是一個進(jìn)程靠益,或者是一個線程 |
Subject | 一個被監(jiān)控的 component |
Observer | 一個用來監(jiān)控 subject 的 component |
Status | 一個 subject 的健康狀態(tài) |
Observation | 一個 observer 確定一個 subject 狀態(tài)的證據(jù) |
Context | 當(dāng)一個 observer 確定 observation 時候的上下文 |
Verdict | 一個用來確定 subject 狀態(tài)的決定,會通過多個 observation 來匯總得出 |
Panorama 整體結(jié)構(gòu)如下:
Panorama 通過一些方式残揉,譬如靜態(tài)分析代碼進(jìn)行代碼注入等胧后,將 Observer 跟要觀察的 Subject 進(jìn)行綁定,Observer 會將 Subject 的一些信息記錄并且匯報給本地的一個 Local Observation Store(LOS)抱环。本地一個決策引擎就會分析 LOS 里面的數(shù)據(jù)來判斷這個組件的狀態(tài)绩卤。如果多個 LOS 里面都有對某個 Subject 的 observation途样,那么 LOS 會相互交換,用來讓中央的 verdict 更好的去判斷這個 component 的狀態(tài)濒憋。
故障判定
而用來判斷一個 component 是不是有故障也比較容易何暇,采用的是一種大多數(shù) bounded-look-back 算法。對于一個 subject凛驮,它可能會有很多 observations裆站,首先我們會對這些 observations 按照 observer 進(jìn)行分組,對每組單獨進(jìn)行分析黔夭。在每個組里面宏胯,Observations 會按照時間從后往前檢查,并且按照 context 進(jìn)行聚合本姥。如果一個被觀察的 observation 的 status 跟記錄前面相同 context 的 observation status 狀態(tài)不一樣肩袍,就繼續(xù) loop-back,直到遇到一個新的 status婚惫。對于一個 context氛赐,如果最后的狀態(tài)是 unhealthy 或者 healthy 的狀態(tài)沒有達(dá)到多數(shù),就會被認(rèn)為是 unhealthy 的先舷。
通過這種方式艰管,我們在每組里面得到了每個 context 的狀態(tài),然后又會在多個組里面進(jìn)行決策蒋川,也就是最常用的大多數(shù)原則牲芋,哪個狀態(tài)最多,那么這個 context 對應(yīng)的狀態(tài)就是哪一個捺球。這里我們需要額外處理下 PENDING 這個狀態(tài)缸浦,如果當(dāng)前狀態(tài)是 HEALTHY 而之前老的狀態(tài)是 PENDING,那么 PENDING 就會變成 HEALTHY氮兵,而如果一直是 PENDING 狀態(tài)并超過了某個閾值裂逐,就會退化成 UNHEALTHY。
Observability
這里再來說說 Observability 的模式胆剧。對于分布式系統(tǒng)來說絮姆,不同 component 之間的交互并不是同步的醉冤,我們會面臨如下幾種情況:
如果兩個組件 C1 和 C2 是同步交互秩霍,那么當(dāng) C1 給 C2 發(fā)送請求,我們就完全能在 C1 這一端知道這次請求成功還是失敗了蚁阳,但是對于非同步的情況铃绒,我們可能面臨一個問題,就是 C1 給 C2 發(fā)了請求螺捐,但其實這個請求是放到了異步消息隊列里面颠悬,但 C1 覺得是成功了矮燎,可是后面的異步隊列卻失敗了。所以 Panorama 需要有機(jī)制能正確處理上面多種情況赔癌。
為了能更好的從 component 上面得到有用的 observations诞外,Panorama 會用一個離線工具對代碼進(jìn)行靜態(tài)分析,發(fā)現(xiàn)一些關(guān)鍵的地方灾票,注入鉤子峡谊,這樣就能去匯報 observations 了。
通常運行時錯誤是非常有用能證明有故障的證據(jù)刊苍,但是既们,并不是所有的錯誤都需要匯報,Panorama 僅僅會關(guān)系跨 component 邊界產(chǎn)生的錯誤正什,因為這也是通過發(fā)起請求端能觀察到的啥纸。Panorama 對于這種跨域的函數(shù)調(diào)用稱為 observation boundaries。對于 Panorama 來說婴氮,第一件事情就是定位 observation boundaries斯棒。通常有兩種 boundaries,進(jìn)程間交互和線程間交互莹妒。進(jìn)程間交互通常就是 socket I/O名船,RPC,而線程間則是在一個進(jìn)程里面跨越線程的調(diào)用旨怠。這些 Panorama 都需要分析出來渠驼。
當(dāng)定位了 observation boundaries 之后,下一件事情就是確定 observer 和 subject 的標(biāo)識鉴腻。譬如對于進(jìn)程間交互的 boundaries迷扇,observer 的標(biāo)識就可能是這個進(jìn)程在系統(tǒng)里面的唯一標(biāo)識,而對于 subject爽哎,我們可以用 method 名字蜓席,或者是函數(shù)的一個參數(shù),類里面的一個字段來標(biāo)識课锌。
然后我們需要去確定 observation points厨内,也就是觀測點。通常這些點就是代碼處理異常的地方渺贤,另外可能就是一些正常處理返回結(jié)果但會對外報錯的地方雏胃。
上面就是一個簡單分析代碼得到 observation points 的例子,但這個仍然是同步的志鞍,對于 indirection 的瞭亮,還需要額外處理。
對于異步請求固棚,我們知道统翩,通過發(fā)出去之后仙蚜,會異步的處理結(jié)果,所以這里分為了兩步厂汗,叫做 ob-origin 和 ob-sink委粉。如下:
對于 ob-origin,代碼分析的時候會先給這個 observation 設(shè)置成 PENDING 狀態(tài)娶桦,只有對應(yīng)的 ob-sink 調(diào)用并且返回了正確的結(jié)果艳丛,才會設(shè)置成 HEALTHY。因為 ob-origin 和 ob-sink 是異步的趟紊,所以代碼分析的時候會加上一個特殊的字段氮双,包含 subject 的標(biāo)識和 context,這樣就能讓 ob-origin 和 ob-sink 對應(yīng)起來霎匈。
小結(jié)
上面大概介紹了 Panorama 的架構(gòu)以及一些關(guān)鍵的知識點是如何實現(xiàn)的戴差,簡單來說,就是在一些關(guān)鍵代碼路徑上面注入 hook铛嘱,然后通過 hook 對外將相關(guān)的狀態(tài)給匯報出去暖释,在外面會有其他的分析程序?qū)δ玫降臄?shù)據(jù)進(jìn)行分析從而判定系統(tǒng)是否在正常工作。它其實跟加 metrics 很像墨吓,但 metrics 只能看出哪里出現(xiàn)了問題球匕,對于想更細(xì)致定位具體的某一個問題以及它的上下文環(huán)境,倒不是特別的方便帖烘。這點來說 Panorama 的價值還是挺大的亮曹。
Panorama 的代碼已經(jīng)開源,總的來說還是挺簡單的秘症,但我沒找到核心的代碼分析照卦,注入 hook 這些,有點遺憾乡摹。但理解了大概原理役耕,其實先強(qiáng)制在代碼寫死也未嘗不可。另一個比較可行的辦法就是進(jìn)行在代碼里面把日志添加詳細(xì)聪廉,這樣就不用代碼注入了瞬痘,而是在外面寫一個程序來分析日志,其實 Panorama 代碼里面提供了日志分析的功能板熊,為 Zookeeper 來設(shè)計的框全,但作者自己也說到,分析日志的效果比不上直接在代碼里面進(jìn)行注入邻邮。
那對我們來說竣况,有啥可以參考的呢克婶?首先當(dāng)然是這一套故障檢查的理念筒严,既然 Panorama 已經(jīng)做出來并且能發(fā)現(xiàn)故障量丹泉,自然我們也可以在 TiDB 里面實施。因為我們已經(jīng)有在 Go 和 Rust 代碼里面使用 fail 來進(jìn)行錯誤注入的經(jīng)驗鸭蛙,所以早起手寫監(jiān)控代碼也未嘗不可摹恨,但也可以直接完善日志,提供一個程序來分析日志就成娶视。如果你對這塊感興趣晒哄,想把 Panorama 相關(guān)的東西應(yīng)用到 TiDB 中來,歡迎聯(lián)系我 tl@pingcap.com肪获。