穩定性全系列(二):如何做線上全鏈路壓測

1. 背景介紹

如今,在微服務架構盛行的互聯網時代,微服務架構下模塊(本文指可獨立部署的服務)之間的關係錯綜複雜(哪怕是避免模塊之間的直接循環依賴都很變得困難),評估一整套業務系統(集羣)的容量已經不像評估單機系統那樣容易,而系統的容量評估,是穩定性建設的核心內容之一,是我們繞不開的主題。

有了系統容量評估,配合今年的業務目標,我們才知道應該申請多少預算、什麼時候需要擴容、系統瓶頸在哪、哪些服務(模塊)需要擴容。評估系統容量或者準確的說 評估線上系統的容量現階段最優效也是最準確的方式就是進行線上全鏈路壓測

2. 準備工作

你要問實現線上全鏈路壓測難不難?當然難(現階段穩定性工作哪一項不難?),但依然有跡可循。而且和當前技術體系的系統化建設程度以及各團隊之間協作有關係。想實現線上全鏈路壓測,我們需要做如下三個方面的準備工作(爲了描述簡單,本文的“全壓”指的是線上全鏈路壓測):

  • 確定需要哪些團隊參與
  • 確定全壓技術方案
  • 設定全壓目標和計劃

3. 拆分詳情

確定需要哪些團隊參與

全壓絕對是一項耗時耗力的工程,特別是剛開始的時候。首當其衝的當然是得到老大的支持,一般需要參與進來的至少有 研發測試運維 三個團隊。研發團隊主要負責技術方案的設定和實施(當然如果有架構組或中間件團隊,技術方案的設定可以交給他們),測試團隊負責驗證全壓方案和數據的正確性以及真正的施壓,而運維團隊需要關注壓測對線上集羣的影響以及一些輔助工作(例如提前調整網關的限流閾值)。

確定全壓技術方案

這一步應該是難度最大的,不同技術體系具體實施方案當然不一樣,但可以相互參考,就拿我的業務部門舉例,我們服務端是Java棧,整個業務流量符合如下鏈路:

上圖最左邊的App指的是用戶手機中裝的App,從後面的鏈路我們可以看出,業務網關後面就是我們的服務端系統,各模塊之間使用Dubbo來進行交互,當然異步用的是DDMQ,而當模塊需要使用集團的中臺服務時,我們使用的是HTTP。模塊內部還使用了線程池,也使用了MySQL、Redis等外部服務。

第一步,確定“全鏈路”應該包含鏈路(或頂級接口),所謂的全鏈路,它其實是一個相對的概念,在剛開始做全壓時,我們主要是把線上的核心鏈路找出來,找到這些鏈路的頂級接口,這其實就是發壓的主要入口。

第二步,確保壓測標識在這些鏈路中傳遞以及處理,第二步是最難的也是最複雜的,我們要分析第一步中這些鏈路中如何有效安全的傳遞壓測標識,壓測標識是系統中用來區分壓測流量還是線上正常流量的標識,我們要保證壓測標識正確的傳遞和清除,否則可能導致嚴重的線上事故。這裏將給出我們的做法,供大家參考,主要分四大部分:

  • 儘可能的對模塊無侵入或低侵入

微服務架構下可獨立你部署的模塊數可能會非常驚人,任何能成功實時的技術方案都應該要求是對業務模塊是無侵入或者是低侵入的,否則將影響方案的推廣以及實施成本,我們爲了做到這一點,打算直接在我們的基礎組件(內部使用的公共庫和中間件)動刀子,儘可能的對用戶透明。

  • 壓測標識安全的傳遞和處理

這個要分模塊內、模塊間、模塊外三個部分來考慮:

模塊內:假如模塊內部已經知道該流量是壓測流量,我們如何保證該壓測流量能在模塊內部複雜的邏輯處理中不丟失?模塊內主要考慮的是線程中和跨線程執行的時候,壓測標識容易丟失,線程中,我們使用的是對ThreadLocal的包裝類(我們沒使用阿里開源的TransmittableThreadLocal)。而爲了能夠跨線程傳遞,我們修改了taxi-thread公共庫,將其中的TaxiThreadPoolExecutor等類進行了修改,加入了壓測標識的傳遞(這裏補充下背景,我們爲了traceId能跨線程傳遞,在taxi-thread公共庫中包裝了JDK線程池相關的類,並在開發規範中要求研發同學不能直接使用JDK原生的線程池)。還有一塊,就是日誌打印,爲了能準確區分壓測流量和正常流量,也爲了壓測流量不污染線上數據(比如線上很多模塊有埋點日誌),我們修改了taxi-log(我們這邊沒有直接使用SLF4J,而是使用包裝過的日誌公共庫taxi-log),將壓測流量所有的日誌打在原日誌目錄下的shadow影子目錄下,這一切對用戶也是透明的。

模塊間:我們這邊模塊間的通訊方式主要是Dubbo和DDMQ,Dubbo這塊的話我們直接通過Filter來實現壓測標識傳遞,而DDMQ本身就自帶壓測標識傳遞方式,可以直接使用。

模塊外:這一部分主要是存儲、緩存以及一些外部服務(比如上圖的中臺服務)。

存儲例如MySQL、MongoDB等,我們必須要隔離壓測和線上數據,所以我們會事先建好所謂的 影子表,影子表其實和線上表的區別就是表名,影子表會在真實表名前加一個shadow_前綴,而我們的taxi-mybatis、taxi-mongo等公共庫在識別到壓測標識時,會給表或者文檔名稱前也帶上shadow_前綴。之所以只是做表隔離而沒有做庫級別隔離,考慮到的還是降低侵入性和成本。關於存儲,還有一個關鍵點,假如模塊只提供查詢服務(比如某些配置中心),如果按照前面說的,存儲接入壓測標識這塊做成無侵入的話,全壓流量查詢也會走影子表,這也許是我們不希望看到的,所以在MySQL這塊我們特意做成有侵入的(需要加一個插件配置),否則默認不識別壓測標識

對於分佈式緩存,我們使用的是Redis,這一塊的處理方式和存儲類似,我們修改了我們自己Redis包裝的公共庫,如果是識別到壓測標識,默認在操作的key上加一個shadow_前綴,保證壓測流量不污染線上緩存數據。

對於外部服務,我們使用的是HTTP來調用,所以修改了我們taxi-util中的HTTP組件,做了壓測標識的傳遞,保證下游外部服務能知道這是壓測流量。那肯定有人問,如果下游服務不支持壓測流量識別該咋辦?所以這裏我們藉助了 SDS服務降級系統https://github.com/didi/sds),可以只對壓測流量進行攔截,使其不調用下游外部服務。

最後的效果如下:

第三步,確保全壓流量能被監控到,這涉及到我們在實際全壓中能否直觀的感受到壓測流量,這一塊需要和內部的監控系統來打通,由於能方便的取到壓測標識,這一塊的實現我們不再闡述。

第四步,準備全壓數據,確定接口調用比例,最理想的方式是能對線上流量進行克隆、放大和處理,作爲壓測輸入數據來重放,但這塊難度較大,需要有好的平臺來支撐,我們目前只能使用更簡單的方式來造數據。由於無法使用仿真數據,我們提前在影子表中造了一批用戶、設備信息、位置等和業務相關的數據,然後去線上統計了鏈路上各頂級接口的流量和交易量的比例,來作爲壓測時流量放大的依據。當然,必不可少的還有一個發壓工具或平臺,例如滴滴的奧創發壓平臺。

確定全壓目標和計劃

全壓前我們需要定下全壓的目標,比如當前我們交易系統能支撐100W訂單(現有日單量峯值),而業務今年的目標是衝擊300W日訂單,那按照峯值流量2倍來算,我們的交易系統需要支撐600W的單量,那麼第一次全壓的目標可以保守些,定爲日訂單200W。因爲哪怕線下驗證已經充分,全壓時也會遇到各種出乎意料的問題,當然發現問題其實也意味着我們發現了系統容量瓶頸,這也是全壓的主要目的之一。全壓計劃也同樣重要,因爲我們系統一定是在不斷的迭代中,上一次的全壓結論可能會很快“過期”,所以我們需要定下明確的全壓計劃和節奏,不斷降低全壓的人力成本,使這一穩定性建設工作持續有效的進行下去。

4. 總結

截止到目前,我們已經進行過很多輪的全壓,也在不斷往全壓中補充新的鏈路,加入新的模塊,目前全壓的人力成本還是較高,我們也在探索全自動化全壓方案,到時候有成果將和大夥繼續分享。

作者介紹

易振強,滴滴專家工程師

我是易振強,熱愛開源,熱愛分享,深耕分佈式系統和穩定性建設,歡迎關注SDS服務降級系統:https://github.com/didi/sds ;也熱愛生活,熱愛漫畫,一拳超人和海賊王都很好看!!

本文轉載自公衆號普惠出行產品技術(ID:pzcxtech)。

原文鏈接

https://mp.weixin.qq.com/s/2M9EyCIuT1mZ9drftdMEBA

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章