https大勢已來?看騰訊專家如何在高並發壓測中支持https


WeTest 導讀

用epoll編寫一個高並發網絡程序是很常見的任務,但在epoll中加入ssl層的支持則是一個不常見的場景。騰訊WeTest服務器壓力測產品,在用戶反饋中收到了不少支持https協議的請求。基於此,本文介紹了在基於epoll的高並發機器人框架中加入openssl,實現對https支持時的基本實現思路。
 
一、背景

2014年,谷歌在其官方博客中發布公告稱,為了打造更安全的互聯網環境,谷歌搜索引擎將嘗試把“是否使用安全加密”(HTTPS)作為搜索排名算法中的一個參考因素,使用加密技術的網站將得到更多的展示機會,排名相對同類網站也更有優勢。面對運營商的http劫持,廣告嵌入,將產品頁面重定向到其他頁面,http站點通常束手無策。所以僅僅是為了加密流量,https的部署也將成為大勢所趨。

 

騰訊WeTest服務器性能測試原本的簡單模式,主要針對以http協議為主的輕量級場景(游戲業務一般會采用更復雜的協議)。而在上線之后,收到了不少需要https測試的用戶反饋,由此決定在我們使用的壓測框架中加入https支持。

 

騰訊WeTest服務器性能測試是一個基於epoll的高並發機器人網絡行為模擬框架。其中的網絡傳輸模塊,是用單線程epoll的多路復用方式,將多個機器人和服務器的交互包進行非阻塞高速轉發。配合以Linux系統層面的一些配置優化,就可以達到單進程幾千的機器人數量。

 

后台開發同學,一般在自己的web服務器中加https的配置相對常見,但自己到socket層去寫https的代碼實現,這個需求還真不太多。動手之前,我們調研了https層可用的庫,最常見的就是OpenSSL了。像curl也有https的相應支持,不過考慮到要在tcp socket(epoll)這一層實現,還是選擇了OpenSSL。

 

 

二、OpenSSL

 

在介紹OpenSSL之前,首先要介紹下https。https是什么?https就是http+tls/ssl(下文簡稱ssl)。從網絡協議的層面來說,tcp是傳輸層協議,http是應用層協議,ssl就是為了給應用層的http報文加密,專門加在tcp和http之間的一層安全協議。網絡上對https協議進行介紹的好文很多,如:http://www.cnblogs.com/LittleHann/p/3741907.html

,詳細闡述了https的原理,這里就不再贅述。

 

OpenSSL就是在常用的socket層連接建好之后,完成ssl層的連接建立、收發包、連接釋放,其實調用的基本思路還是很清晰的。我們以本文中要實現的client側為例,如下圖所示:

 

可以看到,就是在普通的socket建立好tcp連接后,再用SSL_connect建立ssl層的連接。然后用SSL_read/SSL_write替代recv/send進行收發數據,並在close socket的前后釋放ssl層的資源即可。

 

由於已經實現了基於epoll的客戶端數據收發和http協議的解析,所以這兩者都不是本文的重點——下文主要介紹的是在epoll的框架中使用openssl收發數據時,需要注意的地方。

 

 

三、全雙工實現

 

看到這個標題,肯定有同學會納悶:tcp本來不就是全雙工的么,https是在tcp層之上的,怎么還會單獨拎出這個來說?沒錯,tcp是全雙工的,但openssl的實現,不代表你能像普通socket一樣在收發兩個通道上隨意操作。

 

要點1:OpenSSL並發讀寫,是不安全的

其實OpenSSL官方的文檔上還沒找到直接的話術指明同一個SSL不能兩個線程並發讀寫,但實際上,外網上、km上都有文章說在多線程並發情況下讀寫會引起程序崩潰。想來是SSL對象內部實現中,維護了共享的狀態變量或者緩存區之類的資源,並發讀寫時會改壞數據導致崩潰。可以通過初始化時設置加鎖回調的方式來避免(http://linux.die.net/man/3/crypto_set_locking_callback),但鎖終究對性能有不小的影響。

 

不過gaps現有的實現是單進程的,即單進程中通過epoll完成了多個機器人連接的收發數據,所以並不存在多線程並發的問題,也無需加鎖。由此,小標題的“全雙工實現”其實更嚴格說是”單進程情況下讀寫互不干擾的雙工實現“。

 


要點2:OpenSSL的建鏈、收包、發包接口,其是否阻塞都隨socket本身屬性而變,所以OpenSSL可以非阻塞使用

 

在我們的場景下,用epoll來維護機器人的並發建連接和收發包,當然希望任何一個動作都是非阻塞的,這樣才能將多路復用的功效發揮到極致。那現在加了個ssl2進去,是否還能保持這一點?答案是能。所以,這里的要點是,OpenSSL的建立連接、收包、發包,都可以是非阻塞的。

 

建立連接不用上圖中的SSL_connect,而用SSL_do_handshake。這樣,如果socket本身設置為非阻塞的,那這個操作也就不會阻塞,而是有三種返回可能:

 


1)返回0:

意味着ssl層的交互阻塞了。直觀地去理解,雖然這時候tcp已經連好了,但總要去收發些握手數據什么的來建立ssl層連接吧,而這個過程收發數據阻塞了。此時,用SSL_get_error()可以獲取具體的錯誤碼:若是SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE,就在epoll中關注該連接的可讀或可寫事件,並在事件被觸發時接着調用SSL_do_handshake,直到返回下面的1。

 

2)返回1:

ssl層建鏈數據交互完成,可以開始收發業務數據了

 

3)<0:

協議或連接層各種異常出錯,不再詳述。

 

非阻塞建立SSL連接的過程如圖所示:


 

建鏈之后,就是收發數據了。由於socket為非阻塞,所以收發數據的函數SSL_read、SSL_write一樣會非阻塞。他們的參數和普通的recv/send等讀寫類函數很像,就是傳入buff和length這些。需要注意的在於,和SSL_do_handshake一樣,如果返回值大於0,表示成功收發了業務層數據;如果返回值等於0,則需要判斷下錯誤碼是不是SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE,即讀寫阻塞了。

 

發包,即發送一個請求到http服務器的邏輯如下圖:

可以看出,發包的邏輯和普通的使用epoll發包的邏輯大概相同,區別在於以下幾點:

1)SSL_write替代了普通的send

2)SSL_write也會阻塞。只是,我們這里只關注寫阻塞(即圖中的錯誤碼為SSL_ERROR_WANT_WRITE),然后加入epoll,關注socket的可寫事件。

 

上面的第2點就是openSSL比較奇葩的一個地方了:調用SSL_write發包,可能返回的是一個SSL_ERROR_WANT_READ,即發包可能阻塞在讀操作!無法理解吧。其實這個是因為在http的底層,會有一個重協商的過程,這個過程,相當於在業務數據正在單向地收或發的時候,突然在ssl鏈路層要去交互協議數據,重建鏈接了——那這個時候,重協商協議數據交互是雙方的,client可能剛好在recv協議數據時被阻塞了,那就只能乖乖地等socket可讀了——SSL_write在這種情況下,會返回一個SSL_ERROR_WANT_READ,等待可讀。而下次可讀事件發生時,還需要重復調用SSL_write,直到SSL_write成功......是不是有點奇怪,epoll告知我們socket可讀了,我們居然要對socket調用寫操作......

 

重協商的原理網上也有很多,這里不詳述。只是,我們在全雙工的模式下,對於SSL_write操作,只認為寫阻塞是正常的!一旦因為重協商發生而產生讀阻塞,我們就認為鏈路出現問題了——否則,無法真正實現收發互不考慮的全雙工,這個會在半雙工的時候具體介紹。

 

收包,即接收服務器側返回的http響應的邏輯如下圖:

可以看到,收包的邏輯和發包類似,也是有可能會因為重協商產生寫阻塞,我們在全雙工實現的做法,一樣是認為出錯。

 

 

 

四、加入半雙工開關——重協商考慮

 

要點3:當SSL_read或SSL_write阻塞時,需要在SSL對象上重復調用該操作直到收發完成

 

要點3正是我們上面提到的奇葩之處。這也是在OpenSSL的官方文檔中說明了的:

 

所以,我們如果需要真正支持重協商,就必須有一種半雙工的實現——這種實現會在收發包阻塞在對應的操作后,記錄一個中間狀態,不處理當前不期望的收或發,直到之前被阻塞的操作完成。這種情況下,相當於對這個自定義的狀態維護了一個狀態機。由於實際實現非常復雜,所以代碼細節就不在這里貼了。概括一下,大概是下面的這個狀態機轉移圖和一些要點:


 

如上圖:

1)“正常狀態”可以認為連接當前是空閑的,不需要收發數據;

2)正常態下有客戶端數據要發送,則調用SSL_write接口,如果阻塞,則會進入圖左的兩個狀態;

3)正常態下epoll提示有服務端返回的數據可讀,則調用SSL_read接口,如果阻塞,則會進入圖右的兩個狀態;

4)在外側的四種狀態下,不是當前期望的操作,都不會處理:如阻塞在等待讀/寫時,epoll的可寫/可讀事件都不理會,又如,阻塞在任何一種狀態時,客戶的發包請求都會入隊列;

5)紅字標出的兩個狀態和平時普通socket+epoll的操作剛好相反,值得留意。

 

如此,一個半雙工的https客戶端實現就有了。但它的缺陷很明顯:每次讀、寫操作都可能阻塞另一個方向上的數據傳輸,性能會有急劇的下降。由於通常服務器端並不推薦重協商的過程,所以這種情況也是很少見的。因而,全雙工的實現加了開關,當普通https服務器進行壓測時,關閉開關,保證性能;當面對真有重協商這種特殊需求的服務器時,才打開開關。

 

 

五、HTTPS測試功能的使用

 

下面,我們來看一下如何在簡單模式中進行https頁面的服務器性能測試。

1) 點擊服務器性能測試產品首頁(http://wetest.qq.com/gaps/ )中的快捷入口:HTTP直壓。模式選擇簡單模式,名稱和描述可以自己填寫。(圖中示例起始人數50人,每隔60秒增加50人,加到200人為上限)

 

點擊左側“HTTP直壓“進入壓測

 


輸入合適的測試標題和測試設置

(此圖為動圖,橫屏觀看效果更佳)

 

2)新建一個客戶端請求,接口壓測包括讀寫接口,讀接口基本是GET請求,寫接口基本是POST請求。GET請求使用url請求參數,填寫測試用例的基礎數值,選擇正確的URL

配置頁面header信息

 

3) 隨后進行Header的配置,Header的名稱在選定URL的內,打開URL的鏈接(推薦使用chrome瀏覽器),敲擊F12並刷新頁面,選定Network-Name-Headers-Request Headers(Header的名稱與值均在內查看,如下圖所示)

查看頁面header信息

 

到這里,基本就完成了對https的配置過程了,是不是很簡單?下面動圖可以再回顧一下操作的流程:


gif動態圖展示操作的流程

(此圖為動圖,橫屏觀看效果更佳)

 

 

騰訊WeTest服務器性能測試運用了沉淀十多年的內部實踐經驗總結,通過基於真實業務場景和用戶行為進行壓力測試,幫助游戲開發者發現服務器端的性能瓶頸,進行針對性的性能調優,降低服務器采購和維護成本,提高用戶留存和轉化率。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM