開發與維運

Dragonwell特性: Wisp

1、協程與異步編程 

1、多線程和事件模型 

Web Server領域,早像Apache Server大家都是使用多線程模型處理併發下圖左邊這張圖是通過多個進程去處理多個用戶不同的請求,可能在一個單核系統上也可以去創建多個進程來處理這些請求,但這實際上操作系統給大家一個假象操作系統通過分時互動機制去不停的切換線程,表現出一種正在同時執行的假象。 

image.png 

實際上這個切換是非常消耗資源的,然後我們看右邊這張圖NGINX,他率先使用了事件模型,讓大家科學認知到在單個線程裡通過業務代碼去切換不同的上下文,這樣可以大大減少操作系統裡面限制切換開銷,很好的提高性能。 

 

2、上下文切換 

上下文切換會吃掉寶貴的CPU資源,大家很多情況下對上下文誤區,進出內核和調度之間其實很大差異的。假如像剛才這種場景,我們看到多個線程來回調用那一個線程當它資源耗盡或者比較阻塞的時候,下個線程選誰?其實操作系統需要進行調度真正的損耗遠大於想象 

image.png 

我們可以看到進出內核是上圖左邊灰色這一列,它的耗時是很小的,可能在幾十到一百納秒級別。然後假如這一次系統調用它觸發了切換,比如讀一個程序裡面有數據,信令要掛起會觸發上下文切換,如果希望有調度,開銷就會很大,會達到40倍左右。 

 

3、使用異步編程 

所以如果在編程中引發調度切換開銷是很大的,我們應該儘量避免。怎麼避免呢?答案就是異步編程,在node.js裡面,我們可以使用大量callback區域處理業務邏輯。當使用callback以後,代碼可能會變成這樣一種三角形,因為每一個組織方式,它後面返回值都要帶callback調用,都會縮進去一層。這樣業務邏輯非常難以維護 

其次是即便我使用了異步編程,但可能還是不小心在現實裡面使用了一段阻塞代碼,下圖NGINX官網所提供的圖片,雖然我自己去切換不同的請求處理,但是中間可能還是不小心調用了操作系統的一個阻塞方法。 

image.png 

為了解決這個問題,NGINX雖然是一個號稱純異步事件驅動的模型,但是它最近也引入了線程處理這種可能阻塞現實的情況 

 

4、引入協程 

其實最早在操作系統裡沒有協程概念,大家都是通過協程邏輯上抽象來幫助我們寫併發代碼。 

image.png   image.png 

比如說這裡有兩code,一段是解壓code,一段 parsercode。大家要從解壓數據結構裡面去解析數據,這裡對數據進行簡單的encode,如char普通字符,會直接返回。若是特殊字符,可能就進行一個長度encode用協程來組織邏輯emit() 和 parser::getchar()會切換到另一個協程如果沒有協程需兩個線程結合pipe來組織但如果協程,我們可以在 frame裡面直接控制邏輯清晰且性能高 

我們看怎麼實現協程協程執行上下文其實包括這幾個部分,當前的站、局部變量代碼位置,這些其實都可以通過數據表示 

 image.png 

與OS內的線程切換方式一致 

(1) 保存pc 

(2) 保存sp 

(3) 保存callee-save寄存器 

保存完這些後,將來想回去,只要通過反向計算器pop出來,就會回到之前上下協程場景下,emit和 getchar都是通過這種方式去實現的 

 

5、現代編程語言中的協程 

image.png 

左邊是VERT.X Java裡面最近比較流行的框架,想要製作的就是Java裡的node.js的生態,我們可以看到官方所提供的連接數據庫例子。 

Client.getConnection,來獲取數據庫連接,但它不是說立馬返回一個連接給到我們,而是提供callback,然後這個result裡面表示執行是否成功,如果成功的話,我們可以通過result去拿到 connection這就是通過義務編的方式,去讓我們在線程裡面處理大的邏輯NGINX就是這樣的一種方式。這樣代碼其實看起來是非常難以維護的,比如在裡面需要通過result set去把數據放到緩存裡面,又是一個遠程調用需要阻塞,可能又是一種callback,這個嵌套會非常深,非常難處理,由於我們都是callback,所以這個就沒法被維持,假如在這個地方異常非常難以處理 

現代編程語言是怎麼解決這個問題,我們給的答案是ES7C# 他們都提供幫助解決這類問題。我們以一段Kotlin代碼為例怎麼幫助代碼改寫成非常直觀的代碼,Kotlin裡面通過suspend關鍵字來表示,函數是可以被掛起的,然後它也可以在 client上新加的方法,新的方法叫Agetconnection裡面調用Kotlin提供的非常 medical的方法,他會獲取一個當前執行上下文的connection,讓我們getConnection直接調用。getConnectioncallback是恢復當前執行,並且把拿到connection作為返回值。這樣實際上不用一直佔 CPU資源,實際上調度器會繼續去調度其他執行,一旦進行這類封裝以後,我們看到代碼可以被簡化為下面這種形式。 

image.png 

Conn=clinet. AGetConnection(); 

然後 rs= Conn .aQurerythat(“SELECT * FROM ...”) 

這段代碼相比左邊這段代碼那就是大大簡化了,但我們要做對這種回調形式進行封裝。 

6、Dragonwell: Wisp 原理 

image.png 

既然要對這麼多回調形式進行封裝,工作量是非常大的,能不能在更底層去解決,為什麼就提供了這一層幫助?因為jdk提供所有的阻塞方式都是在jdk裡面提供的。比如說Java.lang.Threadj.u.cjava.iosynchronized這些都是有可能阻塞API。在這些API上我們都做了封裝, Wisp把這些髒活苦活全部給做掉了. Wisp對現成模型進行一個映射。我們知道Java裡面的Java thread和操作系統pthread1:1的映射關係,大量線程使用的話就會導致前面提到的上下切換問題。但是在Wisp下我們每一個線都被映射到一個Wispwisp執行過程中可能阻塞CPU,然後這時候就可以讓pthread調動其他Wisp調度效率非常高,可以免費提高應用的性能。 

 

二、使用Wisp提升微服務性能 

Dragonwell: Wisp demo 

下面Dragonwell下用 使用Wisp提高性能的例子 

image.png image.png 

左邊這張圖是不開Wisp使用wrk壓測工具去壓這臺機器,192.168.1.1018080端口,平均的延遲是522秒,QBS是不到5萬,在同個應用完全不改代碼情況下,我們調整一下界面參數把Wisp打開,然後線程就被完全意識到協程latency降低到270秒, QBS變成了6萬多,大概有20%多的性能提升,這不需要修改任何應用代碼是一個免費的性能午餐,所以推薦大家可以通過Wisp提高我們微服務性能表現。 

Leave a Reply

Your email address will not be published. Required fields are marked *