在美團的價值觀中,「以客戶爲中心」被放在一個很是重要的位置,因此咱們對服務出現故障愈來愈不能容忍。特別是目前公司業務正在高速增加階段,每一次故障對公司來講都是一筆很是不小的損失。而整個IT基礎設施很是複雜,包括網絡、服務器、操做系統以及應用層面均可能出現問題。在這種背景下,咱們必須對服務進行一次全方位的「體檢」,從而來保障美團多個業務服務的穩定性,提供優質的用戶服務體驗。真正經過如下技術手段,來幫助你們吃的更好,生活更好:html
全鏈路壓測是基於線上真實環境和實際業務場景,經過模擬海量的用戶請求,來對整個系統進行壓力測試。早期,咱們在沒有全鏈路壓測的狀況下,主要的壓測方式有:算法
但以上方式很難全面的對整個服務集羣進行壓測,若是以局部結果推算整個集羣的健康情況,每每會「以偏概全」,沒法評估整個系統的真實性能水平,主要的緣由包括:數據庫
綜合多種因素考慮,全鏈路壓測是咱們準確評估整個系統性能水平的必經之路。目前,公司內全部核心業務線都已接入全鏈路壓測,月平均壓測次數達上萬次,幫助業務平穩地度過了大大小小若干場高峯流量的衝擊。緩存
Quake (雷神之錘)做爲公司級的全鏈路壓測平臺,它的目標是提供對整條鏈路進行全方位、安全、真實的壓測,來幫助業務作出更精準的容量評估。所以咱們對 Quake 提出了以下的要求:安全
Quake 集數據構造、壓測隔離、場景管理、動態調控、過程監控、壓測報告爲一體,壓測流量儘可能模擬真實,具有分佈式壓測能力的全鏈路壓測系統,經過模擬海量用戶真實的業務操做場景,提早對業務進行高壓力測試,全方位探測業務應用的性能瓶頸,確保平穩地應對業務峯值。性能優化
架構圖服務器
Quake 總體架構上分爲:網絡
傳統的數據構造,通常由測試人員本身維護一批壓測數據。但這種方式存在很大的弊端,一方面維護成本相對較高,另外一方面,其構造出的數據多樣性也不足夠。在真實業務場景中,咱們須要的是能直接回放業務高峯期產生的流量,只有面對這樣的流量衝擊,才能真實的反映系統可能會產生的問題。多線程
Quake 主要提供了 HTTP 和 RPC 的兩種數據構造方式:架構
對於 HTTP 服務,在 Nginx 層都會產生請求的訪問日誌,咱們對這些日誌進行了統一接入,變成符合壓測須要的流量數據。架構圖以下:
底層使用了 Hive 做爲數倉的工具,使業務在平臺上能夠經過簡單的類 SQL 語言進行數據構造。Quake 會從數倉中篩選出相應的數據,做爲壓測所需的詞表文件,將其存儲在 S3 中。
對於 RPC 服務,服務調用量遠超 HTTP 的量級,因此在線上環境不太可能去記錄相應的日誌。這裏咱們使用對線上服務進行實時流量錄製,結合 RPC 框架提供的錄製功能,對集羣中的某幾臺機器開啓錄製,根據要錄製的接口和方法名,將請求數據上報到錄製流量的緩衝服務(Broker)中,再由 Broker 生成最終的壓測詞表,上傳到存儲平臺(S3)。
有些場景下,構造出來的流量是不能直接使用的,咱們須要對用戶 ID、手機號等信息進行數據偏移。Quake 也是提供了包含四則運算、區間限定、隨機數、時間類型等多種替換規則。
數據構造產生的詞表文件,咱們須要進行物理上的分片,以保證每一個分片文件大小盡量均勻,而且控制在必定大小以內。這麼作的主要緣由是,後續壓測確定是由一個分佈式的壓測集羣進行流量的打入,考慮到單機拉取詞表的速度和加載詞表的大小限制,若是將詞表進行分片的話,能夠有助於任務調度更合理的進行分配。
作線上壓測與線下壓測最大不一樣在於,線上壓測要保證壓測行爲安全且可控,不會影響用戶的正常使用,而且不會對線上環境形成任何的數據污染。要作到這一點,首要解決的是壓測流量的識別與透傳問題。有了壓測標識後,各服務與中間件就能夠依據標識來進行壓測服務分組與影子表方案的實施。
對於單服務來講,識別壓測流量很容易,只要在請求頭中加個特殊的壓測標識便可,HTTP 和 RPC 服務是同樣的。可是,要在整條完整的調用鏈路中要始終保持壓測標識,這件事就很是困難。
對於涉及多線程調用的服務來講,要保證測試標識在跨線程的狀況下不丟失。這裏以 Java 應用爲例,主線程根據壓測請求,將測試標識寫入當前線程的 ThreadLocal 對象中(ThreadLocal 會爲每一個線程建立一個副本,用來保存線程自身的副本變量),利用 InheritableThreadLocal 的特性,對於父線程 ThreadLocal 中的變量會傳遞給子線程,保證了壓測標識的傳遞。而對於採用線程池的狀況,一樣對線程池進行了封裝,在往線程池中添加線程任務時,額外保存了 ThreadLocal 中的變量,執行任務時再進行替換 ThreadLocal 中的變量。
對於跨服務的調用,架構團隊對全部涉及到的中間件進行了一一改造。利用 Mtrace (公司內部統一的分佈式會話跟蹤系統)的服務間傳遞上下文特性,在原有傳輸上下文的基礎上,添加了測試標識的屬性,以保證傳輸中始終帶着測試標識。下圖是 Mtrace 上下游調用的關係圖:
因爲鏈路關係的複雜性,一次壓測涉及的鏈路可能很是複雜。不少時候,咱們很難確認間接依賴的服務又依賴了哪些服務,而任何一個環節只要出現問題,好比某個中間件版本不達標,測試標識就不會再往下進行透傳。Quake 提供了鏈路匹配分析的能力,經過平臺試探性地發送業務實際須要壓測的請求,根據 Mtrace提供的數據,幫助業務快速定位到標記透傳失敗的服務節點。
一些大型的壓測一般選擇在深夜低峯時期進行,建議相關的人員要時刻關注各自負責的系統指標,以避免影響線上的正常使用。而對於一些平常化的壓測,Quake 提供了更加安全便捷的方式進行。在低峯期,機器基本都是處於比較空閒的狀態。咱們將根據業務的需求在線上對整條鏈路快速建立一個壓測分組,隔出一批空閒的機器用於壓測。將正常流量與測試流量在機器級別進行隔離,從而下降壓測對服務集羣帶來的影響。
依賴標識透傳的機制,在 Quake 平臺上提供了基於 IP、機器數、百分比不一樣方式的隔離策略,業務只需提供所需隔離的服務名,由 Quake 進行一鍵化的開啓與關閉。
還有一個比較棘手的問題是針對寫請求的壓測,由於它會向真實的數據庫中寫入大量的髒數據。咱們借鑑了阿里最先提出的「影子表」隔離的方案。「影子表」的核心思想是,使用線上同一個數據庫,包括共享數據庫中的內存資源,由於這樣才能更接近真實場景,只是在寫入數據時會寫在了另外一張「影子表」中。
對於 KV 存儲,也是相似的思路。這裏講一下 MQ(消息隊列)的實現,MQ 包括生產和消費兩端,業務能夠根據實際的須要選擇在生產端忽略帶測試標識的消息,或者在消費端接收消息後再忽略兩種選擇。
調度中心做爲整個壓測系統的大腦,它管理了全部的壓測任務和壓測引擎。基於自身的調度算法,調度中心將每一個壓測任務拆分紅若干個可在單臺壓測引擎上執行的計劃,並將計劃以指令的方式下發給不一樣的引擎,從而執行壓測任務。
不一樣的壓測場景,須要的機器資源不同。以 HTTP 服務爲例,在請求/響應體都在 1K 之內,響應時間在 50ms 之內和 1s 左右的兩個請求,單個施壓機能達到的極限值徹底不一樣。影響壓測能力的因素有不少,計算中心會依據壓測模型的不一樣參數,進行資源的計算。
主要參考的數據包括:
由於整個壓測過程一直處在動態變化之中,業務會根據系統的實際狀況對壓力進行相應的調整。在整個過程當中產生的事件類型比較多,包括調整 QPS 的事件、觸發熔斷的事件、開啓事故注入、開啓代碼級性能分析的事件等等,同時觸發事件的狀況也有不少種,包括用戶手動觸發、因爲系統保護機制觸等等。因此,咱們在架構上也作了相應的優化,其大體架構以下:
在代碼設計層面,咱們採用了觀察者和責任鏈模式,將會觸發事件的具體狀況做爲觀察主題,主題的訂閱者會視狀況類型產生一連串執行事件。而在執行事件中又引入責任鏈模式,將各自的處理邏輯進行有效的拆分,以便後期進行維護和能力擴充。
調度中心管理了全部的施壓機資源,這些施壓機分佈在北京、上海的多個機房,施壓機採用容器化方式進行部署,爲後續的動態擴容、施壓機灰度升級以及異常摘除的提供了基礎保障。
業務對壓測的需求有高低峯之分,因此平臺也須要事先部署一部分機器用於平常的業務壓測。當業務申請資源不足時,平臺會按需經過容器化方式動態的進行擴容。這樣作的好處,一方面是節省機器資源,另外一方面就是便於升級。不難想象,升級50臺機器相對升級200臺機器,前者付出的代價確定更小一些。
整個機器池維護着幾百臺機器,若是須要對這些機器進行升級操做,難度係數也比較高。咱們之前的作法是,在沒有業務壓測的時候,將機器所有下線,而後再批量部署,整個升級過程既耗時又痛苦。爲此,咱們引入了灰度升級的概念,對每臺施壓機提供了版本的概念,機器選擇時,優先使用穩定版的機器。根據機器目前使用的狀態,分批替換未使用的機器,待新版本的機器跑完基準和迴歸測試後,將機器選擇的策略改成最新版。經過這種方式,咱們可讓整個升級過程,相對平順、穩定,且可以讓業務無感知。
調度中心維持了與全部施壓機的心跳檢測,對於異常節點提供了摘除替換的能力。機器摘除能力在壓測過程當中很是有必要,由於壓測期間,咱們須要保證全部的機器行爲可控。否則在須要下降壓力或中止壓測時,若是施壓機不能正常作出響應,其致使的後果將會很是嚴重。
在壓測引擎的選擇上,Quake 選擇了自研壓測引擎。這也是出於擴展性和性能層面的考慮,特別在擴展性層面,主要是對各類協議的支持,這裏不展開進行闡述。性能方面,爲了保證引擎每秒能產生足夠多的請求,咱們對引擎作了不少性能優化的工做。
一般的壓測引擎,採用的是 BIO 的方式,利用多線程來模擬併發的用戶數,每一個線程的工做方式是:請求-等待-響應。
通訊圖:
這種方式主要的問題是,中間的等待過程,線程資源徹底被浪費。這種組合模式下,性能問題也會更嚴重(組合模式:即模擬用戶一連串的用戶行爲,如下單爲例,請求組中會包含用戶登陸、加入購物車、建立訂單、支付訂單、查看支付狀態。這些請求彼此間是存在前後關係的,下一個請求會依賴於上一個請求的結果。),若請求組中有5個串聯請求,每一個請求的時長是200ms,那完成一組請求就須要 1s 。這樣的話,單機的最大 QPS 就是能建立的最大線程數。咱們知道機器能建立的線程數有限,同時線程間頻繁切換也有成本開銷,導致這種通訊方式能達到的單機最大 QPS 也頗有限。
這種模型第二個問題是,線程數控制的粒度太粗,若是請求響應很快,僅幾十毫秒,若是增長一個線程,可能 QPS 就上漲了將近100,經過增長線程數的方式沒法精準的控制 QPS,這對探測系統的極限來講,十分危險。
咱們先看下 NIO 的實現機制,從客戶端發起請求的角度看,存在的 IO 事件分別是創建鏈接就緒事件(OP_CONNECT)、IO 就緒的可讀事件 (OP_READ) 和 IO 就緒的可寫事件(OP_WRITE),全部 IO 事件會向事件選擇器(Selector)進行註冊,並由它進行統一的監聽和處理,Selector 這裏採用的是 IO 多路複用的方式。
在瞭解 NIO 的處理機制後,咱們再考慮看如何進行優化。整個核心思想就是根據預設的 QPS,保證每秒發出指定數量的請求,再以 IO 非阻塞的方式進行後續的讀寫操做,取消了 BIO 中請求等待的時間。優化後的邏輯以下:
這裏主要耗時都在 IO 的讀寫事件上,爲了達到單位時間內儘量多的發起壓測請求,咱們將鏈接事件與讀寫事件分離。鏈接事件採用單線程 Selector 的方式來處理,讀寫事件分別由多個 Worker 線程處理,每一個 Worker 線程也是以 NIO 方式進行處理,由各自的 Selector 處理 IO 事件的讀寫操做。這裏每一個 Worker 線程都有本身的事件隊列,數據彼此隔離,這樣作主要是爲了不數據同步帶來的性能開銷。
這裏說的業務邏輯主要是針對請求結果的處理,包括對請求數據的採樣上報,對壓測結果的解析校驗,對請求轉換率的匹配等。若是將這些邏輯放在 Worker 線程中處理,必然會影響 IO 讀取的速度。由於 Selector 在監聽到 IO 就緒事件後,會進行單線程處理,因此它的處理要儘量的簡單和快速,否則會影響其餘就緒事件的處理,甚至形成隊列積壓和內存問題。
壓測引擎另外一個重要的指標是 Full GC 的時間,由於若是引擎頻繁出現 Full GC,那會形成實際壓測曲線(QPS)的抖動,這種抖動會放大被壓服務真實的響應時間,形成真實 QPS 在預設值的上下波動。嚴重的狀況,若是是長時間出現 Full GC,直接就致使預壓的 QPS 壓不上去的問題。
下面看一組 Full GC 產生的壓測曲線:
爲了解決 GC 的問題,主要從應用自身的內存管理和 JVM 參數兩個維度來進行優化。
引擎首先加載詞表數據到內存中,而後根據詞表數據生成請求對象進行發送。對於詞表數據的加載,須要設置一個大小上限,這些數據是會進入「老年代」,若是「老年代」佔用的比例太高,那就會頻發出現 Full GC 的狀況。這裏對於詞表數據過大的狀況,能夠考慮採用流式加載的方式,在隊列中維持必定數量的請求,經過邊回放邊加載的方式來控制內存大小。
引擎在實際壓測過程當中,假設單機是 1W 的 QPS,那它每秒就會建立 1W 個請求對象,這些對象可能在下一秒處理完後就會進行銷燬。若是銷燬過慢,就會形成大量無效對象晉升老年代,因此在對響應結果的處理中,不要有耗時的操做,保證請求對象的快速釋放。
這裏放棄對象複用的緣由是,請求的基本信息佔用的內存空間比較小。可一旦轉換成了待發送對象後,佔用的內存空間會比原始數據大不少,在 HTTP 和 RPC 服務中都存在一樣的問題。並且以前使用 Apache HttpAsyncClient 做爲 HTTP 請求的異步框架時,發現實際請求的 Response 對象掛在請求對象身上。也就是說一個請求對象在接收到結果後,該對象內存增長了響應結果的空間佔用,若是採用複用請求對象的方式,很容易形成內存泄露的問題。
這裏以 JVM 的 CMS 收集器爲例,對於高併發的場景,瞬間產生大量的對象,這些對象的存活時間又很是短,咱們須要:
壓測確定會對線上服務產生必定的影響,特別是一些探測系統極限的壓測,咱們須要具有秒級監控的能力,以及可靠的熔斷降級機制。
壓測引擎會將每秒的數據彙總後上報給監控模塊,監控模塊基於全部上報來的數據進行統計分析。這裏的分析須要實時進行處理,這樣才能作到客戶端的秒級監控。監控的數據包括各 TP 線的響應狀況、QPS 曲線波動、錯誤率狀況以及採樣日誌分析等等。
除了經過引擎上報的壓測結果來進行相應的監控分析以外,Quake 還集成了公司內部統一的監控組件,有監控機器指標的 Falcon 系統(小米開源),還有監控服務性能的 CAT系統(美團已經開源)。Quake 提供了統一的管理配置服務,讓業務能在 Quake 上方便觀察整個系統的健康情況。
Quake 提供了客戶端和服務端兩方面的熔斷保護措施。
首先是客戶端熔斷,根據業務自定義的熔斷闕值,Quake 會實時分析監控數據,當達到熔斷闕值時,任務調度器會向壓測引擎發送下降 QPS 或者直接中斷壓測的指令,防止系統被壓掛。
被壓服務一樣也提供了熔斷機制,Quake 集成了公司內部的熔斷組件(Rhino),提供了壓測過程當中的熔斷降級和限流能力。與此同時,Quake 還提供了壓測故障演練的能力,在壓測過程當中進行人爲的故障注入,來驗證整個系統的降級預案。
最後,總結一下作 Quake 這個項目的一些心得:
其實在 Quake 出來以前,美團公司內部已有一個壓測平臺(Ptest ),它的定位是針對單服務的性能壓測。咱們分析了 Ptest 平臺存在的一些問題,其壓測引擎能力也很是有限。在美團發展早期,若是有兩個大業務線要進行壓測的話,機器資源每每會不足,這須要業務方彼此協調。由於準備一次壓測,前期投入成本過高,用戶須要本身構造詞表,尤爲是 RPC 服務,用戶還須要本身上傳 IDL 文件等等,很是繁瑣。
Quake 針對業務的這些痛點,整個團隊大概花費一個多月的時間開發出了第一個版本,而且快速實現了上線。當時,正面臨貓眼十一節前的一次壓測,那也是 Quake 的第一次亮相,並且取得了不錯的成績。後續,咱們基本平均兩週實現一次迭代,而後逐步加入了機器隔離、影子表隔離、數據偏移規則、熔斷保護機制、代碼級別的性能分析等功能。
項目剛線上時,客服面臨問題很是多,不只有使用層面的問題,系統自身也存在一些 Bug 缺陷。當時,一旦遇到業務線大規模的壓測,咱們團隊都是全員待命,直接在現場解決問題。後續系統穩定後,咱們組內採用了客服輪班制度,每一個迭代由一位同窗專門負責客服工做,保障當業務遇到的問題可以作到快速響應。尤爲是在項目上線初期,這點很是有必要。若是業務部門使用體驗欠佳,項目口碑也會變差,就會對後續的推廣形成很大的問題。
這應該是全部內部項目都會遇到的問題,不少時候,推廣成果決定項目的生死。前期咱們先在一些比較有表明性的業務線進行試點。若是在試點過程當中遇到的問題,或者業務同窗提供的一些好的想法和建議,咱們可以快速地進行迭代與落地。而後再不斷地擴大試點範圍,包括美團外賣、貓眼、酒旅、金融等幾個大的 BG 都在 Quake 上進行了幾輪全流程、大規模的全鏈路壓測。
隨着 Quake 總體功能趨於完善,同時解決了 Ptest(先前的壓測系統)上的多個痛點,咱們逐步在各個業務線進行了全面推廣和內部培訓。從目前收集的數據看,美團超過 90% 的業務已從 Ptest 遷移到了 Quake 。並且總體的統計數據,也比 Ptest 有了明顯的提高。
Quake 目標是打造全鏈路的壓測平臺,可是在平臺建設這件事上,咱們並無刻意去追求。公司內部也有部分團隊走的比較靠前,他們也作一些不少「試水性」的工做。這其實也是一件好事,若是全部事情都依託平臺來完成,就會面臨作不完的需求,並且不少事情放在平臺層面,也可能無解。
同時,Quake 也提供了不少 API 供其餘平臺進行接入,一些業務高度定製化的工做,就由業務平臺獨自去完成。平臺僅提供基礎的能力和數據支持,咱們團隊把核心精力聚焦在對平臺發展更有價值的事情上。
其實,全鏈路壓測整個項目涉及的團隊很是之多,上述提到的不少組件都須要架構團隊的支持。在跨團隊的合做層面,咱們應該有「共贏」的心態。像 Quake 平臺使用的不少監控組件、熔斷組件以及性能分析工具,有一些也是兄弟團隊剛線上沒多久的產品。 Quake 將其集成到平臺中,一方面是減小自身重複造輪子;另外一方面也能夠幫助兄弟團隊推進產品的研發工做。
耿傑,美團點評高級工程師。2017年加入美團點評,前後負責全鏈路壓測項目和 MagicDB 數據庫代理項目,目前主要負責這兩個項目的總體研發和推廣工做,致力於提高公司的總體研發效率與研發質量。
團隊長期招聘 Java、Go、算法、AI 等技術方向的工程師,Base 北京、上海,歡迎有興趣的同窗投遞簡歷到gengjie02@meituan.com。