分享一個頗為曲折的故事肛跌。
一、背景
早在2016年的時候钢猛,我實現(xiàn)了一個監(jiān)控系統(tǒng)伙菜,自動檢查數(shù)據(jù)平臺各節(jié)點的基礎數(shù)據(jù)是否一致。
可是命迈,這個僅僅是監(jiān)控系統(tǒng)贩绕,用于檢驗緩存實時更新功能的正確性。
2019年春節(jié)前夕壶愤,部門提出要做一個萬能的通用的自愈系統(tǒng)淑倾。
當時各種腦暴討論,討論到最后征椒,發(fā)現(xiàn)要做到萬能與通用娇哆,這個自愈系統(tǒng)就需要與業(yè)務無關,也就變成了一個狀態(tài)機模式的調度系統(tǒng)勃救。
而當時周圍還沒有任何一個自愈相關的實踐碍讨,大家不僅希望萬能通用,還希望與業(yè)務有關系剪芥,后來大部分人都有新的項目了垄开,這件事便不了了之了。
年前的時候税肪,我們團隊的服務遇到一個問題溉躲,然后做了一個實實在在的自動發(fā)現(xiàn)與自動修復系統(tǒng),為自動修復積攢了不少經(jīng)驗益兄,下面分享給大家锻梳。
二、幾年后出問題了
還是上面的數(shù)據(jù)平臺净捅,基礎數(shù)據(jù)通過內部設計的一套通知機制疑枯,幾乎做到數(shù)據(jù)完全一致。
而對于非基礎數(shù)據(jù)蛔六,比如第三方儲存或服務提供的數(shù)據(jù)荆永,無法走內部這一套通知機制废亭。
這部分數(shù)據(jù)修改后,生效時間會比較久具钥。
為了加速第三方服務的生效時間豆村,第三方服務也復用了內部的通知機制。
但是這樣有一個問題骂删。
緩存服務收到更新通知后掌动,會去第三方服務拉最新數(shù)據(jù),此時第三方服務有很小的概率返回舊數(shù)據(jù)宁玫。
這導致第三方服務數(shù)據(jù)不一致問題小概率性出現(xiàn)粗恢。
……
巧妙的是,以前底層cms的很多計算邏輯都是通過各種腳本定時完成的欧瘪。
這使得每計算一個數(shù)據(jù)眷射,都會觸發(fā)一次寫操作。
這種多次寫恰好修復了這個不一致問題恋追。
因為第一次寫的時候凭迹,第三方服務會有小概率計算出舊數(shù)據(jù)。
幾秒后第二次寫的時候苦囱,第三方服務依賴的下游是舊數(shù)據(jù)的概率就非常小了嗅绸。
實際情況時,會寫很多很多次撕彤,所以概率被無限縮小鱼鸠。
PS:對于后面的重復寫,大家可以理解為第三方服務計算的新數(shù)據(jù)沒變化羹铅,但是緩存認為有變化蚀狰,再次去拉取第三方服務。
就這樣第三方服務運行了好幾年职员,幾乎沒出現(xiàn)什么問題麻蹋。
……
不幸的是,春節(jié)的前幾周焊切,底層cms升級改造正式上線扮授,所有計算邏輯只會寫一次。
這使得第三方服務問題暴露出來专肪,被無數(shù)運營投訴刹勃。
讓底層cms暫時回滾是行不通的。
對數(shù)據(jù)系統(tǒng)的架構進行重構嚎尤,使這個第三方服務支持快速更新荔仁,短期內也沒那個時間。
所以做一個自愈系統(tǒng)就顯得非常有必要了。
三乏梁、自愈系統(tǒng)架構
簡單思考下次洼,自愈系統(tǒng)大概分為三大模塊:數(shù)據(jù)輸入模塊、數(shù)據(jù)拉取模塊掌呜、數(shù)據(jù)對比修復模塊滓玖。
如下圖
數(shù)據(jù)輸入模塊一般是從消息隊列接收消息。
這里可能還需要對輸入的數(shù)據(jù)進行過濾质蕉、標準化等預處理邏輯。
最終將需要監(jiān)控的數(shù)據(jù)放入任務隊列翩肌。
由于不同任務需要等待不同的時間才能啟動檢查模暗。
任務隊列可以是一個按處理時間排序的列表。
數(shù)據(jù)拉取模塊每次從任務隊列頂部檢查是否有到達時間的任務念祭。
有了取出兑宇,先拉取基準數(shù)據(jù)(認為是正確的),再拉待校驗的數(shù)據(jù)(可能需要拉很多接口的數(shù)據(jù))粱坤。
當然隶糕,這里與數(shù)據(jù)輸入一樣,需要對拉取的結果進行過濾與標準化站玄。
之后就是對比數(shù)據(jù)是否一致枚驻,不一致了調用修復接口進行修復。
上面就是一個自愈系統(tǒng)簡化后的模型株旷。
四再登、加強版自愈
年前的時候,讓一個同事做了這樣一個系統(tǒng)晾剖。
那個版本為了快速測試流程锉矢,很多參數(shù)是 hardcode 的。
我簡單的 codeview 了架構流程齿尽,看著沒啥問題沽损。
后面我提出一個要求:這些參數(shù)需要配置化。
于是相關參數(shù)被改成配置文件讀取后循头,就直接發(fā)布上線了绵估。
上線后的一個月內,運營也都沒有來反饋問題了贷岸。
……
可是壹士,半個月前,運營突然又大面積反饋這個問題了偿警。
我心中有一個很大的疑惑躏救。
如果自愈系統(tǒng)有問題,一個月前就應該不斷的遇到問題。
如果自愈系統(tǒng)沒問題盒使,這些問題就應該被自動發(fā)現(xiàn)自動修復崩掘。
難道僅僅是概率問題?
于是我同時要到 自愈系統(tǒng)和 第三方系統(tǒng)的代碼少办,進行 codereview苞慢。
然后發(fā)現(xiàn)第三方系統(tǒng)存在兩個問題,自愈系統(tǒng)存在一個過濾問題英妓。
將問題反饋給相關負責人后挽放,第三方系統(tǒng)的問題被修復了一個,自愈系統(tǒng)的過濾問題也被修復了蔓纠。
但是運營依舊在投訴辑畦,這說明問題依舊存在。
此時腿倚,我們正處于組織架構調整期纯出。
第三方系統(tǒng) 和 自愈系統(tǒng)的負責人都去做其他新項目去了。
問題還是需要解決敷燎,于是我開始接手這兩個服務了暂筝。
……
接手后需要做兩件事情。
第一件事是修復第三方系統(tǒng)遺留的那個已知問題硬贯。
第二件事是分析自愈系統(tǒng)為啥沒有發(fā)現(xiàn)問題焕襟、修復問題。
由于數(shù)據(jù)節(jié)點眾多澄成,目前自愈系統(tǒng)檢查節(jié)點數(shù)據(jù)的邏輯是抽樣拉取的胧洒。
分析了之前有問題的數(shù)據(jù),如果數(shù)據(jù)有問題墨状,是必現(xiàn)的卫漫。
難道剛開始那幾秒,數(shù)據(jù)在反復變化肾砂?
于是我猜想列赎,一次抽樣可能發(fā)現(xiàn)不了問題,全部計算量又太大镐确。
一種不錯的方法是有策略的多次檢查包吝。
最常見的策略有:等差策略、指數(shù)策略源葫。
等差策略就是每隔多少秒觸發(fā)一次檢查诗越。
比如第5、10息堂、15嚷狞、20块促、25、30秒檢查床未。
指數(shù)策略就是每次間隔時間翻倍竭翠。
比如第5、10薇搁、20斋扰、40、80啃洋、160秒檢查传货。
我對這兩個策略都不是很滿意,因為時間間隔的太近了宏娄。
于是我引入了階乘策略损离,即相乘的因子每次加一。
比如第5绝编、10、30貌踏、120十饥、600、3600秒檢查祖乳。
算法確定后逗堵,就是代碼實現(xiàn)了。
將算法封裝在一個對象內后眷昆,實現(xiàn)還算簡單蜒秤,很快我就上線了。
當我分析策略的正確性時亚斋,我驚呆了作媚。
數(shù)據(jù)拉取模塊竟然有一個隱藏很深的BUG,使得結果永遠都被認為是一致的帅刊。
自此纸泡,我前面提到的疑惑算是得到了解釋,確實是概率問題赖瞒。自愈系統(tǒng)從來沒正常執(zhí)行過女揭。
問題修復后,運營果然幾乎不反饋問題了栏饮。
后來他們又反饋了一個問題吧兔,分析之后,發(fā)現(xiàn)是新功能不在監(jiān)控范圍之內袍嬉,我補充進去后境蔼,然后到現(xiàn)在為止再也沒收到反饋了。
五、回顧
回顧一下這個自愈系統(tǒng)欧穴,整個流程大概確定了民逼。
大概如下:
1、MQ 輸入任務涮帘、過濾拼苍、標準化
2、有策略的進入任務隊列
3调缨、調度任務拉取基準數(shù)據(jù)與對比數(shù)據(jù)疮鲫,結果標準化
4、任務結果對比弦叶,不一致時進行自愈修復
疑惑解開之前俊犯,我引入了有策略的多輪檢查機制來確保問題被發(fā)現(xiàn)與修復。
而最終的問題竟然是一個隱藏的BUG伤哺。
這個問題屬于邏輯BUG燕侠,只能引入單元測試來發(fā)現(xiàn)。
由于涉及到各種接口網(wǎng)絡調用立莉,還需要使用 MOCK 鉤子來解決依賴绢彤。
單元測試和MOCK鉤子我在個別項目中用到過,后面有時間了蜓耻,也引入到這個項目來茫舶。
到時候再給大家分享一下單元測試的實踐與MOCK鉤子的實踐。
《完》
-EOF-