戰狼:業務高速增長,如何保證系統高可用

背景

2017年8月25日,我懷著「再也不要在下班時間收到報警」的美好期待,加入美團金融智能支付負責核心交易,結果入職後收到的報警一天緊似一天。核心交易是整個智能支付的核心鏈路,承擔著智能支付百分之百的流量,不敢有絲毫的懈怠。下面是我們的日單量增長曲線:

從圖中可以看到從17年下半年開始,我們的日單量增長迅速,而且壓力和流量在午、晚高峰時段非常集中。在這種情況下,報警和小事故日益頻繁,交易的穩定性面臨著嚴峻的考驗。下面是早期的可用性趨勢圖,仔細看的話,可以看到可用性有下降的趨勢,旁邊的總可用性顯示只有4個9(99.998765%),美團點評排在第一的價值觀是「以客戶為中心」,顯然對交易這種穩定性要求非常高的系統,低於5個9是不可容忍的。

啟動排查

在很長時間裡,我們做的只是在原有系統上增加功能,架構上沒有大調整。但是隨著業務增長,就算我們系統沒有任何發版升級,也會突然出現一些事故。事故出現的頻率越來越高,我們自身的升級,也經常是困難重重。基礎設施升級、上下游升級,經常會發生「蝴蝶效應」,毫無徵兆的受到影響。以下是這些現象的魚骨圖分析:

為了保證交易的高可用,智能支付技術團隊快速整合平台和集團技術資源,成立了專題項目組——「戰狼」,聚焦支付技術底層基礎,排查系統風險點和系統問題,全力為智能支付商戶與客戶提供一個良好、安全、順暢的支付體驗。使命必達,為核心交易系統保駕護航!

發現問題

通過排查,我們發現了系統的主要問題,從大方面說就是:外部的問題和自身的問題,當然癥結在於架構的問題。問題分類如下圖所示:

值得一提的是:如果我們業務量沒有上來,這些問題其實本不是問題。系統剛搭建完成好比建造了一個「土房子」,考慮自己能在裡面住就可以了,而發展到四合院,就要考慮東廂、西廂等一些布局的問題,再發展到一個小區,就涉及內部道路、配套等等,再大一點發展成一個城市,就要涉及要各種保障工作,比如下雨了怎麼排水,進城要設置哪些關卡,哪些道路要放置監控等。

系統建設也是這樣,一個用戶基數小、日活少的系統,要考慮的主要是完成功能,隨著業務量增大,就要開始涉及系統間的問題,接著去發展一些基礎設施、共通組件等,而我們目前的局面是,系統已經擴大到了「城市」的規模,我們要解決更繁重的問題。

分析問題

首先我們要對目前所面臨的問題進行分析。

事務中包含外部調用

外部調用包括對外部系統的調用和基礎組件的調用。它具有返回時間不確定性的特徵,必然會造成大事務。大的資料庫事務會造成其它對資料庫連接的請求獲取不到,那麼和這個資料庫相關的所有服務都很可能處於等待狀態,造成連接池被打滿,多個服務直接宕掉。如果這個沒做好,危險指數五顆星。下面的圖顯示出外部調用時間的不可控:

超時時間和重試次數不合理

對外部系統和緩存、消息隊列等基礎組件的依賴,如果超時時間設置過長、重試過多,系統長時間不返回,可能會導致連接池被打滿,系統死掉;如果超時時間設置過短,499錯誤會增多,系統的可用性會降低。如果超時時間設置得短,重試次數設置得多,會增加系統的整體耗時;如果超時時間設置得短,重試次數設置得也少,那麼這次請求的返回結果會不準確。波士頓矩陣分析圖如下:

舉個例子:

服務A依賴於兩個服務的數據完成此次操作。平時沒有問題,假如服務B在你不知道的情況下,響應時間變長,甚至停止服務,而你的客戶端超時時間設置過長,則你完成此次請求的響應時間就會變長,此時如果發生意外,後果會很嚴重。

Java的Servlet容器,無論是Tomcat還是Jetty都是多線程模型,都用worker線程來處理請求。這個可配置有上限,當你的請求打滿worker線程的最大值之後,剩餘請求會被放到等待隊列。等待隊列也有上限,一旦等待隊列都滿了,那這台Web

Server就會拒絕服務,對應到Nginx上返回就是502。如果你的服務是QPS較高的服務,那基本上這種場景下,你的服務也會跟著被拖垮。如果你的上游也沒有合理的設置超時時間,那故障會繼續向上擴散。這種故障逐級放大的過程,就是服務雪崩效應。

外部依賴的地方沒有熔斷

在依賴的服務不可用時,服務調用方應該通過一些技術手段,向上提供有損服務,保證業務柔性可用。而系統沒有熔斷,如果由於代碼邏輯問題上線引起故障、網路問題、調用超時、業務促銷調用量激增、服務容量不足等原因,服務調用鏈路上有一個下游服務出現故障,就可能導致接入層其它的業務不可用。下圖是對無熔斷影響的魚骨圖分析:

對於依賴我們的上游沒有限流

在開放式的網路環境下,對外系統往往會收到很多有意無意的惡意攻擊,如DDoS攻擊、用戶失敗重刷。雖然我們的隊友各個是精英,但還是要做好保障,不被上游的疏忽影響,畢竟,誰也無法保證其他同學哪天會寫一個如果下游返回不符合預期就無限次重試的代碼。這些內部和外部的巨量調用,如果不加以保護,往往會擴散到後台服務,最終可能引起後台基礎服務宕機。下圖是對無限流影響的問題樹分析:

慢查詢問題

慢查詢會降低應用的響應性能和並發性能。在業務量增加的情況下造成資料庫所在的伺服器CPU利用率急劇攀升,嚴重的會導致資料庫不響應,只能重啟解決。關於慢查詢,可以參考我們技術博客之前的文章《MySQL索引原理及慢查詢優化》。

依賴不合理

每多一個依賴方,風險就會累加。特別是強依賴,它本身意味著一榮俱榮、一損俱損。

廢棄邏輯和臨時代碼

過期的代碼會對正常邏輯有干擾,讓代碼不清晰。特別是對新加入的同事,他們對明白乾什麼用的代碼可以處理。但是已經廢棄的和臨時的,因為不知道幹什麼用的,所以改起來更忐忑。如果知道是廢棄的,其它功能改了這一塊沒改,也有可能這塊不兼容,引發問題。下圖是William Pietri(曾任Code for America的研發總監)定義的代碼類別,不持續維護的代碼從長期成本來說是很高的:

沒有有效的資源隔離

容易造成級聯多米諾骨牌效應。

針對上述問題,我們提出了解決方案。

解決問題

1. 事務中不包含外部調用

  • 排查各個系統的代碼,檢查在事務中是否存在RPC調用、HTTP調用、消息隊列操作、緩存、循環查詢等耗時的操作,這個操作應該移到事務之外,理想的情況是事務內只處理資料庫操作。
  • 對大事務添加監控報警。大事務發生時,會收到郵件和簡訊提醒,一般的報警標準是1s。
  • 建議不要用XML配置事務,而採用註解的方式。原因是XML配置事務,第一可讀性不強,第二切面通常配置的比較泛濫,容易造成事務過大,第三對於嵌套情況的規則不好處理。

2. 超時時間設置合理和重試次數

  • 首先要調研被依賴服務自己調用下游的超時時間是多少。調用方的超時時間要大於被依賴方調用下游的時間。
  • 統計這個介面99%的超時時間是多少,設置的超時時間在這個基礎上加50%。
  • 重試次數如果系統服務重要性高,則按照默認,一般是重試三次。否則,可以不重試。

3. 外部依賴的地方都要做熔斷

  • 自動熔斷:可以使用Netflix的Hystrix或者美團點評自己研發的Rhino來做快速失敗。
  • 手動熔斷:確認下游支付通道抖動或不可用,可以手動關閉通道。

4. 對於依賴我們的上游要限流

  • 通過對服務端的業務性能壓測,可以分析出一個相對合理的最大QPS。
  • 可以使用Netflix的Hystrix或者美團點評自己研發的Rhino來限流。

5. 解決慢查詢問題

  • 將查詢分成實時查詢、近實時查詢和離線查詢。實時查詢可穿透資料庫,其它的不走資料庫,可以用Elasticsearch來實現一個查詢中心,處理近實時查詢和離線查詢。
  • 讀寫分離。寫走主庫,讀走從庫。
  • 索引優化。索引過多會影響資料庫寫性能。索引不夠查詢會慢。

像核心交易這種資料庫讀寫TPS差不多的,一般建議索引不超過4個。如果這還不能解決問題,那很可能需要調整表結構設計了。

  • 對慢查詢對應監控報警。我們這邊設置的慢查詢報警閾值是100ms。

6. 能去依賴就去依賴,否則盡量同步強依賴改成非同步弱依賴

  • 劃清業務邊界,只做該做的事情。
  • 如果依賴一個系統提供的數據,上游可以作為參數傳入或者下游可以作為返回值返回,則可以下線專門去取數據的邏輯,盡量讓上下游給我們數據。
  • 我們寫入基礎組件,數據提供給其它端,如果其它端有兜底策略,則我們可以非同步寫入,不用保證數據100%不丟失。

7. 精簡代碼邏輯

  • 梳理每個介面的調用情況,對於沒有調用量的介面,確認不再使用後及時下線。
  • code review保證每段邏輯都明白其含義,弄清楚是否是歷史邏輯或者臨時邏輯。

8. 核心路徑進行資源隔離

伺服器物理隔離原則

① 內外有別:內部系統與對外開放平台區分對待。

② 內部隔離:從上游到下游按通道從物理伺服器上進行隔離,低流量服務合併。

③ 外部隔離:按渠道隔離,渠道之間互不影響。

線程池資源隔離

  • Hystrix通過命令模式,將每個類型的業務請求封裝成對應的命令請求。每個命令請求對應一個線程池,創建好的線程池是被放入到ConcurrentHashMap中。

注意:儘管線程池提供了線程隔離,客戶端底層代碼也必須要有超時設置,不能無限制的阻塞以致於線程池一直飽和。

信號量資源隔離

  • 開發者可以使用Hystrix限制系統對某一個依賴的最高並發數,這個基本上就是一個限流策略。每次調用依賴時都會檢查一下是否到達信號量的限制值,如達到,則拒絕。

除了上面的措施之外,戰狼項目還進行了很有成效的兩地三中心機房互備、組件安全漏洞修復和服務健康驗證,限於篇幅,本文就不詳述了。《Clean

Code》一書里說溝通是專業開發者的頭等大事,我個人也認為它可以說是項目過程中最重要的環節。沒有很好的溝通,就好像是越走越遠的兩個人,我在等著你回心轉意,你卻在等著自己死心,你發現心死不了,我卻已經放棄了等待。永遠在平行線上沒有交集,只是徒增苦痛,沒有意義。

實施後的效果

通過這場「飛行中換引擎」的戰狼項目,線上事故急劇減少,基本上維持在零事故。監控報警從常態變成偶爾發生。為了系統性地檢驗效果。我們進行了多次故障演練。

演練效果符合預期,系統穩定性符合標準。這過程中有很多收穫和樂趣,比如第一次演練的時候,把緩存掛掉,系統竟然跟著掛了。所以以後的每次演練都覺得這是很重要的事情。

就好像我3年前從一家公司離職,當時因為是冬天好多人感冒了,咳嗽。我就開玩笑跟我搭檔說:「去了新公司聽不到咳嗽聲了會不會不適應。」搭檔跟我說:「到了新公司肯定還會出現專門咳嗽給你聽的人。」所以從此,我在辦公室里聽到咳嗽聲,都會朝自己笑一笑。一件不經意的小事,讓本來很多不是好事的事情變得有意義。「戰狼」項目,開始是由於故障和報警引起的,肯定算不上是好事。但是這也催生出了「戰狼」這樣意義重大,影響深遠的項目。

近期我們的可用性除了因為一次下游通道抖動引起的可用性6個9之外,其它時間可用性均為100%。下面為可用性趨勢圖。

持續跟進

我們優化了業務大盤、故障大盤。加強了監控報警機制,持續監控和保障著系統的穩定性。故障演練也作為了定時的日常工作來做。穩定性需要建立長期規範,維護組內的checklist,定期檢查是否達到標準。checklist舉例如下:  

項目總結

我們家老大是像星星一樣散發著智慧的人,他給我們總結系統穩定性的三個要素:第一是別人死我們不死,第二是不自己作死,第三是不被豬隊友搞死。

穩定性具體的實施方法總結一下就是:能不依賴就去依賴,儘可能將強依賴轉成弱依賴,實在不能降低依賴就保護依賴,做好自保,出了問題只能收斂不能擴大,對危險要能監控。

線上支付平台總結的穩定性「四板斧」:研發規範、自身穩定、容錯下游、防禦上游。

經過為期4周的戰狼項目,多個小組緊密合作,日夜兼程,高效地完成了一個又一個攻堅任務,保證了交易系統的穩定。整個項目收穫的不僅是一套穩定的系統,更重要的是通過一次次的激烈探討,一場場集體推進會,總結出了一套通用的系統穩定提升方法,同時也鍛鍊出一隻充滿戰鬥力的隊伍,為整個支付業務快速穩定發展奠定了基礎。

所謂生於憂患,死於安樂。歷史上,重大危機總是發生在承平日久之後。戰狼項目雖然已經結束,但是戰狼精神永存。我們要時時刻刻居安思危,保持穩定第一。

工具介紹

項目中多次提到使用Hystrix和Rhino。所以這裡對它們做一個簡單的介紹。

Hystrix

Hystrix實現了斷路器模式來對故障進行監控,當斷路器發現調用介面發生了長時間等待,就使用快速失敗策略,向上返回一個錯誤響應,這樣達到防止阻塞的目的。這裡重點介紹一下Hystrix的線程池資源隔離和信號量資源隔離。

線程池資源隔離

優點

  • 使用線程可以完全隔離第三方代碼,請求線程可以快速放回。
  • 當一個失敗的依賴再次變成可用時,線程池將清理,並立即恢復可用,而不是一個長時間的恢復。
  • 可以完全模擬非同步調用,方便非同步編程。

缺點

  • 線程池的主要缺點是它增加了CPU,因為每個命令的執行涉及到排隊(默認使用SynchronousQueue避免排隊),調度和上下文切換。
  • 對使用ThreadLocal等依賴線程狀態的代碼增加複雜性,需要手動傳遞和清理線程狀態(Netflix公司內部認為線程隔離開銷足夠小,不會造成重大的成本或性能的影響)。

信號量資源隔離

開發者可以使用Hystrix限制系統對某一個依賴的最高並發數。這個基本上就是一個限流策略,每次調用依賴時都會檢查一下是否到達信號量的限制值,如達到,則拒絕。

優點

  • 不新起線程執行命令,減少上下文切換。

缺點

  • 無法配置斷路,每次都一定會去嘗試獲取信號量。

比較一下線程池資源隔離和信號量資源隔離

  • 線程隔離是和主線程無關的其他線程來運行的;而信號量隔離是和主線程在同一個線程上做的操作。
  • 信號量隔離也可以用於限制並發訪問,防止阻塞擴散,與線程隔離的最大不同在於執行依賴代碼的線程依然是請求線程。
  • 線程池隔離適用於第三方應用或者介面、並發量大的隔離;信號量隔離適用於內部應用或者中間件;並發需求不是很大的場景。

Rhino

Rhino是美團點評基礎架構團隊研發並維護的一個穩定性保障組件,提供故障模擬、降級演練、服務熔斷、服務限流等功能。和Hystrix對比:

  • Hystrix組件在熔斷之後,並在試探線程成功之後,就直接關閉熔斷開關,全部流量走正常邏輯。而Rhino會對流量進行恢復灰度。
  • 內部通過CAT(美團點評開源的監控系統,參見之前的博客「深度剖析開源分散式監控CAT」)進行了一系列埋點,方便進行服務異常報警。
  • 接入配置中心,能提供動態參數修改,比如強制熔斷、修改失敗率等。

關於作者

曉靜,20歲時畢業於東北大學計算機系。在畢業後的第一家公司由於出眾的語言天賦,在1年的時間裡從零開始學日語並以超高分通過了國際日語一級考試,擔當兩年日語翻譯的工作。後就職於人人網,轉型做互聯網開發。中國科學院心理學研究生。有近百個技術發明專利,創業公司合伙人。有日本東京,美國矽谷技術支持經驗。目前任美團點評技術專家,負責核心交易。

歡迎關注靜兒的個人技術公眾號:編程一生

weixin.qq.com/r/FymBmRn (二維碼自動識別)

招賢納士

美團金融核心交易大量招收Java高級工程師、技術專家。高速發展的業務需要高速發展的團隊,作為核心部門,我們急需相信技術改變世界的你!有意者請關注我的個人技術公眾號並留言:-)

我們的老大很有想法,團隊氛圍一級棒,歡迎加入我們的大家庭哦~

回答「思考題」、發現文章有錯誤、對內容有疑問,都可以來微信公眾號(美團點評技術團隊)後台給我們留言。我們每周會挑選出一位「優秀回答者」,贈送一份精美的小禮品。快來掃碼關注我們吧!

weixin.qq.com/r/9HVSSg3 (二維碼自動識別)


推薦閱讀:

來自滴滴、微博、唯品會、魅族、點評關於高可用架構的實踐分享
高性能負載均衡設計與實現
高性能、高可用、可擴展的MySQL集群如何組建?
全局唯一ID在分散式系統中用來做什麼用?
探究高可用服務端架構的優秀資料索引

TAG:系統 | 架構 | 高可用 |