高并發(fā)經(jīng)常會發(fā)生在有大活躍用戶量,用戶高聚集的業(yè)務場景中,如:秒殺活動,定時領(lǐng)取紅包等。
為了讓業(yè)務可以流暢的運行并且給用戶一個好的交互體驗,我們需要根據(jù)業(yè)務場景預估達到的并發(fā)量等因素,來設(shè)計適合自己業(yè)務場景的高并發(fā)處理方案。
在工作這些年里,我有幸遇到了高并發(fā)各種坑,對如何設(shè)計高性能接口有一些經(jīng)驗,其實無外乎滿足以下幾個特點:
靈敏性
伸縮性
容錯性
事件驅(qū)動/消息驅(qū)動
高性能接口設(shè)計準則
在引言里我也說了高性能設(shè)計的四個準則,現(xiàn)在具體對這四個準則做一些描述;
1. 靈敏性
應用程序應該盡可能快的對請求做出響應。
如果可以在順序獲取數(shù)據(jù)和并行獲取數(shù)據(jù)之間進行選擇的話,為了盡快向用戶返回響應,始終應該優(yōu)先選擇并行獲取數(shù)據(jù),可以同時請求互相沒有關(guān)聯(lián)的數(shù)據(jù)。當我們需要請求多個互相無關(guān),沒有依賴的數(shù)據(jù)的時候,應該考慮是否能夠同時請求這些數(shù)據(jù)。
如果可能出現(xiàn)錯誤,應該立即返回,將問題通知用戶,不要讓用戶等待直到超時。
1.1 如何設(shè)計靈敏性
緩存前置
對于一些改變不頻繁的數(shù)據(jù),應該放在分布式緩存中,例如redis,如果是一些元數(shù)據(jù)(例如,一些計數(shù)器的配置信息,變量的配置信息等)則應該啟用本地緩存,簡單流程如下:
如果一些熱點數(shù)據(jù)不大的話,建議服務啟動的時候就應該提前加載到緩存中,這樣可以提高服務的性能。
讀寫拆分部署
如果你的服務既涉及到讀操作,也涉及到寫操作, 應該將讀寫隔離部署,這樣讀服務的壓力不會影響到寫服務,寫服務的壓力不會影響到讀服務。流程如下:
當然除了按照讀寫進行拆分部署外,還可以按照業(yè)務進行隔離部署。
對等設(shè)計、無狀態(tài)
所謂無狀態(tài)是指應用服務器不保存業(yè)務的上下文信息,而僅根據(jù)每次請求提交的數(shù)據(jù)進行相應的業(yè)務邏輯處理,多個服務實例(服務器)之間完全對等,請求提交到任意服務器,處理結(jié)果是完全一樣的。
不保存狀態(tài)的應用給高可用的架構(gòu)設(shè)計帶來了巨大便利,既然服務器不保存請求的狀態(tài),那么所有的服務器完全對等,當任意一臺或多臺服務器宕機,請求提交給集群中的其他任意一臺可用機器處理,這樣對終端用戶而言,請求總是能夠成功的,整個系統(tǒng)依然可用。對于應用服務器集群,實現(xiàn)這種服務器可用狀態(tài)實時檢測、自動轉(zhuǎn)移失敗任務的機制就是負載均衡。
DB分庫分表,讀寫分離
對于數(shù)據(jù)層來說,如果數(shù)據(jù)量不大,db可以采用讀寫分離部署,對于讀多寫少的場景可以解決一部分壓力,從而提高我們接口的響應速度,如果寫的數(shù)據(jù)量和讀的數(shù)據(jù)量都很大,那么就必須要對db進行分庫分表外加讀寫分離了。
2. 伸縮性
應用程序應該能夠根據(jù)不同的工作負載進行伸縮擴展(尤其是通過增加計算資源來進行擴展)。為了提供伸縮性,系統(tǒng)應該努力消除瓶頸。
如果在虛擬機上運行內(nèi)存數(shù)據(jù)庫,那么添加另一個虛擬幾點就可以將所有的查詢請求分布到兩臺虛擬服務器上,將可能的吞吐量增加至原來的兩倍。添加額外的節(jié)點應該能夠幾乎線性的提高系統(tǒng)的性能。
增加一個內(nèi)存數(shù)據(jù)庫的節(jié)點后,還可以將數(shù)據(jù)分為兩半,并將其中的一半移至新的節(jié)點,這樣就能夠?qū)?nèi)存容量提高至原來的兩倍。添加節(jié)點應該能夠幾乎線性的提高內(nèi)存容量。
所以一般好的接口設(shè)計是可以通過水平擴展機器來達到提升性能的,這就要求我們設(shè)計接口的時候提現(xiàn)無狀態(tài)性。
3. 容錯性
應用程序應該考慮到錯誤發(fā)生的情況,并且從容的對錯誤情況做出響應。如果系統(tǒng)的某個組件發(fā)生錯誤,對與該組件無關(guān)的請求不應該產(chǎn)生任務影響。錯誤是難以避免的,因此應該將錯誤造成的影響限制在發(fā)生錯誤的組件之內(nèi)。如果可能的話,通過對重要組件及數(shù)據(jù)的備份和冗余,這些組件發(fā)生錯誤時不應該對其外部行為有任何影響。
假設(shè)你的系統(tǒng)既使用了redis,也使用了mysql對數(shù)據(jù)進行處理,當redis或著mysql掛了的時候,程序應該可以繼續(xù)提供服務,而不是一味的報錯。流程如下:
當一個組件不可用的時候,可以使用開關(guān)對某一個組件進行降級,常見的降級方式分為手動降級和自動降級,手動降級可以借助zookeeper進行,自動降級可以使用Hystrix。
4. 事件驅(qū)動/消息驅(qū)動
使用消息而不直接進行方法調(diào)用提供了一種幫助我們滿足另外3個高性能設(shè)計準則的方法。消息驅(qū)動的系統(tǒng)著重于控制何時、何地以及如何對請求做出響應,允許做出響應的組件進行路由以及負載均衡。
由于異步的消息驅(qū)動系統(tǒng)只在真正需要時才會消耗資源(比如線程),因此它對系統(tǒng)資源的利用更為高效。消息也可以被發(fā)送到遠程機器(位置透明)。
通常不是萬不得已,否則我們認為丟失一部分數(shù)據(jù)換取服務的高性能,這是值得的。如果能容忍數(shù)據(jù)的部分丟失(在可接受范圍內(nèi)),比如保存數(shù)據(jù)到db,異步計算耗時的任務,通過消息隊列將是提升我們系統(tǒng)性能的比較好的方式。
總結(jié)
4個設(shè)計準則之間并不是完全獨立的。為了滿足某個準則而采取的方法通常也對滿足其他準備有所幫助。例如,如果發(fā)現(xiàn)某個服務響應速度較慢,我們可能會在短時間內(nèi)停止再向該服務發(fā)送請求,等待其恢復正常,并立即向用戶返回錯誤信息。這樣做降低了響應慢的服務不堪重負直接崩潰的風險,因此也提高了系統(tǒng)的容錯性。除此之外,我們立即告知了用戶系統(tǒng)發(fā)生的問題,也就改善了系統(tǒng)的響應速度,如圖所示:
責任編輯人:CC
評論
查看更多