陳代旺


摘要:高并發在實際生活中隨處可見,然而解決方法卻是一個難點,但這個問題又不得不去優化甚至解決。本文主要圍繞“高并發的產生以及解決高并發的一些思路”展開敘述。其中包括在解決高并發問題中系統所采用的一些常用解決方案、同類技術的選擇、采用相關技術后帶來的新問題及其解決方案,并且本文只探討后端主要框架,暫時不考慮數據庫層面。
Abstract: High concurrency can be seen everywhere in real life. However, the solution is difficult, but this problem has to be optimized or even solved. This article focuses on "the emergence of high concurrency and some ideas for solving high concurrency". These include some common solutions used in the system to solve high concurrency problems, the choice of similar technologies, new problems brought by the adoption of related technologies and their solutions, and this article only discusses the main framework of the backend, temporarily not considering the database level.
關鍵詞:高并發;分布式;Spring Cloud;OAth2.0
Key words: high concurrency;distributed;Spring Cloud;OAth2.0
中圖分類號:TP311.5? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 文獻標識碼:A? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 文章編號:1006-4311(2019)17-0251-03
0? 引言
所謂高并發就是在一個時間段內產生多個請求(我們可以簡單理解為“同時”),這些請求可能是系統能處理的,也有可能是系統不能處理的。但是我們一般請求下談到的高并發是系統處理不了的情況,因為它并沒有一個標準,而是相當于系統而言的,所以本文后續提到的“高并發”默認是指系統滿負載情況下還不能滿足需求的情形。以學校教務系統為例,正常情況下我們可以在系統中正常進行各種操作,但是如果到了選課的時候就可能會出現相應緩慢、部分用戶無法訪問和服務器直接宕機等情況,在選課的時候就處于高并發時段,這個時段中服務器壓力增大以至于產生各種不希望出現的情況。比如一臺服務器每秒可以處理1000請求,但是在某一個使用高峰期中,每秒產生了1500個請求,這個時候就產生了高并發。對于這個問題,我們的解決方案可能有:多出的500請求直接被丟棄、等待系統處理完其他1000請求后再來處理這500請求、增加服務器數量來提高系統處理能力。下面我們將對上述所談到的三個解決方案進行分析與實現方法,并選出一個盡可能好的方案深入研究講解。
1? 現代系統常用架構方式
在現代系統中(特別是用于商業用途的系統),我們可能會為了用戶的使用方便而為系統提供WEB界面;但是可能有部分功能是在WEB界面上難以實現或者無法實現的,這個時候我們可能會為系統提供Android端和IOS端;但是由于現在用戶設備配置等原因,大多數用戶都不想在自己的手機上安裝太多的軟件,所以我們又把系統常用功能提取出來放在小程序端;還有可能有些業務需要后端直接與相關硬件相連,為他們提供數據服務,比如收款所用的POSS機。如“圖1”所示,盡管終端可能會以各種不同的形式出現,但它們的最終目的都是用于系統后臺數據落地展示以及相關數據的收集,所以我們很容易從圖中看出整個系統的核心便是系統后臺,只有系統后臺穩健運行為所有依賴它的終端提供正常快速的服務才能保證各終端正常工作并且使系統數據同步更新。
2? 高并發解決方案
2.1 數據異步處理
在傳統的網絡編程中,我們嘗嘗采用同步的方式進行請求,即我們請求一個方法或者網絡接口獲取數據時,請求方需要一直等待被請求方處理完成并且返回處理結果或者等待時間過長達到超時時間后,請求方才能進行下一步操作。一般情況下,實際應用中的系統處理速度不可能永遠比產生任務的一方慢,而是有時候處理速度更不上任務產生方(這里不考慮在某些情況下系統并未達到滿負荷而處理能力未增加,只簡單假設系統性能與處理能力成正比),大部分時間里系統處于空閑狀態。如果以二八定律來看,正常情況下,一天中只有20%的時間里對系統的使用率會很大,而系統在80%的時間里都處于“空閑”狀態或者負載量極輕。所以我們可以利用這個定律充分利用系統資源,即將20%的高峰請求的一部分分攤給其余80%的空余時間里給系統處理。
由于系統在收到請求時不一定會馬上進行處理并返回結果,而是將該請求暫存起來等系統有空閑時間了再去處理,所以從設計開始就不能讓請求者等待系統返回處理結果,而是當系統處理完成后,系統通知請求發送者或者按照事先約定的規則進行下一步操作。而收集請求的一部分通常采用中間件來完成這個操作,如消息隊列中間件,它以超高的速度使得發送過來的請求全部收集起來,并按接收的順序將請求轉交給處理系統進行處理。
2.2 服務器集群+負載均衡
在實際應用中,往往有很多操作是我們必須要實時等待對方處理結果后才能進行下一步操作,這種情況下就不能完全使用2.1介紹的方法,但是我們能對處理系統進行優化,使其能處理更多的請求,比如代碼優化、提升服務器CPU的運行速率以及直接增加服務器的數量等。而直接增加服務器數量應該是最簡單也最有效的方式。我們將多臺服務器做同樣的工作稱之為“集群”,在一個集群中,我們請求集群中的任何一臺服務器都能得到相同的結果,這樣就能提升系統的處理能力。
但是請求者需要怎樣決定自己將請求發送到哪個服務器有是一個問題,因為在理想的情況下,我們總是希望相同配置的服務器在單位時間能處理的請求數是相同的,所以我們可以采用負載均衡在解決這個問題。如“圖1”所示,負載均衡分為服務器端負載均衡和客戶端負載均衡,常見的服務器端負載均衡就是通過Nginx做反向代理,也就是說請求者將所有請求都發送給Nginx,再由Nginx去決定請求哪一臺服務器,“圖1”中標“1”的請求就屬于服務器端負載均衡。客戶端負載均衡是指客戶端在請求前就已經知道了可用的服務器列表,而自己需要請求拿一臺完全由客戶端自己決定,而這種情況通常是由相關算法計算出來的,“圖1”中標“2”的請求就屬于客戶端負載均衡。
3? 系統拆分
一個單體系統中,隨著時間的推移,它所存在的缺陷就會逐漸被暴露出來。
從應用性能來看,如果一個應用本身就面臨性能方面的瓶頸,那么將其拆分后部署于多臺服務器上會極大地減輕因性能帶來的問題。從“圖1”中可以看出,一般系統都是以暴露接口的方式對外提供服務,而且可能有各種功能不同的接口,隨著應用的拆分,可能有一部分接口也隨著功能的分離而被拆分出去,這樣就能給原來的服務器減輕很大壓力。
從開發角度來看,隨著新功能的增加,系統結構可能會越來越臃腫,越來越不利于后續的維護工作。因為在一個系統中,可能存在多個子模塊,從宏觀上看這些模塊組成了一個完整的系統,但如果就某個具體模塊而言,它或許可以被獨立開來,也或許只有小部分功能需要與其他模塊進行耦合,但是這部分可以采用接口調用方式降低他們之間的耦合度。而這樣做可能導致連專屬于該系統的數據庫部分都能被獨立出來,所以數據庫也可以使用不同的機器來部署,從而使系統性能進一步提升。這樣做拆分后,從整體上看,系統模塊更多,需要維護更多的節點,出現問題的可能也就越大,但是從局部來看,我們的開發邏輯卻更加清晰,因為在系統開發中,往往都是循序漸進對功能進行逐漸完善,而非一氣呵成,所以大多時候開發人員關注的只是其中一個點而已,從而使得開發邏輯更加清晰明了。
從維護角度來看,上面雖然也說到了其中的不足之處,但是其好處也是顯而易見的,因為維護人員的最終目標是盡可能地使系統能夠穩健運行,能達到我們使用的需求。而在上述問題中,我們總是擔心可能會因為其中一個模塊宕機而影響整個系統的正常工作,所以在實際生產中,常常是將所有模塊都單獨部署兩份及其以上,即常說的集群,而每一份就是一個獨立的服務,當其中一個服務宕機后還有其他的服務器來維持整個系統的運行,并且就算整個模塊全部宕機,那也只會影響到需要使用該模塊的部分,其他不相關模塊將不會受到影響。
4? 服務間通信技術選擇
在單體應用中,經常存在方法(函數)調用的情況,在調用過程中,我們可能會向一個方法傳遞相應參數,然后等待方法的返回結果,但是在分布式系統中,這些服務通常在不同的計算機上運行,這種情況下我們就不能向以前調用方法一樣調用其他服務的方法,調用方式就變成了模塊之間的相互調用,而這種調用很明顯不是直接的函數調用,而只能通過網絡遠程調用完成,這種通過網絡來發送盒接收處理數據的方法,其中就涉及到數據傳輸方式,即服務間通信的問題。
對應服務間通信的技術,目前常用的有阿里系的Dubbo和Spring Cloud原生方案,在Spring Cloud中,服務提供者以HTTP協議提供服務,服務器消費者在消費時也是通過HTTP協議進行消費,而HTTP是短連接,即每次請求后都會與調用方斷開連接,需要使用時在進行連接建立。相比之下,Dubbo則提供了更多的選擇,比如Dubbo框架則提供Dubbo協議以及HTTP協議,而Dubbo協議是基于Socket(套接字)協議制定的協議,其特點是服務提供者與服務消費者雙方永遠保持者連接狀態,以便他們之間能夠隨時進行數據通信。從協議層面看,似乎Dubbo相比于Spring Cloud原生通信更勝一籌,但實際體驗時就需要從項目使用場景進行多方面考慮了。原因一,Dubbo在使用時,需要服務提供者與服務消費者雙方共同使用一份方法接口,以省去服務提供者Controller層,但是也帶來一個問題,那就是當服務提供者需要在接口中增加一個抽象方法時,就算服務器消費者未使用到這個方法也要重新編譯并運行才能使雙方正常工作,否則就為因為接口不兼容導致錯誤。原因二,如果忘記對Dubbo配置心跳檢測,則長時間不調用服務消費方將可能使雙方建立的長連接被斷開,從而導致錯誤產生。反觀Spring Cloud原生遠程調用,Spring Cloud使用弱依賴進行消費,即在沒有實際產生消費時不需要考慮服務的啟動順序(Dubbo如果不配置禁用檢測時必須考慮),很多時候也不會因為修改了提供方接口而必須編譯并重啟消費方服務。
5? 數據共享之單點登錄
在一個分布式系統中,有些模塊是直接面向用戶的,而這些模塊又可能是以集群的方式進行部署。如“圖2”所示,假設該圖是由“A服務-1”、“A服務-2”、“A服務-3”和“A服務-4”組成的一個集群,因為這些服務的功能完全相同。當用戶第一次請求服務可能是“服務-3”為用戶提供服務,但是當用戶第二次請求服務是,可能是“服務-1”為用戶提供服務,試想一下,假設用戶在“服務-3”上完成登錄,但是在“服務-1”上沒有登錄,這是很不合理的,所以我們需要將用戶的登錄信息進行共享,以至于讓所有服務都能訪問到。而目前業界常用的解決方案是通過Redis內存數據庫來緩存這類需要共享的數據。
6? 數據共享之授權認證
一般情況下,我們會將具有某些特殊功能或者其功能和其他模塊差別非常大的模塊單獨獨立出來作為一個全新系統。這里也郵件系統為例,它和其他模塊既相互獨立,同時也相互依賴,從邏輯上看,它們應該擁有獨立的登錄授權系統,即用戶在其他模塊登錄并不影響在郵件系統的登錄,反之也成立,但是系統歸屬層面上看,不管是郵件系統也好還是其他模塊也好,他們都應該屬于同一個主體,共同使用同一個賬戶進行登錄。還有可能用戶在其他模塊已經登錄,并且上點擊了一個具有明確標題的郵件,從用戶體驗角度來看,這時候應該直接跳到郵件系統并顯示郵件的完整內容,而不應該再去提示登錄。
針對以上問題,可能有很多解決方案,比如在跳轉前,雙方后端系統先進性溝通等,但我認為目前應該采用OAth2.0開放授權協議來完成這一跳轉操作更為合理。如“圖3”所示,由于整個授權并沒有夸系統,所有在使用過程中我們不一定要走完OAth2.0協議的整個流程,我們可以根據實際情況選取一部分即可。比如上述的郵件問題,我們可以在點擊時先去認證系統獲取一個一次性令牌,這部分對應上述A和B步驟,并且帶上令牌和郵件地址去訪問郵件系統的認證接口,當認證通過后郵件系統將為用戶辦理登錄手續,并自動跳轉到用戶需要訪問的頁面,這部分對應于上述C和D步驟,其中在C和D中間,“Resource Serve”會請求“Authorizatior Serve”以驗證“Client”傳遞過來的Token是否正確。
7? 結語
正所謂條條大路通羅馬,這句話在程序界也同樣適用,所以我們需要根據自己的實際情況對技術進行選型,甚至將已有系統進行改造,從而實現自己的個性化需求。比如我們在開發前期,由于系統大部分地方都需要進行修改等原因,所以這個階段對于服務間通信就不適合選取Dubbo作為通信框架,因為在這個時候它所具有的逆勢遠遠大于其優勢,反觀原生Spring Cloud的服務發布方式,其雖然說也具有逆勢,但是在一定程度上這種逆勢是可以被接受的。
參考文獻:
[1]翟永超 著.Spring Cloud微服務實戰[M].電子工業出版社,2017.
[2]Spring Cloud https://spring.io/projects/spring-cloud.
[3]駱斌 主編.需求工程:軟件建模與分析[M].高等教育出版社,2009.
[4]Dubbo通信協議使用 http://dubbo.apache.org/zh-cn/.
[5]OAuth 2.0 — OAuth https://oauth.net/2/.
作者簡介:陳代旺(1996-),男,云南昭通人,本科生,主要研究方向為軟件開發。