黃文亮
(湖南省婦幼保健院,長(zhǎng)沙 410008)
Kotlin協(xié)程技術(shù)在網(wǎng)絡(luò)開(kāi)發(fā)中的應(yīng)用
黃文亮
(湖南省婦幼保健院,長(zhǎng)沙 410008)
隨著互聯(lián)網(wǎng)的發(fā)展,新的技術(shù)與開(kāi)發(fā)框架不斷涌現(xiàn),vertx-web是較有代表性的新型網(wǎng)絡(luò)開(kāi)發(fā)框架之一,而Kotlin則是剛剛成為Android開(kāi)發(fā)語(yǔ)言的一款新的JVM語(yǔ)言,并于最新版本宣布支持協(xié)程技術(shù),將兩者結(jié)合使用會(huì)有何效果,就此展開(kāi)研究。
網(wǎng)絡(luò)開(kāi)發(fā);異步編程;vertx;Kotlin
當(dāng)前,網(wǎng)絡(luò)用戶量不斷增長(zhǎng),傳統(tǒng)的網(wǎng)站架構(gòu)應(yīng)對(duì)高并發(fā)請(qǐng)求顯得越來(lái)越低效,各種異步網(wǎng)絡(luò)編程架構(gòu)也應(yīng)運(yùn)而生,vertx-web是其中的一款優(yōu)秀產(chǎn)品,能夠支持JVM平臺(tái)上的各種語(yǔ)言,并且完全開(kāi)源。
在傳統(tǒng)的thread per request servlet模式下,每個(gè)HTTP請(qǐng)求的處理函數(shù)內(nèi)部的任務(wù)都是同步依次執(zhí)行,而線程占用的資源和切換的帶價(jià)都比較高,如果中間有阻塞任務(wù)(例如文件讀寫(xiě)或數(shù)據(jù)庫(kù)操作)就更為嚴(yán)重;新型的異步框架一般是在底層使用一個(gè)線程池,然后由框架對(duì)子任務(wù)進(jìn)行分配,最大化地減少線程數(shù)量和切換次數(shù)。在使用異步框架進(jìn)行開(kāi)發(fā)的情況下,如何在一個(gè)任務(wù)完成后繼續(xù)執(zhí)行接下來(lái)的其他任務(wù),而不掛起主線程,是一個(gè)重點(diǎn)問(wèn)題,最基礎(chǔ)的方式是使用回調(diào)函數(shù),但有多個(gè)任務(wù)需要依次執(zhí)行時(shí),回調(diào)的嵌套層數(shù)不斷增加,會(huì)大大提高維護(hù)和調(diào)試的難度。
Kotlin語(yǔ)言剛剛推出的協(xié)程特性是解決異步回調(diào)問(wèn)題的利器,這里我們將研究它與vertx-web結(jié)合使用的效果。
vertx-web已經(jīng)為我們提供了基本的網(wǎng)絡(luò)請(qǐng)求監(jiān)聽(tīng)以及路由功能,現(xiàn)在需要做的就是在處理請(qǐng)求的函數(shù)中,將可能阻塞主線程的任務(wù)(例如數(shù)據(jù)庫(kù)操作和文件讀寫(xiě))異步執(zhí)行,執(zhí)行完畢后再回到主線程進(jìn)行下一步的處理。
vertx對(duì)這種場(chǎng)景提供了一種自帶的使用回調(diào)函數(shù)的處理方法,即executeBlocking接口:
這里我們將使用三種不同方式來(lái)執(zhí)行Web處理器中的阻塞任務(wù):1.同步處理;2.使用vertx回調(diào)函數(shù)方式;3.使用kotlin協(xié)程。
Kotlin協(xié)程是通過(guò)編譯技術(shù)實(shí)現(xiàn)(不需要虛擬機(jī)VM/操作系統(tǒng)OS的支持),通過(guò)插入相關(guān)代碼來(lái)生效,與其他語(yǔ)言中的協(xié)程(例如golang)類似,具有相對(duì)于線程小得多的內(nèi)存占用和極快的切換速度。Kotlin協(xié)程針對(duì)Java8提供了lauch函數(shù)(啟動(dòng)無(wú)返回值的協(xié)程),future函數(shù)(啟動(dòng)需要捕獲返回值的協(xié)程)和await函數(shù)(掛起父協(xié)程,異步等待子協(xié)程返回的結(jié)果),這樣在代碼結(jié)構(gòu)上就實(shí)現(xiàn)了類似java.util.concurrent.Future#get()的同步代碼寫(xiě)法,而又不會(huì)阻塞父線程。
程序啟動(dòng)流程描述:
1.創(chuàng)建Verticle對(duì)象(vertx框架中的最小應(yīng)用單元),設(shè)置 3個(gè)路由:/mode1,/mode2,/mode3分別對(duì)應(yīng)三種不同的處理方法:同步處理函數(shù),vertx異步回調(diào)處理函數(shù),Kotlin協(xié)程處理函數(shù)。
2.啟動(dòng)HTTP服務(wù)對(duì)象,監(jiān)聽(tīng)網(wǎng)絡(luò)請(qǐng)求(使用8088端口)。
測(cè)試方式描述:
使用HTTP客戶端測(cè)試工具模擬大批量并發(fā)HTTP請(qǐng)求,發(fā)往localhost:8088端口,查看并發(fā)量、響應(yīng)時(shí)間和錯(cuò)誤率等,并觀察是否有卡死或內(nèi)存溢出現(xiàn)象。
各處理器邏輯:
1.同步處理函數(shù)
接收到HTTP請(qǐng)求后,直接同步讀取文件,讀取完成后響應(yīng)HTTP請(qǐng)求,向客戶端輸出文本。該過(guò)程會(huì)阻塞線程,實(shí)際性能相當(dāng)于傳統(tǒng)的thread per request servlet
2.vertx異步回調(diào)處理函數(shù)
接收到HTTP請(qǐng)求后,調(diào)用vertx.executeBlocking接口,注冊(cè)回調(diào)函數(shù),分別用于讀取文件內(nèi)容和獲取讀文件結(jié)果,響應(yīng)HTTP請(qǐng)求,各個(gè)回調(diào)函數(shù)將被分配給vertx底層的線程池依次執(zhí)行。該過(guò)程不阻塞主線程。
3.Kotlin協(xié)程處理函數(shù)
接收到HTTP請(qǐng)求后,創(chuàng)建一個(gè)父協(xié)程用于調(diào)度,然后在內(nèi)部創(chuàng)建子協(xié)程用于讀取文件,執(zhí)行子協(xié)程時(shí)可以使用await方法掛起父協(xié)程,使代碼邏輯結(jié)構(gòu)接近同步代碼。該過(guò)程不阻塞主線程。
程序中的關(guān)鍵代碼如下:
這里我們?yōu)榱耸钩绦驁?zhí)行情況更接近實(shí)際業(yè)務(wù),使用了一個(gè)隨機(jī)數(shù)來(lái)模擬真實(shí)業(yè)務(wù)中的阻塞與非阻塞任務(wù)比例(例如可以直接讀緩存的查詢就可以立即返回),只有25%的請(qǐng)求被判斷為需要執(zhí)行阻塞操作,阻塞操作使用線程的Thread.sleep來(lái)進(jìn)行模擬,每個(gè)請(qǐng)求中掛起兩次,每次50毫秒。
第一種實(shí)現(xiàn)方式代碼最為簡(jiǎn)單,就是直接以同步方式執(zhí)行,未作任何特殊處理,理論上該方式只能應(yīng)對(duì)較低的并發(fā)數(shù),每個(gè)任務(wù)都阻塞主線程將會(huì)大大降低vertx主線程接收請(qǐng)求的能力,并有超時(shí)風(fēng)險(xiǎn)。
第二種實(shí)現(xiàn)方式就是vertx自帶的異步處理邏輯,用executeBlocking接口分配異步任務(wù)給底層線程池處理,可以看到代碼中出現(xiàn)了兩層嵌套的回調(diào)結(jié)構(gòu),當(dāng)需要執(zhí)行多個(gè)阻塞任務(wù)時(shí),將會(huì)產(chǎn)生較復(fù)雜的嵌套,即“callback hell”,難以調(diào)試和維護(hù),但這種方式性能要遠(yuǎn)高于第一種。
第三種方式是使用Kotlin協(xié)程。這里我們使用了父子兩個(gè)協(xié)程,這是因?yàn)樽尤蝿?wù)內(nèi)部有一個(gè)阻塞操作,在該阻塞操作完成之前,外部程序需要掛起等待,而Kotlin的協(xié)程掛起操作必須在一個(gè)協(xié)程的內(nèi)部進(jìn)行,所以在外部另啟動(dòng)了一個(gè)協(xié)程。父協(xié)程相當(dāng)于普通程序中的主線程,而await操作則類似于線程中的join(但協(xié)程掛起的代價(jià)遠(yuǎn)遠(yuǎn)小于線程掛起)。當(dāng)需要執(zhí)行多個(gè)阻塞任務(wù)時(shí),第二個(gè)或更多的子協(xié)程可以直接跟在第一個(gè)子協(xié)程后面,寫(xiě)法完全類似于同步代碼,不會(huì)產(chǎn)生多層嵌套結(jié)構(gòu)。
第二種和第三種實(shí)現(xiàn)理論上并發(fā)性能應(yīng)在同一級(jí)別。
這里我們使用apache-jeter工具來(lái)模擬HTTP客戶端進(jìn)行高并發(fā)請(qǐng)求測(cè)試。測(cè)試參數(shù)配置如下:
線程組數(shù)量:1,線程數(shù)量:100,循環(huán)次數(shù):10,服務(wù)器名:localhost
請(qǐng)求路徑1:/mode1(對(duì)應(yīng)同步處理器)
請(qǐng)求地址2:/mode2(對(duì)應(yīng)vertx回調(diào)處理器
請(qǐng)求地址3:/mode3(對(duì)應(yīng)Kotlin協(xié)程處理器)
目標(biāo)端口:8088,http method:GET
流量定時(shí)器限制:target throughput(in samples per minute)=240000.0(即每秒 4000)
測(cè)試輸出數(shù)據(jù)如下:
圖1
圖2
可以看到/mode1的最大響應(yīng)時(shí)間高達(dá)6.2秒,平均響應(yīng)時(shí)間和中位數(shù)響應(yīng)時(shí)間都超過(guò)了2.7秒,這主要是由于每個(gè)請(qǐng)求都阻塞了vertx調(diào)度線程,后面的請(qǐng)求自然不得不浪費(fèi)大量的時(shí)間用于等待。而/mode2和/mode3的表現(xiàn)都相當(dāng)不錯(cuò),最大響應(yīng)時(shí)間分別控制在了300毫秒和200毫秒,/mode3的中位數(shù)響應(yīng)時(shí)間甚至只有1毫秒。
圖3
同時(shí),/mode2和/mode3的CPU占用率只在極短時(shí)間內(nèi)超過(guò)了90%,大部分時(shí)間維持在20%以下,內(nèi)存占用率由于測(cè)試客戶端提前分配了線程,測(cè)試過(guò)程中幾乎沒(méi)有發(fā)生變化,也就是說(shuō),服務(wù)端只使用了預(yù)分配的內(nèi)存就完全響應(yīng)了所有請(qǐng)求,由此可見(jiàn)vertx-web+Kot?lin協(xié)程組合的高并發(fā)性能是十分強(qiáng)勁的。
本文對(duì)新型網(wǎng)絡(luò)開(kāi)發(fā)框架vertx-web與新型異步編程技術(shù)Kotlin協(xié)程的結(jié)合使用展開(kāi)了探索,并給出了具體實(shí)現(xiàn),經(jīng)過(guò)實(shí)際測(cè)試對(duì)比,該方案確實(shí)能提供優(yōu)異的并發(fā)性能,同時(shí)還具有協(xié)程的易于開(kāi)發(fā)、調(diào)試和維護(hù)的特點(diǎn),兼顧了程序性能和開(kāi)發(fā)易用性,有助于快速開(kāi)發(fā)高性能的網(wǎng)絡(luò)應(yīng)用。
[1]Roman Elizarov.Module Kotlinx-coroutines-jdk8[DB/OL].https://github.com/Kotlin/kotlinx.coroutines/blob/master/integration/kotlinx-coroutines-jdk8/README.md,2017-5-17.
Kotlin Coroutine in Web Development
HUANG Wen-liang
(Maternal and Child Health Hospital of Hunan,Changsha 410008)
With the development of the Internet technology and the emergence of new development framework,vertx-web is one of the representative of the new network development framework,Kotlin is Android's official language development has just become a new JVM language,which announced support Coroutine technology in the latest version,so as to study the combine effect on them.
Web Development;Async Coding;Vertx;Kotlin
1007-1423(2017)33-0080-05
10.3969/j.issn.1007-1423.2017.33.019
黃文亮(1987-),男,湖南長(zhǎng)沙人,軟件工程師,本科
2017-10-12
2017-11-20