流量高峰時期的效能瓶頸有哪些、以及如何來解決

weixin_33807284發表於2018-09-27

在高併發大量使用者的場景,系統一般會面臨如下三個挑戰:

1. 日益增長的使用者數量

2. 日漸複雜的業務

3. 急劇膨脹的資料

這些挑戰對於效能優化而言表現為:在保持和降低系統TP95響應時間(指的是將一段時間內的請求響應時間從低到高排序,高於95%請求響應時間的下確界)的前提下,不斷提高系統吞吐量,提升流量高峰時期的服務可用性。

本文主要目標是為類似的場景提供優化方案,確保系統在流量高峰時期的快速響應和高可用。

效能瓶頸場景

在講解如何效能優化之前,有必要先了解下效能瓶頸在哪裡,以下我們先從瓶頸(各種效能堵塞場景)講起,明確問題所在,然後我們再根據瓶頸,來看如何來解決方案。

 

1.長請求擁塞瓶頸

這是一種單次請求時延變長而導致系統效能惡化甚至崩潰的惡化模式。

對於多執行緒服務,大量請求時間變長會使執行緒堆積、記憶體使用增加,最終可能會通過如下三種方式之一惡化系統效能:

執行緒數目變多導致執行緒之間CPU資源使用衝突,反過來進一步延長了單次請求時間;

執行緒數量增多以及執行緒中快取變大,記憶體消耗隨之劇增,對於基於Java語言的服務而言,又會更頻繁地full GC,反過來單次請求時間會變得更長;

記憶體使用增多,會使作業系統記憶體不足,必須使用Swap,可能導致服務徹底崩潰。

典型惡化流程圖如下圖:

長請求擁塞反模式所導致的效能惡化現象非常普遍,所以識別該模式非常重要。

典型的場景如下:某複雜業務系統依賴於多個服務,其中某個服務的響應時間變長,隨之系統整體響應時間變長,進而出現CPU、記憶體、Swap報警,在分散式環境下,從而引起連鎖反應,最後造成雪崩的情況。

系統進入長請求擁塞反模式的典型標識包括:被依賴服務可用性變低、響應時間變長、服務的某段計算邏輯時間變長等。

2.多次請求槓桿造成效能瓶頸

客戶端一次使用者點選行為往往會觸發多次服務端請求,這是一次請求槓桿。

每個服務端請求進而觸發多個更底層服務的請求,這是第二次請求槓桿。每一層請求可能導致一次請求槓桿,請求層級越多,槓桿效應就越大。

在多次請求槓桿反模式下執行的分散式系統,處於深層次的服務需要處理大量請求,容易會成為系統瓶頸。

與此同時,大量請求也會給網路帶來巨大壓力,特別是對於單次請求資料量很大的情況,網路可能會成為系統徹底崩潰的導火索。典型惡化流程圖如下圖:

多次請求槓桿所導致的效能惡化現象非常常見

例如:對於推薦系統,一個使用者列表請求會有多個演算法參與,每個演算法會召回多個列表單元(商家或者團購),每個列表單元有多種屬性和特徵,而這些屬性和特徵資料服務又分佈在不同服務和機器上面,所以客戶端的一次使用者展現可能導致了成千上萬的最底層服務呼叫。

對於存在多次請求槓桿反模式的分散式系統,效能惡化與流量之間往往遵循指數曲線關係。這意味著,在平常流量下正常執行服務系統,在流量高峰時通過線性增加機器解決不了可用性問題。所以,識別並避免系統進入多次請求槓桿反模式對於提高系統可用性而言非常關鍵。

3.反覆快取造成效能瓶頸

為了降低響應時間,系統往往在本地記憶體中快取很多資料。快取資料越多,命中率就越高,平均響應時間就越快。為了降低平均響應時間,有些開發者會不加限制地快取各種資料,在正常流量情況下,系統響應時間和吞吐量都有很大改進。

但是當流量高峰來臨時,系統記憶體使用開始增多,觸發了JVM進行full GC,進而導致大量快取被釋放(因為主流Java記憶體快取都採用SoftReference和WeakReference所導致的),而大量請求又使得快取被迅速填滿,這就是反覆快取。反覆快取導致了頻繁的full GC,而頻繁full GC往往會導致系統效能急劇惡化。典型惡化流程圖如下圖:

反覆快取所導致效能惡化的原因是無節制地使用快取。快取使用的指導原則是:工程師們在使用快取時必須全域性考慮,精細規劃,確保資料完全快取的情況下,系統仍然不會頻繁full GC。

為了確保這一點,對於存在多種型別快取以及系統流量變化很大的系統,設計者必須嚴格控制快取大小,甚至廢除快取(這是典型為了提高流量高峰時可用性,而降低平均響應時間的一個例子)。

反覆快取反模式往往發生在流量高峰時候,通過線性增加機器和提高機器記憶體可以大大減少系統崩潰的概率。

效能優化方式

 

1.水平分割模式

原理和動機

典型的服務端執行流程包含四個環節:接收請求、獲取資料、處理資料、返回結果。在一次請求中,獲取資料和處理資料往往多次發生。在完全序列執行的系統裡,一次請求總響應時間滿足如下公式:

一次請求總耗時=解析請求耗時 + ∑(獲取資料耗時+處理資料耗時) + 組裝返回結果耗時

大部分耗時長的服務主要時間都花在中間兩個環節,即獲取資料和處理資料環節。

對於非計算密集性的系統,主要耗時都用在獲取資料上面。獲取資料主要有三個來源:本地快取,遠端快取或者資料庫,遠端服務。三者之中,進行遠端資料庫訪問或遠端服務呼叫相對耗時較長,特別是對於需要進行多次遠端呼叫的系統,序列呼叫所帶來的累加效應會極大地延長單次請求響應時間,這就增大了系統進入長請求擁塞反模式的概率。

如果能夠對不同的業務請求並行處理,請求總耗時就會大大降低。例如下圖中,Client需要對三個服務進行呼叫,如果採用順序呼叫模式,系統的響應時間為18ms,而採用並行呼叫只需要7ms。

水平分割模式首先將整個請求流程切分為必須相互依賴的多個Stage,而每個Stage包含相互獨立的多種業務處理(包括計算和資料獲取)。完成切分之後,水平分割模式序列處理多個Stage,但是在Stage內部並行處理。如此,一次請求總耗時等於各個Stage耗時總和,每個Stage所耗時間等於該Stage內部最長的業務處理時間。

水平分割模式有兩個關鍵優化點:減少Stage數量和降低每個Stage耗時。為了減少Stage數量,需要對一個請求中不同業務之間的依賴關係進行深入分析並進行解耦,將能夠並行處理的業務儘可能地放在同一個Stage中,最終將流程分解成無法獨立執行的多個Stage。降低單個Stage耗時一般有兩種思路:1. 在Stage內部再嘗試水平分割(即遞迴水平分割),2. 對於一些可以放在任意Stage中進行並行處理的流程,將其放在耗時最長的Stage內部進行並行處理,避免耗時較短的Stage被拉長。

水平分割模式不僅可以降低系統平均響應時間,而且可以降低TP95響應時間(這兩者有時候相互矛盾,不可兼得)。通過降低平均響應時間和TP95響應時間,水平分割模式往往能夠大幅度提高系統吞吐量以及高峰時期系統可用性,並大大降低系統進入長請求擁塞反模式的概率。

具體案例

例如為使用者提供高效能的優質個性化列表服務,每一次列表服務請求會有多個演算法參與,而每個演算法基本上都採用“召回->特徵獲取->計算”的模式。 在進行效能優化之前,演算法之間採用順序執行的方式。伴隨著演算法工程師的持續迭代,演算法數量越來越多,隨之而來的結果就是客戶端響應時間越來越長,系統很容易進入長請求擁塞反模式。曾經有一段時間,一旦流量高峰來臨,出現整條服務鏈路的機器CPU、記憶體報警。在對系統進行分析之後,我們採取瞭如下三個優化措施,最終使得系統TP95時間降低了一半:

演算法之間平行計算;

每個演算法內部,多次特徵獲取進行了並行處理;

在排程執行緒對工作執行緒進行排程的時候,耗時最長的執行緒最先排程,最後處理。

缺點和優點

對成熟系統進行水平切割,意味著對原系統的重大重構,工程師必須對業務和系統非常熟悉,所以要謹慎使用。水平切割主要有兩方面的難點:

平行計算將原本單一執行緒的工作分配給多執行緒處理,提高了系統的複雜度。而多執行緒所引入的安全問題讓系統變得脆弱。與此同時,多執行緒程式測試很難,因此重構後系統很難與原系統在業務上保持一致。

對於一開始就基於單執行緒處理模式編寫的系統,有些流程在邏輯上能夠並行處理,但是在程式碼層次上由於相互引用已經難以分解。所以並行重構意味著對共用程式碼進行重複撰寫,增大系統的整體程式碼量,違背奧卡姆剃刀原則。

對於上面提到的第二點,舉例如下:A和B是邏輯可以並行處理的兩個流程,基於單執行緒設計的程式碼,假定處理完A後再處理B。在編寫處理B邏輯程式碼時候,如果B需要的資源已經在處理A的過程中產生,工程師往往會直接使用A所產生的資料,A和B之間因此出現了緊耦合。並行化需要對它們之間的公共程式碼進行拆解,這往往需要引入新的抽象,更改原資料結構的可見域。

雖然進行程式碼重構比較複雜,但是水平切割模式非常容易理解,只要熟悉系統的業務,識別出可以並行處理的流程,就能夠進行水平切割。有時候,即使少量的並行化也可以顯著提高整體效能。

對於新系統而言,如果存在可預見的效能問題,把水平分割模式作為一個重要的設計理念將會大大地提高系統的可用性、降低系統的重構風險。總的來說,雖然存在一些具體實施的難點,水平分割模式是一個非常有效、容易識別和理解的模式。

2.垂直分割模式

原理和動機

對於移動網際網路節奏的公司,新需求往往是一波接一波。基於程式碼複用原則,工程師們往往會在一個系統實現大量相似卻完全不相干的功能。伴隨著功能的增強,系統實際上變得越來越脆弱。這種脆弱可能表現在系統響應時間變長、吞吐量降低或者可用性降低。導致系統脆弱原因主要來自兩方面的衝突:資源使用衝突和可用性不一致衝突。

資源使用衝突是導致系統脆弱的一個重要原因。不同業務功能並存於同一個執行系統裡面意味著資源共享,同時也意味著資源使用衝突。可能產生衝突的資源包括:CPU、記憶體、網路、I/O等。

例如:一種業務功能,無論其呼叫量多麼小,都有一些記憶體開銷。對於存在大量快取的業務功能,業務功能數量的增加會極大地提高記憶體消耗,從而增大系統進入反覆快取反模式的概率。對於CPU密集型業務,當產生衝突的時候,響應時間會變慢,從而增大了系統進入長請求擁塞反模式的可能性。

不加區別地將不同可用性要求的業務功能放入一個系統裡,會導致系統整體可用性變低。當不同業務功能糅合在同一執行系統裡面的時候,在運維和機器層面對不同業務的可用性、可靠性進行調配將會變得很困難。但是,在高峰流量導致系統瀕臨崩潰的時候,最有效的解決手段往往是運維,而最有效手段的失效也就意味著核心業務的可用性降低。

垂直分割思路就是將系統按照不同的業務功能進行分割,主要有兩種分割模式:

1.部署垂直分割

部署垂直分割主要是按照可用性要求將系統進行等價分類,不同可用性業務部署在不同機器上,高可用業務單獨部署;

2.程式碼垂直分割。

程式碼垂直分割就是讓不同業務系統不共享程式碼,徹底解決系統資源使用衝突問題。

缺點和優點

垂直分割主要的缺點主要有兩個:

增加了維護成本。一方面程式碼庫數量增多提高了開發工程師的維護成本,另一方面,部署叢集的變多會增加運維工程師的工作量;

程式碼不共享所導致的重複編碼工作。

垂直分割是一個非常簡單而又有效的效能優化模式,特別適用於系統已經出現問題而又需要快速解決的場景。部署層次的分割既安全又有效。需要說明的是部署分割和簡單意義上的加機器不是一回事,在大部分情況下,即使不增加機器,僅通過部署分割,系統整體吞吐量和可用性都有可能提升。所以就短期而言,這幾乎是一個零成本方案。對於程式碼層次的分割,開發工程師需要在業務承接效率和系統可用性上面做一些折衷考慮。

3.降級模式

原理和動機

降級模式是系統效能保障的最後一道防線。理論上講,不存在絕對沒有漏洞的系統,或者說,最好的安全措施就是為處於崩潰狀態的系統提供預案。從系統效能優化的角度來講,不管系統設計地多麼完善,總會有一些意料之外的情況會導致系統效能惡化,最終可能導致崩潰,所以對於要求高可用性的服務,在系統設計之初,就必須做好降級設計。根據作者的經驗,良好的降級方案應該包含如下措施:

在設計階段,確定系統的開始惡化數值指標(例如:響應時間,記憶體使用量);

當系統開始惡化時,需要第一時間報警;

在收到報警後,或者人工手動控制系統進入降級狀態,或者編寫一個智慧程式讓系統自動降級;

區分系統所依賴服務的必要性,一般分為:必要服務和可選服務。必要服務在降級狀態下需要提供一個快速返回結果的權宜方案(快取是常見的一種方案),而對於可選服務,在降級時系統果斷不呼叫;

在系統遠離惡化情況時,需要人工恢復,或者智慧程式自動升級。

典型的降級策略有三種:流量降級、效果降級和功能性降級。

流量降級是指當通過主動拒絕處理部分流量的方式讓系統正常服務未降級的流量,這會造成部分使用者服務不可用;效果降級表現為服務質量的降級,即在流量高峰時期用相對低質量、低延時的服務來替換高質量、高延時的服務,保障所有使用者的服務可用性;功能性降級也表現為服務質量的降級,指的是通過減少功能的方式來提高使用者的服務可用性。效果降級和功能性降級比較接近,效果降級強調的是主功能服務質量的下降,功能性降級更多強調的是輔助性功能的缺失。

缺點和優點

為了使系統具備降級功能,需要撰寫大量的程式碼,而降級程式碼往往比正常業務程式碼更難寫,更容易出錯。在確定使用降級模式的前提下,工程師需要權衡這三種降級策略的利弊。大多數面向C端的系統傾向於採用效果降級和功能性降級策略,但是有些功能性模組(比如下單功能)是不能進行效果和功能性降級的,只能採用流量降級策略。對於不能接受降級後果的系統,必須要通過其他方式來提高系統的可用性。

總的來說,降級模式是一種設計安全準則,任何高可用性要求的服務,必須要按照降級模式的準則去設計。對於違背這條設計原則的系統,或早或晚,系統總會因為某些問題導致崩潰而降低可用性。不過,降級模式並非不需要成本,也不符合最小可用原則,所以對於處於MVP階段的系統,或者對於可用性要求不高的系統,降級模式並非必須採納的原則。

4.其他效能優化建議

對於無法採用系統性的模式方式講解的效能優化手段,給出一些總結性的建議:

刪除無用程式碼有時候可以解決效能問題,例如:有些程式碼已經不再被呼叫但是可能被初始化,甚至佔有大量記憶體;有些程式碼雖然在呼叫但是對於業務而言已經無用,這種呼叫佔用CPU資源。

避免跨機房呼叫,跨機房呼叫經常成為系統的效能瓶頸,特別是那些偽batch呼叫(在使用者看起來是一次性呼叫,但是內部實現採用的是順序單個呼叫模式)對系統效能影響往往非常巨大。

以下是架構進階資料,需要學習免費課程的狂戳

相關文章