分散式服務治理及優化經驗
導讀:高可用架構 7 月 30 日在上海舉辦了『網際網路架構的基石』專題沙龍,進行了閉門私董會研討及對外開放的四個專題的演講,期望能促進業界對網際網路基礎架構的建設及發展,本文是蘭建剛分享餓了麼服務治理經驗。
蘭建剛,餓了麼框架部門技術總監,前愛立信首席軟體工程師,10 年以上高可用性,高併發系統架構設計經驗。現餓了麼框架工具部負責人,負責餓了麼中介軟體的設計及實施,通過中介軟體以及研發工具的輔助提升研發人員的工作效率,提升網站的穩定性及效能。
今天我想站在一個大的角度上,看一下餓了麼最近一年多的時間,經歷的技術上一些痛苦的問題與改進的過程。
為什麼講比較痛苦的事情?昨天和一位專家聊天受益很大,他說人在什麼時候能夠自我驅動?就是痛苦的時候。只有感到痛苦,才會有改變。
當然改變有兩種結果,一種是徹底放棄沉淪,另外就是一想辦法自動化、智慧化,把自己變成一個高手。MVP 原則
我現在也很痛苦,但是還沒有放棄。先講一下 MVP 原則,MVP(Minimum Viable Product) 現在比較火,一個產品是做大而全,還是可用就行?我從去年 3 月份加入餓了麼,開始組建框架和工具的團隊。中介軟體裡面很多東西都可以去做,但是我真的需要把所有的東西都做全嗎還是 MVP 原則就好?這是我們思考的一個問題。
MVP 的意思就是做一個最小可用的就可以,大家以前很流行說,“世界那麼大,我想去看看”,其實框架很多東西看看就好,做全做好是需要長時間積累的,我們缺的恰恰是時間。我們要做的就是立足現狀,解決痛點問題。現在餓了麼的現狀說白了比百廢待興好一點。當有太多事情可以去做的情況下,更需要抓住重點,不死人的儘量不要去踏。
服務治理的現狀
服務治理是一個很大的話題,它涵蓋了很多內容,比如前面曉波老師介紹的 Redis 治理、姚捷老師講的鏈路監控系統(參看文末文章),都可以涵蓋在裡面。
程式語言
先介紹語言,剛才會場一些人說他們是異構的語言,但可能還是沒有餓了麼這麼複雜。餓了麼語言主要有兩種,Python 及 Java,原來整個公司語言都是以 Python 為主,可以說是上海最大的 Python 大廠。為什麼不堅持用 Python?不是說 Python 語言不好,而是招不到人。在業務急速發展的時候怎麼辦?換 Java 語言就成了自然的選擇。
在我進公司的時候,其實不僅僅是這兩種語言,還有 PHP,C 語言等。基於這些現狀,框架的選擇點就比較少。因此做了一些妥協,SOA 的框架有兩套,主要是為 Python 和 Java 做的,Python 的叫 Vespense,Java 版本的叫 Pylon,Vespense 和 Pylon 都是星際爭霸裡面的兩種最基本的東西,沒有這兩種東西遊戲根本打不下去。
SOA 框架
SOA 框架裡面需要包含什麼?首先必須包含 RPC,我們的 RPC 有兩種:Thrift 和 JSON。Python 使用 Thrift,Java 使用 JSON。為什麼 Java 框架重新選擇一套 RPC 協議? 主要是覺得 Thrift 對 Java 不太友好。舉個例子,用 Thrift 生成的 Java 程式碼在介面比較多的時候,它的一個檔案就超過 20M ,連 IDE 都拒絕分析這個檔案。另外 JSON 是純文字的,因為當初也沒有日誌系統,也沒有鏈路跟蹤系統,排查問題的時候,一種好的辦法就是抓包,如果是一個二進位制的協議的話那就痛苦。所以最終 Java 選擇了 JSON。當然 RPC 都是對業務透明的,SOA 框架會遮蔽 RPC 細節,業務就像使用本地呼叫一樣使用遠端服務。
路由我們是基於叢集做的,沒有進一步細化到機器級別,因為覺得這個就足夠了。此外也做了客戶端的 SLB,還有熔斷、降級、限流,這是保護服務的幾大法寶,充分證明了這些東西拯救了我們很多次。經常看到監控群裡面說,我們把什麼什麼服務限流了吧,把它降級了吧,那是因為這個服務可能寫的不太好,把它降掉了,保住我們的主要業務。還有一些埋點,全鏈路跟蹤等等,全部都內嵌在框架裡面。這樣的好處就是,增加特性只要升級一個框架就可以了。
服務發現與配置中心
我們的服務發現和配置中心叫 Huskar,這是 DOTA 裡面的一個英雄人物的名字,它是基於 ZooKeeper。
負載均衡
負載均衡有好幾種,首先有嵌在 SDK 裡的軟負載,拿到一個服務全部的列表,做一個輪循就可以了。這種策略有一些不足,比如一個 IDC 裡面機器型號效能可能會稍微不一樣,如果單純用輪循會產生負載不均的問題。但這個問題當前還不是最緊迫的,我們可以繞過去,只要保證同一個叢集裡機器型號都是相同的就好。
此外也用中間層的方式,以前我們的 haproxy 用起來比較麻煩,配置複雜,而且它不能進行熱載入,一個配置上去了之後需要重啟一遍,因此工程師就用 Go 語言寫了一個 GoProxy,基於四層,它會從服務中心把你需要的列表全部抓下來,做四層的負載均衡。他可以代理一些沒有 SOA 框架支援的語言寫的服務,也可以代理其他的基礎元件,比如說像 Redis,資料庫,它都會代理。
CI / CD 灰度釋出
我們有 CI、CD 灰度釋出的系統,叫 eless,雖然我們做了 CD 但是百廢待興,目前只有一些基本的單元測試,因為這個太耗工夫了。灰度釋出也是基於釋出系統做的,我們會在釋出系統裡面定義叢集,每個叢集裡面的機器又分成不同的組,釋出的時候按這些組來發布的,你可以先發一個組,觀察沒有問題後,然後再發其他的組。
監控與報警
我們有自己的監控和報警系統。監控現在做的比較簡單,是 statsd + graphite + grafana 的組合。現在最大的問題是監控系統不支援 tags,所有的指標匯聚到一起,一個服務的指標是匯聚在一起的,一個機器或者叢集慢了,它會把這些指標分攤到其他機器或者叢集上去的,所以查的時候比較困難,所以我們現在準備切成我們自己的系統。
報警系統也是自己寫的。報警系統的需要是快、全、準。我們現在做的是全,逼著大家去把報警系統用起來,只看監控系統是看不過來。如果線上發生了一個故障,比如交換機發生故障,影響到某個業務,但是業務報警沒有報出來,那業務要承擔連帶責任,因為你沒有報警出來。
報警最常見的基於閾值,閾值這件事情比較痛苦,我們有很多指標,但這個閾值怎麼去配,需要很有經驗的人才能配好,閾值配小了,你會經常收到報警,配太大有可能出問題收不到報警,這個非常痛苦。所以一個同事提出基於趨勢來配置來判斷,我們在一段時間發現趨勢在偏離了,就做報警。
我們也在做 trace,前兩天終於把拓撲圖給畫出來,把一個業務所有的呼叫展示成一個呼叫樹,這樣就可以很好的分析業務。我們現在是一個近千人公司,業務系統極度複雜,很少有工程師能清晰說出一個業務到底呼叫了哪些服務,通過這種 trace 方式來做輔助分析就很有用了。
光展示,我們覺得它的價值還沒有利用到極致,可以把所有的呼叫關係和報警結合在一起。大家分析問題的時候,會發現如果某個點上發生的錯誤一直往上報,從而導致整條鏈路失敗,那這個點就是 root cause,把它修復就可以問題解決了。我們做 trace 的思路是一樣的,在呼叫鏈上進行著色,輔助找到問題的 root cause,也就是最初發生問題的那個點。
服務治理的經驗
我們最重要的經驗是做好保護與自我保護。
“不能被爛用的框架不是好框架”,這個是我們 CTO 經常說的一句話。原因是什麼?比如我們那個監控的 SDK 曾經被業務錯誤的使用,每發一次報警就啟一個新執行緒,最後整個程序因為開了太多的執行緒掛掉了。峰哥(CTO)說你無法預測每個開發者會怎麼使用你的框架,即使框架被濫用了,最壞的情況,也需要保證能夠活的下去。
所以後面寫的東西都嚴格要求自我狀態的檢查,比如秒殺的時候,所有的監控系統,鏈路跟蹤系統都是可以降級的,不能因為這些東西導致整個系統崩潰。
框架由於在底層,出了問題最容易被懷疑。比如一個 SDK,使用方說為什麼佔用了整個叢集上 8% 的 CPU?跑過去一看,整個機器的 CPU 才 12%。某種程度做框架其實有無助的時候,容易被質疑及譴責,所以做好自我狀態檢查是很必要的。
定期線上掃描
為了避免濫用的問題,我們會定期線上掃描。比如一些日誌本來就是可以降級可以丟的,但如果開發用了寫檔案的同步方式,那效能就會變慢,通過掃描發現這些問題,改成非同步日誌服務效能就會更好。
限流、熔斷、降級
這個強調多少遍都不過分,因為確實很重要。服務不行的時候一定要熔斷。限流是一個保護自己最大的利器,原來我們前端是用 PHP 做的,沒有自我保護機制,不管有多少連線都會接收,如果後端處理不過來,前端流量又很大的時候肯定就掛了。所以我們做任何框架都會有限流措施。
有個小插曲,我們上 DAL (資料庫中介軟體)第一版的時候,有次一個業務怎麼指標突然降了 50%,然後大家去查,原來 DAL 做了限流,你不能做限流,你把它給我開啟,聽你們的我打開了,打開了然後資料庫的 QPS 瞬間飆到兩萬,業務部門就慌了,趕緊邀請我們再給他限制住。這是我覺得 DAL 做的最好的一個功能。
連線複用
還有連線複用,有些工程師並不能特別理解,如果不用連線池,來一個請求就發一個連線怎麼樣?這樣就會導致後端資源連線過多。對一些基礎服務來說,比如 Redis,資料庫,連線是個昂貴的消耗。所以我們一些中介軟體的服務都實現了連線複用的功能。
程式碼釋出
上線釋出是很危險的一件事情,絕大部分的事故都是由釋出引起的,所以釋出需要跟很多系統結合起來,所以我們做了整套流程。在每次釋出的時候,一個釋出事件開始,到我們這個監控系統以及呼叫鏈上,呼叫鏈就開始分析了,釋出後把它的所有指標比一比,到底哪些指標發生了改變,這些指標如果有異常,就發報警,所有釋出都會打到監控的主屏上面去,如果出了什麼問題,這些事情優先回滾,如果可以回滾,我們肯定第一時間就把問題解決掉了。
服務治理的痛點
配置複雜
超時配置:超時配多少是合適的?100ms?300ms?極端情況有些業務配到 3 秒的。閾值怎麼配和超時怎麼配其實是同一個概念,並不是所有的程式設計師都知道超時設成多少合適。那怎麼辦?峰哥(CTO)想了一個辦法,你的監控系統,你的呼叫鏈分析系統,你的日誌系統,基礎監控系統每天產生多少資料?這些資料到底有沒有用?是否可以從這些資料裡面挖掘出一些東西,比如這種超時的配置,是可以基於它歷史的超時配置、所有請求的響應時間去做的。
這件事情正在進行中,但落地有點麻煩,比如說我們請求大概每天有三千萬的呼叫量,你只有很小的一個比例它會超時,但是絕對量是很大的,你設定一個超時值,它可能有三萬個請求都失敗了。現在一直在優化這個東西,希望下次大家來我們這裡的時候,能給大家詳細介紹一下這個超時到底怎麼做。
執行緒池配置:剛才說最重要的是限流,你不可能無限制的接受請求,不可能一百個併發你就接收一百個併發,併發到底怎麼配?又是很複雜的事情。我經常看我們執行緒池的配置,這個東西要經過嚴格的效能測試,做很多調整才能調出來。在做效能測試的時候,其實有條曲線的,有個最高點的,我們在想在實時的過程中計算出這個最高點,但發現這個東西其實挺難的。
我們便用了另外一種方法,每個執行緒池用一個排列佇列,當我發現它在漲的時候我就適當把那個執行緒池擴大一點,同時我們監測其他指標。如果發現在我擴大併發量的時候這些指標產生了報警,那我就把這個執行緒調整的操作直接拒絕掉,就保持原來那個大小。如果這些指標證明是沒有問題的,那我就把它再擴大一點。
Cache,DB,Queue 的手工配置問題。還有一個是服務治理,Redis、資料庫等配置還都是手工的,我們也不知道我們線上有 Redis,怎麼辦?我們正在做基礎服務的服務化,業務其實不需要關心到底連到哪個 Redis,你上線的時候你告訴我你需要多大的容量,填個工單過來,運維幫你配好了,然後通過一些自動化的方式你把這些拿到初始化 SDK 就可以了。
故障定位
還有一個比較痛的問題就是排查問題很難。首先故障定位困難,每次我們出了事情之後,大家各自查各自的,比較低效。問題排查其實是有方法可以做,需要把它自動化,我們現在還缺這個東西,呼叫鏈分析是需要考慮去做。
效能退化
我們現在的業務增長量非常恐怖,去年我們是以 5 倍的速度增長了,但其實這個 5 - 10 倍要看你的基數,當基數很大,擴一倍量也是非常多,評估線上到底要布多少臺機器是一件很複雜的事情。
我們成立一支效能測試團隊,做全鏈路的壓測。基於全鏈路壓測的結果來評估整個系統的容量。這個全鏈路只能在線上做,也不能在白天壓,只能在晚上低峰期的時候做。所以效能測試也是一個比較挑戰的工作,不僅僅是智力上,也是身體上的一種考驗。
全鏈路壓測試一些服務有時候出現效能下降,比如 QPS 從 500 下降到了 400,但大家並不知道最直接的原因。上次畢洪宇老師也幫我們出了主意,比如把全鏈路指標拉出來做一下對比,看看哪些指標有變化,可能就是罪魁禍首。
容量評估的方法
容量評估方面容易出現溫水煮青蛙的事情,今天流量增長一點沒問題,明天再增長一點也沒有問題,連續幾天然後服務就掛了。這個時候怎麼辦?只能用最苦逼的方法,找個效能測試團隊進行壓測。有沒有更智慧化的方法?我們也正在探尋這條道路。
資源利用率低
資源利用率低的問題。很多團隊一碰到效能下降,就希望擴容,這會導致很多時候機器利用率只有 10%(更新一下資料,其實很多伺服器的利用率不足1%)。我們正在積極準備上容器化方案來解決這個問題。
服務依賴不清晰
大的系統中,服務依賴的呼叫鏈相當複雜,一個業務下去到底呼叫了哪些服務比較難說清。我們已經在做一個泳道規劃。泳道這件事情有多種說法,有的人喜歡所有的服務都做一個大池子,只要保證它的足夠容量就可以了,但是我更傾向小叢集的思路,因為隔離起來就會更安全。
我們現在還沒有做到按使用者來區分泳道,目前只是按流量來切,50% 隨機,部署的東西都一樣。我們想通過泳道把這些流量隔離,VIP 客戶可以把他放最重要的泳道里面,一些不那麼重要的城市,可以放到另一個叢集,如果不得已降級,只能犧牲這些次重要的使用者。
服務治理的方向
有痛點就要努力,要麼放棄,要麼努力,這是我們努力的方向,前面講了一下智慧流控系統,超時推薦我們也做,大資料和智慧化才是將來。有些監控資料只是落在磁碟上不用那就是浪費,是不是能把它利用起來?
然後我們也在做 Cache、資料庫、Queue 等服務化。
Trace 系統我們也在做,拓撲圖畫出來,幫助大家瞭解是怎麼回事,我們可以做鏈路染色,幫你瞭解問題的根源在哪裡,我們也可以做依賴度的分析。我們說依賴分兩種,強依賴和弱依賴。弱依賴要處理它,有一個異常出來的時候要把它幹掉,不能把這個異常跑到最上面去,那整個服務就都掛掉了,但是大家並不知道到底它是弱依賴還是強依賴,這需要分析,我們去統計一下,它是一個強依賴還是弱依賴。然後弱依賴就可以做一些改進,比如做一些非同步呼叫,節省整個服務的呼叫時間,優化使用者體驗。
容量預警我們通過做一些大資料的分析,所有的指標跟訂單量這些關聯,做一個相似度的分析,當這些指標偏離的時候,我們是不是可以認為它的容量有問題,當然這是努力的方向。
容器方面我們也在做,系統叫 APPOS,有的服務 CPU 只用了10%,但是我們規定了一臺伺服器只能裝一個服務怎麼辦,那就上容器吧。
Q&A
提問:想問一下報警閾值設定,上面提到的方法是根據趨勢來設計報警,這個趨勢其實您能做到多少準確?能控制在什麼情況下?
蘭建剛:準確度的確是個問題,我們用了一個演算法叫 3-sigma,準確度還不是特別確定,因為這個東西真的是服務治理裡面最大的難題,報警分級怎麼分?這是很大的學問,我們現在整個報警系統裡面報警通道每天上千個報警,很多都不看的,因為覺得這個報警沒什麼意義。這是一個實際當中要去調整的問題。
提問:報警存在誤報的情況?
蘭建剛:對,你要知道我們的業務有兩個明顯的尖峰,十二點和下午四五點的時候都是訂餐的高峰,之後則所有的指標都會有下降趨勢的時候,如果你曲線偏離的很厲害就會引發報警。
多指標聚合是我們正在做的,發生一個指標報警的時候可能是一個小問題,但是這個問題會觸發一個 CEP 的流程,比如“是不是 CPU 飆高的同時響應時間會抖動?”,我們可以定義這樣整個一套規則,去做報警來提高準確度。
提問:剛才您提到用 Go 寫的一個東西,為什麼選擇使用 Go 因為 Go 是自己的垃圾回收機制?而且我想了解一下您認為 Go 它是比較適合什麼樣的系統?
蘭建剛:當然是底層的基礎服務了,我們不建議用 Go 寫業務。為什麼我們選 Go 做工具,是因為我前面提到,公司原來一些工具是搭在 Python 上,有一幫 Python 工程師,讓他去寫 Java 他是絕對不幹的,但是讓他去寫 Go 語言是沒有問題的,Python 其實不適合寫底層框架,因為它是個動態語言,工程化方面也會差一點。
提問:剛剛看到您提到超時配置推薦,這塊是和你們的應用場景有關係,還是說和你們遇到的故障?
蘭建剛:就是因為遇到故障了,因為很多超時配得很亂,有的同學直接配 3 秒超時(這是配置模版裡的一個例子,很多同學就拿去直接用了),那還不如不配,有些情況很多服務就是 10ms 就正常返回了。只要保證這件事情對絕大部分的服務來說,是有利可圖的,那我們就去做這件事情。
如果您喜歡我寫的博文,讀後覺得收穫很大,不妨小額贊助我一下,讓我有動力繼續寫出高質量的博文,感謝您的讚賞!!!