背景
靜兒在2017年8月25日懷着“再也不要下班時間收到報警”的美好期待加入美團金融智能支付負責核心交易,結果入職后收到的報警一天緊似一天。核心交易是整個智能支付的核心鏈路,承擔着智能支付百分之百的流量。下面是我們的日單量增長曲線:
從圖中可以看到從17年下半年開始,我們的日單量增長迅速,而且壓力和流量在午、晚高峰時段非常集中。在這種情況下,交易的穩定性面臨着嚴峻的考驗。
為了保證交易的高可用,智能支付技術團隊快速整合平台和集團技術資源,成立了專題項目組—“戰狼”,聚焦支付技術底層基礎,排查系統風險點和系統問題,全力為智能支付商戶與客戶提供一個良好、安全、順暢的支付體驗。使命必達,保駕護航!
啟動排查
核心交易上游承接智能支付業務方,我們的產品POS機、小白盒、小黑盒、二維碼和所有通過開放平台接入的商家都通過我們進行收單,下游調用銀行等支付渠道。業務邏輯並不復雜。通過系統梳理,我們發現如下圖所示,不合理邏輯很多。
發現問題
通過排查,我們了解到了我們的系統的主要問題,從大的方面說就是:“自身不強壯,隊友不可靠”。問題分類如下圖所示:
分析問題
1>事務中包含外部調用
外部調用包括對外部系統的調用和基礎組件的調用。它具有返回時間不確定性,必然會造成大事務。大的數據庫事務會造成其他請求數據庫連接獲取不到,那么和這個數據庫相關的所有服務都很可能處於等待狀態,造成連接池被打滿,多個服務直接宕掉。如果這個沒做好,危險指數五顆星。
2>超時時間和重試次數不合理
對外部系統和緩存、MQ等基礎組件的依賴,如果超時時間設置過長、重試過多,系統長時間不返回,可能會導致連接池被打滿,系統死掉;如果超時時間設置過短,499錯誤會增多,系統的可用性會降低。如果超時時間設置的短,重試次數設置的多,會增加系統的整體耗時;如果超時時間設置的短,重試次數設置的也少,那么這次請求的返回結果會不准確。
咱們舉個具體場景來看這個事情
服務A依賴於兩個服務的數據完成此次操作。平時沒有問題,一旦發生意外, 服務B在你不知情的情況下,響應時間變長,甚至停止服務,而你的客戶端超時時間設置過長,則你完成此次請求的響應時間就會變長。Java的Servlet容器,無論是tomcat還是jetty都是多線程模型,都用worker線程來處理請求。這個可配置有上限,當你的請求打滿worker線程的最大值之后,剩余請求會被放到等待隊列。等待隊列也有上限,一旦等待隊列都滿了,那這台webserver就會拒絕服務,對應到Nginx上返回就是502。如果你的服務是QPS較高的服務,那基本上這種場景下,你的服務也會跟着被拖垮。如果你的上游也沒有合理的設置超時時間,那故障會繼續向上擴散.這種故障逐級放大的過程,就是服務雪崩效應。
3>外部依賴的地方沒有熔斷
在依賴的服務不可用時,服務調用方應該通過一些技術手段,向上提供有損服務,保證業務柔性可用。而系統沒有熔斷,如果由於代碼邏輯問題上線引起故障、網絡問題、調用超時、業務促銷調用量激增、服務容量不足等原因,服務調用鏈路上有一個下游服務出現故障,就可能導致接入層其他的業務不可用。
4>對於依賴我們的上游沒有限流
在開放式的網絡環境下,對外系統往往會收到很多有意無意的惡意攻擊,如DDos攻擊,用戶失敗重刷。雖然我們的戰友兄弟各個是精英,但是我們必須要做好不被隊友搞死的保障,搞不好哪天誰寫了一個如果下游返回不符合預期就無限次重試的代碼。這些內部和外部的巨量調用,如果不加以保護,往往會擴散到后台服務,最終可能引起后台基礎服務宕機。
5>慢查詢問題
慢查詢會降低應用的響應性能和並發性能。在業務量增加的情況下造成數據庫所有的服務器CPU利用率急劇攀升,嚴重的會導致數據庫不響應,只能重啟解決。
6>依賴不合理
每多一個依賴方,風險就會累加。特別是強依賴,它本身意味着一榮俱榮、一損俱損。
7>有廢棄邏輯和臨時代碼
過期的代碼會對正常邏輯有干擾,讓代碼不清晰。特別是對新加入的同事,他們對明白干什么用的代碼可以處理。但是已經廢棄的和臨時的,因為不知道干什么用的,所以改起來更忐忑。如果知道是廢棄的,其他功能改了這一塊沒改,也有可能這塊不兼容,引發問題。容易造成級聯多米諾骨牌效應。
8>沒有有效的資源隔離
容易造成級聯多米諾骨牌效應。
解決問題
1>事務中不包含外部調用
☆ 排查各個系統的代碼,檢查在事務中是否存在RPC調用、http調用、MQ操作、緩存、循環查詢等耗時的操作,這個操作應該移到事務之外,理想的情況是事務內只處理數據庫操作。
☆ 對大事務添加監控報警。大事務發生時,會收到郵件和短信提醒。大事務一般的報警標准是1s。
☆ 建議不要用xml配置事務,而采用注解的方式。原因是xml配置事務第一可讀性不強,二是切面通常配置的比較泛濫,容易造成事務過大,三是對於嵌套情況的規則不好處理。
2>超時時間設置合理和重試次數。
☆ 首先要調研被依賴服務自己調用下游的超時時間是多少。調用方的超時時間要大於被依賴方調用下游的時間。
☆ 統計這個接口99%的超時時間是多少,設置的超時時間在這個基礎上加50%。
☆ 重試次數如果系統服務重要性高,則按照默認,一般是重試三次。否則,可以不重試。
3>外部依賴的地方都要做熔斷
☆ 自動熔斷:可以使用netflix的hystrix或者美團點評自己研發的Rhino來做快速失敗。
☆ 手動熔斷:確認下游支付通道抖動或不可用,可以手動關閉通道。
4>對於依賴我們的上游要限流
☆ 通過對服務端的業務性能壓測,可以分析出一個相對合理的最大QPS。
☆ 可以使用netflix的hystrix或者美團點評自己研發的Rhino來限流。
5>解決慢查詢問題
☆ 將查詢分成實時查詢、近實時查詢和離線查詢。實時查詢可穿透數據庫,其他的不走數據庫,可以用ES來實現一個查詢中心,處理近實時查詢和離線查詢。
☆ 讀寫分離。寫走主庫,讀走從庫。
☆ 索引優化。索引過多會影響數據庫寫性能。索引不夠查詢會慢。 像核心交易這種數據庫讀寫TPS差不多的,一般建議索引不超過4個。如果這還不能解決問題,那很可能需要調整表結構設計了。
☆ 對慢查詢對應監控報警。我們這邊設置的慢查詢報警閾值是100ms。
6>能去依賴就去依賴,否則盡量同步強依賴改成異步弱依賴
☆ 划清業務邊界,只做該做的事情。
☆ 如果依賴一個系統提供的數據,上游可以作為參數傳入或者下游可以作為返回值返回,則可以下線專門去取數據的邏輯,盡量讓上下游給我們數據。
☆ 我們寫入基礎組件,數據提供給其他端,如果其他端有兜底策略,則我們可以異步寫入,不用保證數據100%不丟失。
7>廢棄邏輯和臨時代碼要刪除
☆ 梳理每個接口的調用情況,對於沒有調用量的接口,確認不再使用后及時下線
☆ code review保證每段邏輯都明白其含義,弄清楚是否是歷史邏輯或者臨時邏輯
8>核心路徑進行資源隔離
☆ 服務器物理隔離原則:
△ 內外有別:內部系統與對外開放平台區分對待
△ 內部隔離:從上游到下游按通道從物理服務器上進行隔離。低流量服務合並
△ 外部隔離:按渠道隔離,渠道之間互不影響
☆ 線程池資源隔離
△ Hystix通過命令模式,將每個類型的業務請求封裝成對應的命令請求。每個命令請求對應一個線程池,創建好的線程池是被放入到ConcurrentHashMap中。注意:盡管線程池提供了線程隔離,客戶端底層代碼也必須要有超時設置,不能無限制的阻塞以致於線程池一直飽和。
☆ 信號量資源隔離
△ 開發者可以使用Hystix限制系統對某一個依賴的最高並發數。這個基本上就是一個限流策略。每次調用依賴時都會檢查一下是否到達信號量的限制值,如達到,則拒絕。
除了上面的措施之外,戰狼項目進行很有成效的兩地三中心機房互備、組件安全漏洞修復和服務健康驗證,限於篇幅,本篇不詳述。只是在戰狼開始之前,運維和架構師們也強調這些,但是大家忙於對應需求,沒有引起重視。溝通可能可以說是項目過程中最重要的環節。沒有很好的溝通,就好像是越走越遠的兩個人,我在等着你回心轉意,你卻在等着自己死心。你發現心死不了,我卻已經放棄了等待。永遠在平行線上沒有交集,徒勞的苦痛,沒有意義。
實施后的效果
經過上面8個步驟,我們同時也新接入一些業務,邊界如下:
從圖中可以看到,邊界更清晰了。我們通過故障演練證實了解決方案實施后的穩定性提升。
持續跟進
我們優化了業務大盤、故障大盤。加強了監控報警機制,持續的監控和保障着系統的穩定性。故障演練也作為了定時的日常工作來做。穩定性需要建立長期規范,維護組內的checklist,定期檢查是否達到標准。checklist舉例如下:
項目總結
我們家老大是像星星一樣散發着智慧的人。他給我們總結系統穩定性的三個要素:第一是別人死我們不死,第二是不自己作死,第三是不被豬隊友搞死。
穩定性具體的實施方法總結一下就是:能不依賴就去依賴;盡可能將強依賴轉成弱依賴;實在不能降低依賴就保護依賴;做好自保;出了問題只能收斂不能擴大;對危險要能監控。
線上支付平台總結的穩定性“四板斧”:研發規范、自身穩定、容錯下游、防御上游。
經過為期4周的戰狼項目,多個小組緊密合作,日夜兼程,高效的完成了一個又一個攻堅任務,保證了交易系統的穩定。整個項目收獲的不僅是一套穩定的系統,更重要的是通過一次次激烈的探討,一場場集體推進會,總結出了一套通用的系統穩定提升方法,同時也鍛煉出一只充滿戰斗力的隊伍,為整個支付業務快速穩定發展奠定了基礎。
春秋戰國時期的君主普遍的勤政,滿清的康乾盛世繁榮。我總結最根本的原因是憂患。因為連年各國的割據爭斗、因為漢族不甘心受外族的統治,君王在壓力下反而有作為。戰狼項目雖然已經結束,但是戰狼精神永存。我們要時時刻刻居安思危,保持穩定第一。
工具介紹
項目中多次提到使用hystrix和Rhino。所以這里對它們做一個簡單的介紹。
☆ hystrix
Hystix是一個實現了斷路器模式來對故障進行監控,當斷路器發現調用接口發生了長時間等待,就使用快速失敗策略,向上返回一個錯誤響應,這樣達到防止阻塞的目的。這里重點介紹一下hystrix的線程池資源隔離和信號量資源隔離。
□ 線程池資源隔離
線程隔離優點:
△ 使用線程可以完全隔離第三方代碼,請求線程可以快速放回。
△ 當一個失敗的依賴再次變成可用時,線程池將清理,並立即恢復可用,而不是一個長時間的恢復。
△ 可以完全模擬異步調用,方便異步編程。
□ 線程隔離缺點:
△ 線程池的主要缺點是它增加了CPU,因為每個命令的執行涉及到排隊(默認使用SynchronousQueue避免排隊),調度和上下文切換。
△ 對使用ThreadLocal等依賴線程狀態的代碼增加復雜性,需要手動傳遞和清理線程狀態。(Netflix公司內部認為線程隔離開銷足夠小,不會造成重大的成本或性能的影響)
☆ 信號量資源隔離
△ 開發者可以使用Hystix限制系統對某一個依賴的最高並發數。這個基本上就是一個限流策略。每次調用依賴時都會檢查一下是否到達信號量的限制值,如達到,則拒絕。
信號量隔離優點:
△ 不新起線程執行命令,減少上下文切換。
信號量隔離缺點:
△ 無法配置斷路,每次都一定會去嘗試獲取信號量。
□ 比較一下線程池資源隔離和信號量資源隔離。
△ 線程隔離是和主線程無關的其他線程來運行的;而信號量隔離是和主線程在同一個線程上做的操作。
△ 信號量隔離也可以用於限制並發訪問,防止阻塞擴散,與線程隔離的最大不同在於執行依賴代碼的線程依然是請求線程。
△ 線程池隔離適用於第三方應用或者接口、並發量大的隔離;信號量隔離適用於內部應用或者中間件;並發需求不是很大的場景。
☆ Rhino
Rhino是美團點評基礎架構團隊研發並維護的一個穩定性保障組件,提供故障模擬、降級演練、服務熔斷、服務限流等功能。和Hystrix對比:
△ Hystrix組件在熔斷之后,並在試探線程成功之后,就直接關閉熔斷開關,全部流量走正常邏輯。而Rhino會對流量進行恢復灰度。
△ 內部通過Cat(Cat是美團點評開源的一個監控系統)進行了一系列埋點,方便進行服務異常報警。
△ 接入配置中心,能提供動態參數修改,比如強制熔斷、修改失敗率等。
關於作者
謝曉靜,85后程序媛,20歲時畢業於東北大學計算機系。在畢業后的第一家公司由於出眾的語言天賦,在1年的時間里從零開始學日語並以超高分通過了國際日語一級考試,擔當兩年日語翻譯的工作。后就職於人人網,轉型做互聯網開發。中國科學院心理學研究生。有近百個技術發明專利,創業公司合伙人。有日本東京,美國硅谷技術支持經驗。目前任美團點評技術專家,負責核心交易。歡迎關注靜兒的個人技術公眾號:編程一生