0 異步的優勢
太多的線程會造成頻繁的cpu上下文切換,你可以想象一下,假設你的小公司隻有8台電腦,你雇8個程式員一直不停的工作顯然是效率最高的。考慮到程式員要休息不可能連軸轉,雇傭24個人,每天三班倒,效率也還行。
但是,你要雇傭10000個人,他們還是隻能用這8台電腦,大部分時間不都浪費在換人、交接工作上啦。
異步程式設計是通過分工的方式,是為了減少了cpu因線程等待的可能,讓CPU一直處于工作狀态。換句話說,如果我們能想辦法減少CPU空閑時間,我們的計算機就可以支援更多的線程。
其實線程是一個抽象概念,我們從實體層面了解,就是機關時間内把每毫核配置設定處理不同的任務,進而提高機關時間内CPU的使用率。
線程就是為了能自動配置設定CPU時間片而生的。
異步實作裡面還是要用線程池限制一下線程數吧,否則沒有達到減少線程的效果。
異步模式設計的程式可以顯著減少線程等待,進而在高吞吐量的場景中,極大提升系統的整體性能,顯著降低延遲時間。
是以,像MQ這種需要超高吞吐量和超低延遲時間的中間件系統,在其核心流程中,會大量采用異步設計。
1 案例引入
一個轉賬的微服務Transfer( accountFrom, accountTo, amount),該服務有三參數
- 轉出賬戶
- 轉入賬戶
- 轉賬金額
要從賬戶A中轉賬100到賬戶B:
- 先從A的賬戶中減去100元
- 再給B的賬戶加上100元,轉賬完成。
對應時序圖:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SMzIDM4E2NiVjZ0UTNiBTYjJ2M0YGO2UjY4M2YlZjN58CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
調用另外一個微服務Add(account, amount),給賬戶account增加金額amount,當amount為負值時,就是扣減相應金額。
為簡化,省略了錯誤處理和事務相關代碼
2 同步的性能瓶頸
同步實作,對應的僞代碼:
Transfer(accountFrom, accountTo, amount) {
// 先從accountFrom的賬戶中減去相應的錢數
Add(accountFrom, -1 * amount)
// 再把減去的錢數加到accountTo的賬戶中
Add(accountTo, amount)
return OK
}
先從accountFrom的賬戶中減去相應的錢數,再把減去的錢數加到accountTo的賬戶中,同步實作簡單直接。
性能如何?
假設微服務Add的平均響應時延是50ms,微服務Transfer的平均響應時延大約等于執行2次Add的時延,即100ms。随調用Transfer服務的請求越來越多,會出現什麼情況呢?
該實作,每處理一個請求耗時100ms,這100ms過程要獨占個線程。
可得:每個線程每秒最多可處理10個請求。假設伺服器同時打開的線程數量上限10,000,可以計算出這台伺服器每秒鐘可以處理的請求上限是: 10,000 (個線程)* 10(次請求每秒) = 100,000 次每秒。
若請求速度超過這個值,那麼請求就不能被馬上處理,隻能阻塞或者排隊,這時候Transfer服務的響應時延由100ms延長到了:排隊的等待時延 + 處理時延(100ms)。即大量請求時,我們的微服務的平均響應時延變長了。
這是不是已達伺服器極限?遠沒有!
若監測伺服器名額,會發現無論是CPU、記憶體,還是網卡流量或者是磁盤的IO都空閑的很,那我們Transfer服務中的那10,000個線程在作甚?
沒錯!絕大部分線程都在等待Add服務傳回結果。
即采用同步,整個伺服器的所有線程大部分時間都沒在工作,而在等待!
若能減少或避免這種無意義等待,即可大幅提升服務吞吐能力,提升性能。
異步實作方案
異步實作同樣的業務。
TransferAsync(accountFrom, accountTo, amount, OnComplete()) {
// 異步從accountFrom的賬戶中減去相應的錢數,然後調用OnDebit方法。
AddAsync(accountFrom, -1 * amount, OnDebit(accountTo, amount, OnAllDone(OnComplete())))
}
// 扣減賬戶accountFrom完成後調用
OnDebit(accountTo, amount, OnAllDone(OnComplete())) {
// 再異步把減去的錢數加到accountTo的賬戶中,然後執行OnAllDone方法
AddAsync(accountTo, amount, OnAllDone(OnComplete()))
}
// 轉入賬戶accountTo完成後調用
OnAllDone(OnComplete()) {
OnComplete()
}
TransferAsync服務比Transfer多個參數,且該參數傳入的是一個回調方法OnComplete()(雖然Java并不支援将方法作為方法參數傳遞,但像JavaScript等很多語言都具有這樣特性,Java可傳個回調類的執行個體來變相實作)。
-
TransferAsync()方法語義
請幫我執行轉賬操作,當轉賬完成後,請調用OnComplete()方法。
調用TransferAsync的線程不必等待轉賬完成即可立即傳回,待轉賬結束,TransferService自然會調用OnComplete()方法來執行轉賬後續工作。
異步實作相對同步稍複雜。先定義倆回調方法:
- OnDebit():扣減賬戶accountFrom完成後調用的回調方法
- OnAllDone():轉入賬戶accountTo完成後調用的回調方法。
異步實作語義相當于:
- 異步從accountFrom的賬戶中減去相應錢數,然後調用OnDebit
- 在OnDebit方法中,異步把減去的錢數加到accountTo的賬戶中,然後執行OnAllDone
- 在OnAllDone中調用OnComplete
時序圖
異步後,整個流程時序和同步完全一樣,隻是線程模型由同步調用改為異步和回調。