本文是蘭建剛分享餓了么服務治理經驗。
蘭建剛,餓了么框架部門技術總監,前愛立信首席軟件工程師,10 年以上高可用性,高并發系統架構設計經驗。現餓了么框架工具部負責人,負責餓了么中間件的設計及實施,通過中間件以及研發工具的輔助提升研發人員的工作效率,提升網站的穩定性及性能。
今天我想站在一個大的角度上,看一下餓了么最近一年多的時間,經歷的技術上一些痛苦的問題與改進的過程。
為什么講比較痛苦的事情?昨天和一位專家聊天受益很大,他說人在什么時候能夠自我驅動?就是痛苦的時候。 只有感到痛苦,才會有改變。 當然改變有兩種結果,一種是徹底放棄沉淪,另外就是一想辦法自動化、智能化,把自己變成一個高手。
我現在也很痛苦,但是還沒有放棄。先講一下 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 框架里面需要包含什么? 首先必須包含 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 灰度發布的系統,叫 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%,但是我們規定了一臺服務器只能裝一個服務怎么辦,那就上容器吧。
蘭建剛:準確度的確是個問題,我們用了一個算法叫 3-sigma,準確度還不是特別確定,因為這個東西真的是服務治理里面最大的難題,報警分級怎么分?這是很大的學問,我們現在整個報警系統里面報警通道每天上千個報警,很多都不看的,因為覺得這個報警沒什么意義。這是一個實際當中要去調整的問題。
蘭建剛:對,你要知道我們的業務有兩個明顯的尖峰,十二點和下午四五點的時候都是訂餐的高峰,之后則所有的指標都會有下降趨勢的時候,如果你曲線偏離的很厲害就會引發報警。
多指標聚合是我們正在做的,發生一個指標報警的時候可能是一個小問題,但是這個問題會觸發一個 CEP 的流程,比如“是不是 CPU 飆高的同時響應時間會抖動?”,我們可以定義這樣整個一套規則,去做報警來提高準確度。
蘭建剛:當然是底層的基礎服務了,我們不建議用 Go 寫業務。為什么我們選 Go 做工具,是因為我前面提到,公司原來一些工具是搭在 Python 上,有一幫 Python 工程師,讓他去寫 Java 他是絕對不干的,但是讓他去寫 Go 語言是沒有問題的,Python 其實不適合寫底層框架,因為它是個動態語言,工程化方面也會差一點。
蘭建剛:就是因為遇到故障了,因為很多超時配得很亂,有的同學直接配 3 秒超時(這是配置模版里的一個例子,很多同學就拿去直接用了),那還不如不配,有些情況很多服務就是 10ms 就正常返回了。只要保證這件事情對絕大部分的服務來說,是有利可圖的,那我們就去做這件事情。
文章轉載請保留網址:http://aberdeenanguscattle.com/news/industry/1747.html