林平榮,陳澤榮,施曉權(quán)
(1.廣州大學(xué)華軟軟件學(xué)院 軟件研究所,廣東 廣州 510990; 2.華南理工大學(xué) 計(jì)算機(jī)科學(xué)與工程學(xué)院,廣東 廣州 510006)
隨著互聯(lián)網(wǎng)的快速發(fā)展和線上用戶的急劇增加,高并發(fā)請(qǐng)求產(chǎn)生的多線程競(jìng)爭(zhēng)共享資源系統(tǒng)面臨著更大的挑戰(zhàn)。高并發(fā)是一種系統(tǒng)運(yùn)行過程中出現(xiàn)同時(shí)并行處理大量用戶請(qǐng)求的現(xiàn)象。多線程競(jìng)爭(zhēng)共享資源指的是在高并發(fā)場(chǎng)景下,多個(gè)請(qǐng)求產(chǎn)生多個(gè)線程,從而造成多個(gè)線程同時(shí)競(jìng)爭(zhēng)訪問共享資源。共享資源應(yīng)用對(duì)象可以是火車票或商品等有限數(shù)量資源,典型的場(chǎng)景有“12306搶火車票”、“天貓雙十一秒殺活動(dòng)”等。由于并發(fā)訪問數(shù)量多且共享資源有限,因此對(duì)系統(tǒng)的并發(fā)性能以及資源一致性有更高的要求。
經(jīng)過查閱相關(guān)文獻(xiàn),目前已有大量解決Web系統(tǒng)性能難題的方案[1-9]被提出,解決方案主要圍繞以下幾個(gè)方面:負(fù)載均衡[5]、數(shù)據(jù)緩存[6,9]、數(shù)據(jù)庫優(yōu)化[7]以及Web前端優(yōu)化[8]。雖然這些方案在一定程度上可以提高系統(tǒng)性能,但考慮的點(diǎn)比較單一,并沒有過多提及多線程競(jìng)爭(zhēng)共享資源時(shí)系統(tǒng)垂直或水平擴(kuò)展下如何確保資源的安全性和一致性。基于此,本文將從多方面綜合考慮,基于數(shù)據(jù)緩存、分布式鎖、消息隊(duì)列、負(fù)載均衡4個(gè)方面進(jìn)行詳細(xì)的分析與研究,提出一種適合高并發(fā)多線程競(jìng)爭(zhēng)共享資源應(yīng)用場(chǎng)景的高性能通用系統(tǒng)架構(gòu)。
系統(tǒng)架構(gòu)分為訪問層、應(yīng)用層、存儲(chǔ)層。訪問層用于Web接入、反向代理、負(fù)載均衡等;應(yīng)用層用于核心業(yè)務(wù)服務(wù)模塊處理,具備服務(wù)治理、調(diào)度、異步通信等核心服務(wù)能力;存儲(chǔ)層進(jìn)行最終數(shù)據(jù)的落地及提供數(shù)據(jù)的能力。架構(gòu)如圖1所示。
圖1 系統(tǒng)架構(gòu)
架構(gòu)部署后,應(yīng)用服務(wù)器先從數(shù)據(jù)庫服務(wù)器將高并發(fā)請(qǐng)求中頻繁訪問的數(shù)據(jù)讀取到緩存服務(wù)器中??蛻舳说母卟l(fā)請(qǐng)求先由LVS(linux virtual server)在IP層均衡地轉(zhuǎn)發(fā)到負(fù)載均衡服務(wù)器,負(fù)載均衡服務(wù)器會(huì)先將資源返回給客戶端,再將請(qǐng)求轉(zhuǎn)發(fā)到應(yīng)用服務(wù)器。應(yīng)用服務(wù)器接收到請(qǐng)求時(shí),在一定時(shí)間內(nèi)不斷嘗試從分布式緩存服務(wù)器中獲取鎖的操作,獲取成功則會(huì)進(jìn)行相應(yīng)的業(yè)務(wù)邏輯處理。在緩存服務(wù)器中進(jìn)行數(shù)據(jù)的增刪改查并將數(shù)據(jù)發(fā)送到消息隊(duì)列服務(wù)器中,最后由消費(fèi)者進(jìn)行消息監(jiān)聽,將數(shù)據(jù)同步到數(shù)據(jù)庫服務(wù)器中,實(shí)現(xiàn)數(shù)據(jù)異步更新。
架構(gòu)設(shè)計(jì)秉持“冗余”+“主動(dòng)故障轉(zhuǎn)移”兩個(gè)原則,“冗余”是為了解決單一節(jié)點(diǎn)出現(xiàn)故障而backup節(jié)點(diǎn)能夠繼續(xù)提供服務(wù),而主動(dòng)故障轉(zhuǎn)移是探測(cè)到故障的發(fā)生則自動(dòng)進(jìn)行轉(zhuǎn)移,將故障節(jié)點(diǎn)流量進(jìn)行引流。架構(gòu)的設(shè)計(jì)從業(yè)務(wù)的角度出發(fā),第一是純靜態(tài)資源的請(qǐng)求,另外一個(gè)是涉及業(yè)務(wù)處理的請(qǐng)求。
純靜態(tài)資源請(qǐng)求的處理:前端固化頁面展示采用靜態(tài)化的思想,將Redis緩存數(shù)據(jù)填充靜態(tài)模板中,形成靜態(tài)化頁面,再推送到Nginx服務(wù)器中,當(dāng)客戶端訪問請(qǐng)求時(shí),LVS均衡地在IP層轉(zhuǎn)發(fā)至處理服務(wù)的Nginx,從負(fù)載的Nginx直接返回沒有涉及業(yè)務(wù)邏輯處理的靜態(tài)頁面,減少與數(shù)據(jù)庫服務(wù)交互的次數(shù),且不執(zhí)行任何代碼,給予客戶端較快的響應(yīng)速度。當(dāng)頁面數(shù)據(jù)發(fā)生變更時(shí),會(huì)觸發(fā)監(jiān)聽器將變更的消息壓入消息隊(duì)列中,緩存服務(wù)感知數(shù)據(jù)變更,便會(huì)調(diào)用數(shù)據(jù)服務(wù),將整理好數(shù)據(jù)重新推送至Redis中,更新Nginx頁面。Nginx本地緩存數(shù)據(jù)也是有一定的時(shí)限,為了避免頁面的集中獲取,采用隨機(jī)散列的策略來規(guī)避。
涉及業(yè)務(wù)請(qǐng)求的處理:倘若LVS負(fù)載均衡分配Nginx的高并發(fā)請(qǐng)求是攜帶業(yè)務(wù)性的,就會(huì)導(dǎo)致多線程競(jìng)爭(zhēng)共享資源時(shí)數(shù)據(jù)不一致的窘境。為避免這種問題,采取Redis分布式鎖的方式以維護(hù)共享資源的數(shù)據(jù)一致和安全問題,然后再將操作后的相關(guān)緩存數(shù)據(jù)寫入消息隊(duì)列服務(wù)器中,最后交由消費(fèi)者進(jìn)行監(jiān)聽消費(fèi),這樣的異步設(shè)計(jì)是為了實(shí)現(xiàn)緩存與數(shù)據(jù)庫的互通,逐級(jí)削減對(duì)數(shù)據(jù)庫服務(wù)器的訪問。此外,為了維護(hù)緩存和數(shù)據(jù)庫的雙寫一致性,堅(jiān)持“Cache Aside Pattern”原則。
設(shè)計(jì)過程中,盡量考慮到分布式緩存服務(wù)和消息隊(duì)列服務(wù)在生產(chǎn)環(huán)境會(huì)產(chǎn)生的細(xì)節(jié)問題,針對(duì)所在問題依次采取下列措施:
(1)在分布式緩存服務(wù)中,考慮緩存應(yīng)用于高并發(fā)場(chǎng)景易發(fā)生的雪崩、穿透和擊穿情況,分別提供相對(duì)應(yīng)的方案如下:
緩存雪崩情況,分別在緩存雪崩前中后的各時(shí)間段,依據(jù)業(yè)務(wù)設(shè)計(jì)相應(yīng)的解決方案:
1)在事前:利用Redis高可用的主從+哨兵的Redis Cluster避免全盤崩潰;
2)在事中:采用本地緩存+Nginx限流,避免數(shù)據(jù)庫壓力過大;
3)在事后:Redis開啟持久化,一旦重啟,自動(dòng)加載磁盤數(shù)據(jù),快速恢復(fù)數(shù)據(jù)。
緩存穿透是高并發(fā)請(qǐng)求的目標(biāo)數(shù)據(jù)都不在緩存與數(shù)據(jù)庫的情況,易導(dǎo)致數(shù)據(jù)庫的直接崩盤,該情況采取Set -999 UNKNOWN,盡量設(shè)置有效期,避免下次訪問直接從緩存獲取。為了防止緩存擊穿,可采取一些熱點(diǎn)數(shù)據(jù)嘗試設(shè)置永不過期。
(2)在消息隊(duì)列服務(wù)中,共享資源的最終持久化是在消息隊(duì)列服務(wù)中進(jìn)行的,為了維護(hù)共享資源的最終一致性,考慮寫入消息隊(duì)列服務(wù)器中消息的消費(fèi)冪等性、可靠性傳輸、消費(fèi)順序性和容器飽滿的情況,檢查數(shù)據(jù)庫主鍵的唯一性來確保消費(fèi)的冪等性,避免重復(fù)消費(fèi)的請(qǐng)求;消息隊(duì)列的可靠性傳輸,在生產(chǎn)者設(shè)置Ack響應(yīng),要求leader接收到消息之后,所有的follower必須進(jìn)行同步的確認(rèn)寫入成功,如果沒有滿足該條件,生產(chǎn)者會(huì)自動(dòng)不斷地重試。為了避免中間件消息隊(duì)列自己丟失數(shù)據(jù),必須開啟持久化功能;消息隊(duì)列的順序消費(fèi),采取一個(gè)queue對(duì)應(yīng)一個(gè)consumer,然后這個(gè)consumer內(nèi)部采用內(nèi)存隊(duì)列做排隊(duì),分發(fā)給底層不同的worker來處理;消息隊(duì)列容器飽滿時(shí),將會(huì)觸發(fā)待命的臨時(shí)程序加入消費(fèi)的行列中,加快消費(fèi)速度,以免消息溢出的慘狀。
現(xiàn)代Web系統(tǒng)一般都會(huì)采用緩存策略對(duì)直接進(jìn)行數(shù)據(jù)庫讀寫的傳統(tǒng)數(shù)據(jù)操作方式進(jìn)行改進(jìn)。緩存大致主要分為兩種:頁面緩存和數(shù)據(jù)緩存。架構(gòu)基于Redis集群實(shí)現(xiàn)數(shù)據(jù)緩存,具有故障容錯(cuò)等功能。Redis完全基于內(nèi)存,通過Key-Value存儲(chǔ)能獲得良好的性能,尤其表現(xiàn)在良好的并行讀寫性能[10]。由于投票容錯(cuò)機(jī)制要求超過半數(shù)節(jié)點(diǎn)認(rèn)為某個(gè)節(jié)點(diǎn)出現(xiàn)故障才會(huì)確定該節(jié)點(diǎn)下線,因此Redis集群至少需要3個(gè)節(jié)點(diǎn),為了保證集群的高可用性,每個(gè)主節(jié)點(diǎn)都有從節(jié)點(diǎn)。實(shí)現(xiàn)Redis集群拓?fù)浣Y(jié)構(gòu)如圖2所示。
圖2 Redis集群網(wǎng)絡(luò)拓?fù)?/p>
Redis集群由6臺(tái)服務(wù)器搭建,分別由3個(gè)主節(jié)點(diǎn)和3個(gè)從節(jié)點(diǎn)組成,數(shù)據(jù)都是以Key-Value形式存儲(chǔ),不同分區(qū)的數(shù)據(jù)存放在不同的節(jié)點(diǎn)上,類似于哈希表的結(jié)構(gòu)。使用哈希算法將Key映射到0-16383范圍的整數(shù),一定范圍內(nèi)的整數(shù)對(duì)應(yīng)的抽象存儲(chǔ)稱為槽,每個(gè)節(jié)點(diǎn)負(fù)責(zé)一定范圍內(nèi)的槽,槽范圍如圖3所示。
圖3 槽范圍
集群?jiǎn)?dòng)時(shí),會(huì)先從數(shù)據(jù)庫服務(wù)器讀取高并發(fā)請(qǐng)求中頻繁訪問的數(shù)據(jù),其中包括共享資源數(shù)據(jù),將數(shù)據(jù)轉(zhuǎn)換為JSON數(shù)據(jù)格式或?qū)ο笮蛄谢跏蓟絉edis服務(wù)器中,數(shù)據(jù)會(huì)根據(jù)哈希算法新增到對(duì)應(yīng)的節(jié)點(diǎn)中。應(yīng)用服務(wù)器接收到高并發(fā)請(qǐng)求進(jìn)行數(shù)據(jù)查詢、修改或刪除時(shí),會(huì)隨機(jī)把命令發(fā)給某個(gè)節(jié)點(diǎn),節(jié)點(diǎn)計(jì)算并查看這個(gè)key是否屬于自己的,如果是自己的就進(jìn)行處理,并將結(jié)果返回,如果是其它節(jié)點(diǎn)的,會(huì)把對(duì)應(yīng)節(jié)點(diǎn)信息(IP+地址)轉(zhuǎn)發(fā)給應(yīng)用服務(wù)器,讓應(yīng)用服務(wù)器重定向訪問。Redis返回結(jié)果后,應(yīng)用服務(wù)器會(huì)將數(shù)據(jù)發(fā)送到消息隊(duì)列服務(wù)器中,并立即將返回結(jié)果給客戶端。
在分布式系統(tǒng)中,共享資源可能被多個(gè)競(jìng)爭(zhēng)者同時(shí)請(qǐng)求訪問,往往會(huì)面臨數(shù)據(jù)的一致性問題[11],因此必須保證資源數(shù)據(jù)訪問的正確性和性能。架構(gòu)使用分布式鎖的方式解決該問題。分布式架構(gòu)下的開源組件很多,如Zookee-per、Redis、Hbase等,相比之下,Redis的性能與成熟度較高。指定一臺(tái)Redis服務(wù)器作為鎖的操作節(jié)點(diǎn),保證獲取鎖的操作是原子性的。Redis本身是單線程程序,可以保證對(duì)緩存數(shù)據(jù)操作都是線程安全的。當(dāng)應(yīng)用服務(wù)器集群接收到高并發(fā)產(chǎn)生的多線程同時(shí)請(qǐng)求訪問共享資源時(shí),線程必須先從Redis服務(wù)器中獲取鎖,使用setnx指令獲取鎖成功后,其余的線程請(qǐng)求獲取鎖的操作會(huì)返回失敗,返回失敗后的線程在一定時(shí)間內(nèi)不斷重試獲取鎖,只有等待已獲取鎖的線程執(zhí)行成功后釋放鎖,才能讓下一個(gè)線程獲取鎖后訪問共享資源。線程獲取鎖成功后,若有線程報(bào)錯(cuò)會(huì)中途退出,獲取鎖之后沒有釋放,就會(huì)造成死鎖。使用expire作為默認(rèn)過期時(shí)間,如果線程獲取鎖后超過默認(rèn)過期時(shí)間,則鎖會(huì)自動(dòng)釋放,為了避免業(yè)務(wù)邏輯處理報(bào)錯(cuò),導(dǎo)致線程中途退出,因此需要再加上捕獲異常處理塊。示例代碼如下:
//獲取lock失敗
if(!set lock true ex 5 nx)
{ return; }
try{#處理業(yè)務(wù)邏輯
……
//釋放lock
del lock }
catch(Exception ex)
{ //釋放lock
del lock }
由于采用分布式鎖,在系統(tǒng)垂直或水平擴(kuò)展的情況下,保證同一時(shí)間的一個(gè)線程獲取到鎖,確保共享資源的安全性和一致性。在高并發(fā)場(chǎng)景下,如果有大量的線程不斷重試獲取鎖失敗的操作,會(huì)造成Redis服務(wù)器壓力過大,Redis服務(wù)器與應(yīng)用服務(wù)器之間交互流量過高。因此,在應(yīng)用系統(tǒng)上使用基于cas算法的樂觀鎖方式解決該問題。cas算法存在著3個(gè)參數(shù),內(nèi)存值V,預(yù)期值E,更新值N。當(dāng)且僅當(dāng)內(nèi)存值V與預(yù)期值E相等時(shí),才會(huì)將內(nèi)存值修改為N,否則什么也不做。借助jdk的juc類庫所提供的cas算法,以及帶有原子性的基本類型封裝類AtomicBoolean,實(shí)現(xiàn)區(qū)別于synchronized同步鎖的一種樂觀鎖,線程不會(huì)阻塞,不涉及上下文切換,具有性能開銷小等優(yōu)點(diǎn)。示例代碼如下:
long startTime = System.currentTimeMillis();
AtomicBoolean state=new AtomicBoolean(false);
//X秒內(nèi)不斷重試調(diào)用compareAndSet方法修改內(nèi)存
while ((startTime+X)>= System.currentTimeMillis()){
//預(yù)期值false,更新值true
if(!state.get()&&state.compareAndSet(false,true)){
//修改內(nèi)存值成功
try{
#獲取分布式鎖
…
//將內(nèi)存值重新修改為false
state.compareAndSet(true,false);
}catch(Exception ex){
state.compareAndSet(true,false);
}
}
}
各個(gè)應(yīng)用系統(tǒng)的線程通過在一定時(shí)間內(nèi)不斷重試修改內(nèi)存值,如果修改成功,才可以繼續(xù)獲取分布式鎖,以解決Redis服務(wù)器的壓力和流量問題。
消息隊(duì)列是一種異步傳輸模式,其主要核心優(yōu)點(diǎn)為以下3點(diǎn):業(yè)務(wù)解耦、通信異步、流量削峰[12,13],因此可應(yīng)用于很多高并發(fā)場(chǎng)景。目前主流的消息隊(duì)列中間件有Kafka、RabbitMQ、ActiveMQ及Microsoft MSMQ[12]等,其中RabbitMQ是一個(gè)開源的AMQP實(shí)現(xiàn),支持多種客戶端,總共有6種工作模式,用于在分布式或集群系統(tǒng)中存儲(chǔ)轉(zhuǎn)發(fā)消息,具有較好的易用性、擴(kuò)展性、高可用性。架構(gòu)使用RabbitMQ實(shí)現(xiàn)數(shù)據(jù)庫與Redis緩存數(shù)據(jù)的同步,可以根據(jù)實(shí)際高并發(fā)場(chǎng)景進(jìn)行判斷選擇合適的通信模式以及生產(chǎn)者與消費(fèi)者的對(duì)應(yīng)關(guān)系。下面以RabbitMQ的路由模式為例,具體實(shí)現(xiàn)的路由模式結(jié)構(gòu)如圖4所示。
圖4 路由模式結(jié)構(gòu)
按照實(shí)際業(yè)務(wù),以明顯的類別劃分,聲明一個(gè)交換機(jī),4個(gè)消息隊(duì)列,創(chuàng)建4個(gè)消費(fèi)者分別監(jiān)聽4個(gè)消息隊(duì)列,應(yīng)用系統(tǒng)數(shù)量對(duì)應(yīng)生產(chǎn)者數(shù)量。當(dāng)系統(tǒng)接收到高并發(fā)請(qǐng)求資源時(shí),會(huì)使用Redis分布式鎖確保所有系統(tǒng)產(chǎn)生的線程同步執(zhí)行訪問共享資源,保證共享資源的安全性、一致性。釋放鎖后,再將處理完成的數(shù)據(jù)更新到Redis中,Redis返回結(jié)果后,系統(tǒng)會(huì)將數(shù)據(jù)轉(zhuǎn)化為JSON格式,按照消息路由鍵,確保同個(gè)客戶端請(qǐng)求的數(shù)據(jù)在同個(gè)消息隊(duì)列中,能夠在消費(fèi)者進(jìn)行數(shù)據(jù)增刪改時(shí)按照先后順序執(zhí)行,保證系統(tǒng)公平性。設(shè)置路由鍵后將數(shù)據(jù)發(fā)送到交換機(jī)中,交換機(jī)會(huì)根據(jù)路由規(guī)則將不同類別的數(shù)據(jù)發(fā)送到綁定的對(duì)應(yīng)消息隊(duì)列中,每個(gè)消息隊(duì)列都有對(duì)應(yīng)的一個(gè)消費(fèi)者,消費(fèi)者會(huì)監(jiān)聽對(duì)應(yīng)的消息隊(duì)列,監(jiān)聽到消息后會(huì)進(jìn)行JSON格式數(shù)據(jù)的解析,再將數(shù)據(jù)同步到數(shù)據(jù)庫中。利用RabbitMQ消息隊(duì)列,進(jìn)行強(qiáng)弱依賴梳理分析,將數(shù)據(jù)同步到數(shù)據(jù)庫的操作異步化,以解決數(shù)據(jù)庫的高并發(fā)壓力。
由圖1~3可知,3種水灰比的試件,經(jīng)過凍融0次、25次和50次后,其峰值應(yīng)力均隨著應(yīng)變加載速率的增加而增加。
應(yīng)對(duì)高并發(fā)訪問,負(fù)載均衡技術(shù)是構(gòu)建高并發(fā)Web系統(tǒng)有效的方法[14]。常用負(fù)載均衡方法有:①DNS負(fù)載均衡;②NAT負(fù)載均衡;③軟件、硬件負(fù)載均衡;④反向代理負(fù)載均衡。①方法無法獲知各服務(wù)器差異,④方法在流量過大時(shí)服務(wù)器本身容易成為瓶頸,③方法中的軟件方式是通過在服務(wù)器上安裝軟件實(shí)現(xiàn)負(fù)載均衡,如LVS、PCL-SIS等。LVS是Linux虛擬服務(wù)器,從操作系統(tǒng)層面考慮,架構(gòu)訪問層采用LVS及Nginx集群組成,分別在IP層和應(yīng)用層進(jìn)行請(qǐng)求的負(fù)載均衡轉(zhuǎn)發(fā)。通過OSI七層模型結(jié)構(gòu)可知,在IP層實(shí)現(xiàn)請(qǐng)求的負(fù)載均衡比在應(yīng)用層更加高效,減少了上層的網(wǎng)絡(luò)調(diào)用及分發(fā)。架構(gòu)采用LVS的DR模式,由LVS作為系統(tǒng)整個(gè)流量的入口,采用IP負(fù)載均衡技術(shù)和基于內(nèi)容請(qǐng)求分發(fā)技術(shù),將請(qǐng)求均衡地轉(zhuǎn)發(fā)到不同的Nginx服務(wù)器上,但是只負(fù)責(zé)接收請(qǐng)求,結(jié)果由Nginx服務(wù)器直接返回,避免LVS成為網(wǎng)絡(luò)流量瓶頸。由多個(gè)客戶端發(fā)送的高并發(fā)請(qǐng)求,會(huì)先進(jìn)行DNS尋址,找到對(duì)應(yīng)機(jī)房的公網(wǎng)IP,由LVS接入,再將請(qǐng)求均衡的轉(zhuǎn)發(fā)到Nginx服務(wù)器,再由Nginx服務(wù)器將請(qǐng)求均衡的轉(zhuǎn)發(fā)到應(yīng)用服務(wù)器,有利于Nginx服務(wù)器的擴(kuò)展,可將整個(gè)系統(tǒng)進(jìn)行水平拓展,加入更多的硬件支持,提高并發(fā)請(qǐng)求的處理效率。
為了驗(yàn)證架構(gòu)設(shè)計(jì)的有效性,選擇具有高并發(fā)資源競(jìng)爭(zhēng)需求的選課場(chǎng)景為例,課程的額定容量就是共享資源,學(xué)生選課的過程其實(shí)就是搶占額定容量的過程。把案例分別部署在普通集群架構(gòu)和本文提出的集群架構(gòu),基于內(nèi)網(wǎng)同個(gè)網(wǎng)段搭建實(shí)驗(yàn)平臺(tái)環(huán)境。普通集群采用Nginx技術(shù)實(shí)現(xiàn)負(fù)載均衡,使用了基于表記錄的新增與刪除操作,利用字段唯一性約束的方式實(shí)現(xiàn)數(shù)據(jù)庫分布式鎖以及使用Redis實(shí)現(xiàn)會(huì)話共享??紤]到測(cè)試的公平性以及簡(jiǎn)化測(cè)試復(fù)雜度,保證兩個(gè)集群的物理配置以及應(yīng)用配置參數(shù)一致,其中設(shè)置Nginx主要參數(shù) keepAlivetimeout、fastcgi_connect_timeout、fastcgi_send_timeout、fastcgi_read_timeout均為8000,以服務(wù)器配置為準(zhǔn)分配權(quán)重,設(shè)置 tomcat主要參數(shù)connectionTimeout為80 000,maxConnections為80 000,maxThreads為8000,minSpareThreads為100,maxIdleTime為6000。在測(cè)試中本文集群排除了LVS,直接以Nginx服務(wù)器作為請(qǐng)求負(fù)載均衡以及Redis單機(jī)方式完成整個(gè)測(cè)試流程。沒有引入LVS的原因是本次實(shí)驗(yàn)是以搶課業(yè)務(wù)為測(cè)試場(chǎng)景,LVS的加入只是為了保障Nginx容錯(cuò)性,且目前的并發(fā)數(shù)實(shí)在難以使其出現(xiàn)宕機(jī)情況,故剔除LVS的引入,選擇直接利用Nginx負(fù)載均衡、轉(zhuǎn)發(fā)的特性,對(duì)深層次的服務(wù)進(jìn)行并發(fā)測(cè)試,更能獲取到架構(gòu)內(nèi)部整體的性能指標(biāo)。
普通集群一共用了5個(gè)節(jié)點(diǎn),其中節(jié)點(diǎn)2、3作為Tomcat服務(wù)節(jié)點(diǎn),其余的分別為節(jié)點(diǎn)1(Nginx節(jié)點(diǎn))、節(jié)點(diǎn)5(Mysql節(jié)點(diǎn))、節(jié)點(diǎn)4(Redis節(jié)點(diǎn))。本文集群與普通集群的區(qū)別是在節(jié)點(diǎn)4加入了RabbitMq,節(jié)點(diǎn)4 作為數(shù)據(jù)緩存、消息隊(duì)列和分布式鎖節(jié)點(diǎn)。兩個(gè)集群的各節(jié)點(diǎn)配置和容器版本等見表1。
表1 服務(wù)器節(jié)點(diǎn)配置
測(cè)試過程采用JMeter測(cè)試工具進(jìn)行性能數(shù)據(jù)采集,通過不斷調(diào)高并發(fā)數(shù)量得到的各項(xiàng)性能指標(biāo)值見表2和表3。測(cè)試過程中通過服務(wù)代理的方式監(jiān)控節(jié)點(diǎn)機(jī)器,實(shí)時(shí)抓取各節(jié)點(diǎn)的資源使用情況,并重點(diǎn)記錄了核心節(jié)點(diǎn)的資源使用率,具體見表4和表5,列表頭的1-cpu表示節(jié)點(diǎn)1的CPU資源使用特征。由于篇幅所限,只羅列比較關(guān)鍵的數(shù)據(jù)。
表2 普通集群架構(gòu)指標(biāo)值
表3 本文集群架構(gòu)指標(biāo)值
表4 普通集群下的節(jié)點(diǎn)表現(xiàn)/%
表5 本文集群下的節(jié)點(diǎn)表現(xiàn)/%
根據(jù)不同并發(fā)數(shù)測(cè)試得到的數(shù)據(jù),轉(zhuǎn)換成兩種集群架構(gòu)的TPS和響應(yīng)時(shí)間變化曲線,分別如圖5、圖6所示,并且將相應(yīng)的節(jié)點(diǎn)表現(xiàn)轉(zhuǎn)換為曲線,如圖7、圖8所示。
圖5 TPS變化曲線
圖6 響應(yīng)時(shí)間變化曲線
圖7 Memory變化曲線
圖8 CPU變化曲線
此次測(cè)試中并未針對(duì)Tomcat容器、數(shù)據(jù)庫連接池等進(jìn)行過多的配置參數(shù)優(yōu)化,相信后續(xù)加強(qiáng)對(duì)容器調(diào)優(yōu),架構(gòu)的性能還有一定的上升空間。
本文從多角度挖掘高并發(fā)請(qǐng)求的痛點(diǎn),從數(shù)據(jù)緩存、分布式鎖、消息隊(duì)列、負(fù)載均衡4個(gè)方面進(jìn)行分析與研究,提出了一種適合高并發(fā)多線程競(jìng)爭(zhēng)共享資源場(chǎng)景的高性能系統(tǒng)通用架構(gòu)。架構(gòu)基于Redis集群實(shí)現(xiàn)數(shù)據(jù)緩存,避免直擊數(shù)據(jù)庫;攔截失敗請(qǐng)求,提高系統(tǒng)吞吐量;采用分布式鎖,在系統(tǒng)垂直或水平擴(kuò)展的情況下,保證同一時(shí)間的一個(gè)線程獲取到鎖,確保共享資源的安全性和一致性,同時(shí)采用基于cas算法的樂觀鎖方式避免Redis服務(wù)器與應(yīng)用服務(wù)器之間交互流量過高導(dǎo)致服務(wù)器壓力過大;借助消息隊(duì)列組件進(jìn)行異步處理操作,降低數(shù)據(jù)庫服務(wù)陷入堵塞的風(fēng)險(xiǎn);從LVS調(diào)度轉(zhuǎn)發(fā)解決Nginx負(fù)載均衡的單點(diǎn)故障。本文架構(gòu)面臨流量沖擊仍可維持服務(wù)高可用,組件服務(wù)皆可縱向擴(kuò)展,具有廣泛的通用性,可適用于高校搶課、高峰訂票、商品秒殺等典型場(chǎng)景,對(duì)于構(gòu)建高并發(fā)應(yīng)用具有一定參考價(jià)值。