本文作者:斷空(王鬆鬆)
在典型的微服務架構中,RPC框架扮演着連接各個服務、組件的關鍵角色。作為雲音樂的基礎組件之一,本文將分享我們在RPC穩定性建設過程中的經驗和實踐。
背景
在典型的微服務架構的架構中,RPC框架扮演着連接各個服務、組件的關鍵角色。作為雲音樂的最基礎組件之一,支撐了用户、會員、廣告、數據平台等各個業務的平穩運行。然而隨着當前雲原生理念的快速發展落地,在當前整體降本增效的大背景下,對RPC也提出了新的挑戰。一旦RPC框架出現問題,整個系統的可用性和性能都會受到影響,用户可能會面臨播放中斷、延遲增加等不良體驗,導致用户的流失、客訴等。因此我們開始從架構、流程等各方面重新梳理,參考業界的優秀實踐,結合音樂特殊的業務場景,我們開始了RPC的穩定性建設歷程。
整體架構
由傳統的單體應用遷移到微服務後,應用被拆分、部署到了獨立的節點、集羣,進程內的函數調用不再適用於分佈式場景,於是RPC框架應運而生,但同樣的也引入了一系列新的問題:
- 服務發現: consumer如何快速發現provider所有的節點? provider下線後如何快速將通知下發到consumer
- 連接管理:單連接還是連接池? 網絡抖動後如何重連、自愈?
- 面向雲原生:容器化部署後,節點異常、宕機常態化,如何快速感知異常節點並快速熔斷
- 重試:請求異常、超時應當選擇怎樣的重試策略合適?
穩定性是一個非常複雜的話題,從故障的角度來看,可以分為以下幾個階段:
- 故障前:這個階段主要是預防,例如在問題上升為故障前,通過自動化測試、流程規範管控、監控報警、等手段快速發現。
- 故障中:這個階段主要是發現、恢復和定位故障。需要快速發現系統出現的故障,及時採取措施進行恢復,並準確定位故障原因,最大化降低對用户的影響。
- 故障後:這個階段主要對故障發生的原因進行分析和總結,找出不足之處並採取措施進行改進,思考通過標準化、流程化、自動化的手段,避免同樣的故障二次出現。
在後續的行文中,我們將從這幾個方面來探討穩定性的話題。
故障前
SLO體系建設
You can't manage what you can't measure.
就RPC而言,音樂內部有N套平台可以用於故障的發現、預警,例如異常日誌、核心指標(線程池、CPU、內存、GC等)、自動化用例等。太多的指標會導致我們忽視真正核心的問題;而選擇過少的指標,例如只有異常日誌告警,會導致故障無法被及時發現。我們需要站在用户的角度思考問題,當用户使用一個系統是時候更關注的是哪些指標?例如CPU使用率達到80%是一個現象,我們更希望知道對用户的影響是什麼,接口可用率下降還是RT升高?
SLO是什麼?
SLO是指服務等級的目標值或範圍值,通常用於衡量服務健康狀況。SLO提供了一種標準化的方式來描述、衡量和監控微服務應用程序的性能、質量和可靠性。SLO為應用開發和平台團隊、運維團隊提供了一個共享的質量基準,可作為衡量服務水平質量以及持續改進的參考。
示例如下:
- 90%的RPC請求能夠在200ms以內完成
- 99%的請求返回的code為200
對於RPC這種在線服務的場景來説,例如歌曲播放、查看評論、會員充值等場景,相對來説我們會更關注接口的成功率、延遲等核心指標,即接口能正常響應用户的請求嗎? 花了多久? 因此前期我們選擇了成功率、RT來作為SLO的數據源,並重分利用了SLO平台提供的監控、告警等能力,有效實現了對接口可用性的度量,當SLO出現波動時,開發需要及時介入排查。
日誌治理
早期版本打印的日誌非常混亂,大部分異常缺少説明,導致用户無從下手。因此我們針對日誌進行了一系列治理措施。
- 鏈路串聯:將日誌和trace打通,當業務排查問題時,通過traceId即可將上下游所有應用關聯在一起,並快速跳轉到APM平台
- 異步適配:RPC和單體應用時代不同,天生就具備了異步的特性,請求發出後,consumer等待,而當provider處理完成之後,框架內部根據requestId關聯到對應的請求,並將結果返回。過程中會存在很多線程池切換,例如業務現場、IO線程、worker線程等,而線程池的切換可能會導致traceId丟失,從而影響問題的排查。因此我們針對內部所有的異步、線程切換等進行了統一的治理,有效避免類似問題的發生。
- 梳理&&完善核心鏈路日誌
- 日誌標準化,例如統一前綴、模塊,錯誤碼等信息,從而能夠根據接口、ip等信息快速匹配到相關異常日誌
異常大盤
當前告警機制更多偏向應用、資源維度,例如應用的異常日誌超出配置的閾值、線程池隊列堆積等,而RPC作為中心化的組件,一旦出現問題爆炸半徑往往不太可控。因此我們在思考如何能夠快速的發現框架層的問題。
對於RPC來説,組件能的問題往往都會通過異常日誌體現,例如超時、接口熔斷、限流、服務上下線、未知異常等,因此我們通過梳理出所有的logger以及異常日誌,並基於此構建了以RPC為中心的異常大盤。並提供了應用維度的topN計算、離羣點檢測、日誌採樣等豐富的能力,從而能夠協助中心化負責人快速發現問題、確定受影響應用、並通過日誌採樣等功能進一步根因定位。
故障中
降級
降級平台目前在音樂內部廣泛使用,當前已有數千應用接入,通過與降級平台的打通聯動,RPC提供了豐富的降級能力:
- 模板規則:用户無需觸動配置,當錯誤率查出一定閾值時,自動觸發降級
- 支持豐富的兜底策略,例如fallback方法調用、固定值,並與緩存平台打通,支持故障時返回緩存中的數據
- 監控告警:具備豐富的指標監控以及告警能力,對於BFF等類網關應用,支持支持將告警分發給對應的接口負責人
- 動態調整:降級規則支持秒級動態調整與下發,應用無需重啓、發佈
- 便捷:無需主動接入,平台配置後動態生效
限流
通過接入內部的限流平台,提升應用對於異常流量的應對能力,並支持豐富的限流策略:
- 單機、併發限流
- 分佈式限流
- 參數限流
- 高頻限流等
離羣節點剔除
在分佈式系統中,宕機是常態時間,而站在服務消費者的層面來説,如果不能及時檢測到服務提供者中的異常節點,並快速剔除,將會影響服務的可用性(SLO),進而導致客訴。因此組件層面提供了離羣節點檢測與剔除能力,當個別節點錯誤率不符合預期時會將該節點剔除,在指定時間窗口內不再將流量分配到該節點,並再探活成功後,將該節點重新加入服務列表中。
線程池隔離
若所有的接口都路由到同一個線程池,那麼這些接口變會資源爭搶的風險,接口A的RT抖動,會導致接口B的吞吐下降,因此對於一些核心的業務,我們希望能夠進行線程池的隔離。因此我們對RPC框架的線程池使用進行了梳理之後,提供了豐富的隔離能力:
- 產品:支持不同的產品配置路由到各自單獨的集羣,例如雲音樂、直播等不同的APP路由到各自獨立的集羣、線程池,從而實現更好的隔離
- 應用、接口、方法:支持各個粒度的隔離
- 區分普通請求、重試請求:例如將重試請求放到單獨的線程池中,避免阻塞正常流量
快速失敗
針對超時、堆積等場景,在執行業務邏輯前、執行業務邏輯後進行多層超時判斷,例如超時配置為100ms,而provider在收到請求時會進行一次計算,若此時耗時已經大於100ms,則直接返回超時異常,而無需調用業務接口,從而有效避免無效請求。同時結合客户端、服務端提供的異步能力,更進一步減少線程資源的使用。
註冊中心弱依賴
對於RPC框架來説最核心的依賴便是註冊中心,當前雲音樂內部使用Zookeeper作為主要的註冊中心,而在之前的版本中對於Zookeeper是強依賴,導致Zookeeper一旦抖動或發生網絡異常,provider便會大量下線,導致客户端路由不到正確的節點,影響整體的可用性。
從一致性的角度出發,Zookeeper底層基於Zab的類Paxos協議實現,更注重的是CP,即一致性,因此各種大數據、中間件都會利用Zookeeper來進行元數據、配置的管理。而對於註冊中心的場景來説,對於一致性的要求並沒有那麼高,舉個例子,服務提供者有一個節點OOM宕機,沒有調用下線接口從Zookeeper中摘除,因此客户端會認為該節點沒有下線,仍會保留在路由表中,如果仍將流量達到該節點,那麼必然會導致請求失敗,但RPC框架通常會具備探活能力,此時下游節點OOM宕機,網絡鏈接斷開不可用會直接被節點剔除,對可用率沒有任何影響。我們發現對於註冊中心來説,底層存儲大多數時候只需最終一致即可,不需要這麼強的一致性。以這個思路出發,我們對RPC框架進行了一系列專項改造:
Zookeeper調優
- 配置、重試策略調優,例如sessionTimeout調整到30s,那麼網絡抖動只要在30s以內,由於session不會過期,provider也不會觸發批量下線,對於服務的可用性幾乎沒有影響。這個參數需要結合實際的應用場景調整。
- 事件監聽優化,區分sessionId,若sessionId未變化,説明session未過期,此時provider不需要重新註冊。在服務規模較大時,能夠減少大量無用的寫入操作。
- 配置化:重試、超時等參數可動態配置,並將連接串統一管理,節點擴容下線業務無需感知
回收站
當服務下線時,將該節點的元數據暫存到緩存中,並支持根據容量、過期時間等策略進行清理。當發生Zookeeper大規模故障時,支持從回收站中將已下線節點重新添加到路由表中。
這裏需要注意的是,容器化部署後,節點的ip可能會被其他應用複用、回收站中的節點已經真實下線,因此不能無腦回收,需要對節點進行探活、異常節點檢測,從而能夠快速將離羣節點剔除,最小化對於可用率的影響。
自動化降級
區別於dubbo、Nacos的推空保護,平台支持手動以及自動化的降級,當監控集羣檢測到Zookeeper服務端異常時,例如無法正常建立鏈接、寫入失敗等,則會根據配置自動打開註冊中心的回收站機制,從而有效應對註冊中心變更(擴容、修改配置)、網絡分區等情況。
多註冊
在新版本中,我們完善了RPC框架的多註冊能力,支持將Nacos作為備,並支持動態調整,例如開關打開後,provider會同時往Zookeeper、Nacos註冊,而客户端也支持了多訂閲能力,並支持豐富的路由策略,例如優先從Nacos讀、若Nacos不存在可用節點,從Zookeeper讀取作為兜底。
同時由於平台默認支持了Zookeeper、Nacos的多註冊能力,我們在啓動時對Zookeeper、Nacos做了強弱依賴的管理,例如當進行Zookeeper擴容、遷移操作時,支持將Zookeeper作為弱依賴,即Zookeeper連接、註冊、訂閲失敗不影響應用啓動,框架底層會異步重試,從而更進一步降低RPC對於註冊中心的依賴。
故障後
經驗庫沉澱
區別於業務異常,RPC組件內部的代碼相對穩定,不會頻繁變動,因此問題的定位、排查通常具備一定的套路。因此通過日常的答疑、故障定位等逐步積累了組件的經驗庫。例如當發生指定異常時,平台自動根據異常、堆棧等信息匹配到對應的經驗庫,進一步優化用户看到異常後無法自助處理的問題。
總結
RPC作為一個微服務框架來説,穩定性、性能是最基本的要素,需要我們持續的打磨、治理,同時我們需要思考在當前雲原生、降本增效的大背景下,如何能夠更好的支撐未來的架構,並給開發者提供更好的使用體驗。
最後:
更多崗位,可進入網易招聘官網查看 https://hr.163.com/