李思莉,楊井榮,茍 強
(成都理工大學 工程技術學院 電子信息與計算機工程系,四川 樂山 614000)
大量用戶在同一個時間點同時訪問某個相同的站點稱為高并發。高并發現象在如今的互聯網行業應用中非常普遍,如12306鐵路購票網站,雙11時阿里巴巴、京東、唯品會等電子商務網站要處理的并發數通常都高達每秒百萬級。但如何處理高并發卻是一個非常難的技術瓶頸。該文研究的是在單機無集群的情況[1],以NIO為基礎的同步非阻塞IO,而非傳統的IO方式,結合Vert.x[2]的事件驅動完成同步通信與異步事件處理的并行運算,是數據通信部分百萬級別的并發。并在此研究基礎上利用Java Spring線程池,完成了課外學分管理系統。通過大量的實驗數據,與傳統Web應用的IO方式進行對比,得出論文研究并實現的MVC層的擴展、數據安全優化、同步非阻塞模式與NIO在Web的應用中完全能勝任百萬級甚至更高的并發量的結論。同時,由于這種異步事件處理方式是基于Spring管理的線程池,在系統擴展上,很容易實現分布式系統完成更多的并發與集群架設。
客戶/服務器模式(C/S)不能應對多平臺帶來的開發時間、開發效率、開發投入等多方面要求,加之各PC之間操作系統不同,為了兼顧過時的Windows XP系統,在開發PC端系統時通常出現兩種情況:(1)開發多個版本;(2)兼顧XP不使用高版本Windows系統的特性和高效率API。這兩種情況都不好,因此在開發學分管理系統時,放棄C/S架構,使用B/S[3]架構。這套架構,在客戶端上只需要前端網頁和可運行在系統上的瀏覽器就可滿足用戶對于多平臺,不同系統設備的需求,節約開發時間和開發成本。在具體的程序內部架構設計上,傳統的三層架構已經無法滿足系統高并發需求,數據傳輸中傳統的I/O設計模式和傳統的I/O傳輸必將面臨性能瓶頸甚至會導致整個課外學分管理系統的崩潰。因此在實際的開發過程中,將系統設計成5層模式,由外向內展開依次是:
(1)負責與前端信息交互的restfulApi層;
(2)負責管理處理邏輯的中央組件管理層;
(3)負責管理并發線程的調度和管理的并發層;
(4)負責處理信息的邏輯層;
(5)負責持久化信息的ORM層。
通過實驗證明,該架構在技術上是可行的,在并發請求每秒10萬數量級上依然保持穩定。
要完成十萬級,百萬級的并發請求,普通的IO會導致系統性能急速下降,這將導致系統無法正常運行。因此,在具體的開發實現中,使用了非阻塞式IO。非阻塞式IO分為異步非阻塞IO和同步非阻塞IO。通過對學分管理系統的需求分析,得出整個流程不需要消耗很多的等待時間,因此,采用同步非阻塞IO模式。加之非阻塞IO、零拷貝、事件驅動等特性,在開發生態圈里有很多經驗可取,在框架的設計上也能利用現有的同步非阻塞IO框架,不必重頭開發底層。
Server層的高并發,著重體現在線程安全上,在數據處理上,不能出現很多線程去同時操作運算數據的情況。對于線程安全,在整個Server層實現上完全使用了線程安全的數據結構,如:ConcurrentHashMap,SynchronizeList等,需要注意的是要避免使用過時的線程安全的數據結構,如:vector,HashTable等,這會降低整體的效率。
除了線程安全的數據結構,很多方法的邏輯也不允許多線程同時操作,一般的解決方案是使用Synchronize關鍵字對需要加鎖的方法或者代碼塊進行修飾,但這是一種悲觀鎖,如果發生異常,會出現阻塞,這對系統是致命的,不僅會導致后續的操作掛起,還會導致程序崩潰。要避免發生這種情況,在Server層實現上采用了非阻塞的并發算法CountDownLatch[4],它是Java提供的原生非阻塞并發算法,可以有效實現學分管理系統的線程同步。
利用數據庫的隔離機制完成數據安全是一種低效的做法。在學分管理系統持久化層的設計中,將數據安全因素放到調用持久化層的Server層里面去實現[5]。持久化層事務的傳播機制統一采用Spring的傳播機制,并利用緩存技術,減少系統響應時間。在初始化時,采用快速數據庫連接池初始化一個足夠大的數據庫連接池交給持久化層使用[6]。
并行數據接收是并發的開始,這里采用了成熟的模式設計,即一個接收的總線Boss線,多個負責傳輸轉發到相應處理的Server邏輯的Worker線程,將多個Worker線程初始化為一個線程池由Spring統一管理,它存在于整個Springboot程序中。這個模式實現了代碼復用,減少了初始化、調用等冗余代碼,也能更好地融合在主框架里。
Restful層的設計采用Vert.x Web框架而放棄了低性能的SpringMvc[7],Vert.x是事件驅動的,整個處理過程基于事件總線而非單獨的控制器。
整個系統采用YAML配置模板,Server配置了Http訪問端口,訪問根路徑和內嵌的Tomcat編碼[8]、響應時間等配置,其關鍵參數如表1所示。

表1 關鍵配置參數
Restful層的設計采用Vert.x Web框架,它采用異步模式,通過事件循環調用存儲在異步任務隊列中的任務,大大降低了傳統阻塞模型中線程對于操作系統的開銷[9]。
整個學分管理系統實現高并發通信高效率的核心步驟是創建多路復用的通信通道。為了減少冗余代碼,主框架采用Springboot。將復用通道交由Spring統一管理,在此之前要創建由Spring管理的Worker線程池,部分代碼如下:
@Component
public class SpringVertxFactory implements VerticleFactory, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public String prefix() {
return "credit"; }
@Override
public boolean blockingCreate() {
return true;
}
@Override
public Verticle createVerticle(String s, ClassLoader classLoader) throws Exception {
String clazz=VerticleFactory.removePrefix(s);
return (Verticle) applicationContext.getBean(Class.forName(clazz));
}
@Override
public voidsetApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext; }}
上述代碼完成了三個目標:
(1)實現了VerticleFactory和Application ContextAware接口,VerticleFactory接口能產生Vert.x工作線程,ApplicationContextAware接口是當SpringContext初始化完成后,用于獲取SpringContext的接口,其目的是將產生的Vert.x工作線程Verticle加入到SpringContext中,達到由Spring容器統一管理Verticle線程池的目的[10]。
(2)初始化通道總線和事件總線,注冊RestfulApi到Vert.x,并設置相關聯的屬性。使用了線程同步的方式保證初始化順序執行。
(3)初始化Vert.x核心容器Vertx,并設置最大線程量和最大連接響應時間。注冊Vert.x工作線程到Vert.x容器,檢查初始化過程中是否超時,初始化過程中是否有錯誤,以及是否全部線程都已經初始化完成。
由于Vert.x的工作線程由Spring容器統一管理[11],只有當Spring容器初始化完畢后才能使用Spring容器里的Vert.x工作線程,故需要監聽Springboot的啟動消息事件。Vert.x中的RestfulApi沒有一套現成的能直接完成映射注冊的開發注解或模板類,因此Vert.x的RestfulApi需要自己去實現。
該文定義的RestfulApi必須繼承AbstractVerticle這個抽象類,才能被Vert.x核心容器接收作為通信處理鏈上的一部分。并且會去執行該對象類里面的start方法,所以一定要重寫這個方法。這個方法里面首先要創建處理邏輯的代理接口,而且這個接口也必須要能被Vert.x核心容器接受,然后注冊訪問地址[12]到Vert.x的核心路由上面,由于整個過程是事件驅動的,所以要設立監聽端口。將事件處理的邏輯結果寫入到Router的routingContext中,這樣才可到前端解析[13]。
Java原生的NIO API在開發中顯得過于繁瑣,也未封裝成一個高并發的架構。為了減少開發帶來的時間消耗和框架封裝的性能消耗,采用現有的Vert.x框架。現有主流的Web開發中Spring是必不可少的,將兩者結合由Spring管理Vert.x的部分組件能用工程化的開發流程去簡化異步Web程序的開發。
將部分ajax請求接口更改為Vert.x開發,應用更多Spring帶來的方便且規范的服務,減少在后續服務帶來的開發難度和性能消耗。
整合Web其余所有部分通過Spring與Vert.x協同工作,并借此管理Vert.x的異步線程池,動態地申請資源,減少性能浪費。
為了保證實驗的可行度和可信度,采用由Apache基金會開發的JMeter壓力測試工具[14-15],對該項目進行測試,并且實驗是基于課外學分管理系統設計的,這兩個不同接口會運行在同一個Java虛擬機中,最大程度地保證了在運行環境、參數、性能等各方面的一致性,得出的實驗結果對比也更有說服力。
設定為百萬級并發請求:讓程序能模擬一百萬個用戶對同一個接口模塊請求。
設定圖形結果計算包括吞吐量和響應時間。
固定時間為一分鐘或者一分鐘又幾秒鐘(結束上百個線程會消耗幾秒時間)。
接口調用的邏輯和功能完全一致。
設定線程請求無延遲,即延遲0 ms。
在實驗過程中,為了保證發送的數量是一樣的,應當同時啟動兩個線程組,且設置完全一模一樣,設置在同一個測試組中,啟動整個測試組。
在此期間密切關注線程數量變化,記錄線程非滿載的情況下的測試數據,在后期處理數據時需要除去這一部分不合格的啟動數據。觀察后臺是否已經崩潰,因為在百萬級的并發下SpringMvc大概率會假死,如果已經崩潰或者假死則數據上沒有對比的必要性。
在實驗數據監聽器中取得相應數據和統計圖形,首先在SpringMvc組里面取得吞吐量和響應時間結果,如圖1和圖2所示。

圖1 學分管理系統SpringMvc吞吐量

圖2 學分管理系統SpringMvc響應時間
Vert.x的響應時間和吞吐量如圖3和圖4所示。

圖3 學分管理系統Vert.x吞吐量

圖4 學分管理系統Vert.x響應時間
由上面四幅圖片可以獲得的信息,仍然需要比對SpringMvc和Vert.x,需要排除不合格的測試量。首先排除前10秒鐘的線程啟動時測試的數據,再減去20秒后衰減的線程量這樣的響應時間才是合格的比對樣本,其結果如圖5所示。
相同時間發出的數量能保證在誤差范圍內,故可以記錄所有的量一次吞吐代表完成一次請求,結果如圖6所示。

圖6 吞吐量對比
在實驗數據的對比下能發現,在響應時間是萬倍的差距,在吞吐量上是數十倍的差距,在同一JVM,同一功能,執行同一邏輯,同一線程組中排除不合格數據得出的數據對比中可以得到如下結論:
(1)相較于傳統且主流的SpringMvc的IO模式,NIO更能勝任高并發環境,而且這個性能是提升巨大的,能在主要的兩方面中體現出指數倍的差距;
(2)能在相同邏輯下大幅度減少通信時間;
(3)相同條件下,NIO通信的程序能處理更多的請求。