異步是一種程式設計的思想,使用異步模式設計的程式可以顯著減少執行緒等待,從而在高吞吐量的場景中,極大提升系統的整體性能,降低請求時延,
同步設計流程
我們假設要做一個轉賬的業務,即從賬戶A中轉賬100元到賬戶B中,它包含2步:
- 從A的賬戶中減少100元
- 給B的賬戶增加100元
我們可以設計2個Service:
- Transfer服務,負責轉賬,介面是Transfer(A, B, 100)
- Account服務,負責賬戶管理,介面是Add(A, -100)和Add(B, 100)
轉賬業務的偽代碼如下:
Transfer(accountFrom, accountTo, amount){
Add(accountFrom, -1*amount)
Add(accountTo, 1*amount)
return OK
}
假設Add操作的平均回應時延是50ms,那么我們的Transfer操作平均實驗大約是100ms,在實際運行程序中,每處理一次Transfer操作,需要耗時100ms,并且在100ms內需要獨占一個執行緒,即每個執行緒每秒最多可以處理10次Transfer操作,
假設我們在一個服務器上同時打開的執行緒數量上限是10000,那么這個服務器每秒可以處理的請求上限是:10000*10 = 100000次,
當客戶請求數量超過上限時,請求就需要排隊,那么Transfer操作的回應時延變成:排隊等待時延+處理時延(100ms),即在大量請求的場景中,我們的服務回應時延增加了,
但是,在這種情況下,服務器的資源并沒有被消耗很多,例如CPU、記憶體、網卡等資源都很空閑,我們的100000個執行緒大部分時間在等待Add操作回傳結果,
上面就是同步設計方式,在這種情況下,整個服務器的所有執行緒大部分時間都沒有在作業,而是在等待,
異步設計流程
對于同樣的業務場景,我們來看一下異步方式下的偽代碼:
TransferAsync(accountFrom, accountTo, amount, OnComplete){
AddAsync(accountFrom, -1*amount, OnDebit(accountTo, 1*amount, OnAllDone(OnComplete)))
}
OnDebit(amountTo, amount, OnAllDone(OnComplete)){
AddAsync(accountTo, amount, OnAllDone(OnComplete))
}
OnAllDone(OnComplete){
OnComplete()
}
這里TransferAsync和之前的Transfer相比,增加了一個引數,這個引數是一個回呼方法OnComplete(),
上面方法的語意:請幫我執行轉賬操作,當轉賬完成后,請呼叫OnComplete()方法,呼叫TransferAsync的執行緒不必等待轉賬完成就可以立即回傳,當轉賬結束后,OnComplete()方法會被呼叫來執行后續的作業,
上面代碼中,我們定義了2個回呼方法:
- OnDebit():扣減賬戶accountFrom完成后呼叫的回呼方法
- OnAllDone():轉入賬戶accountTop完成后呼叫的回呼方法
整個異步操作的語意如下:
- 異步從accountFrom的賬戶中減去相應的錢數,然后呼叫OnDebit方法
- 在OnDebit方法中,異步把減去的錢數加到accountTop賬戶中,然后執行OnAllDone方法
- 在OnAllDone方法中,呼叫OnComplete方法
采用異步方式后,整個流程的時序和同步實作是完全一樣的,區別在于執行緒模型由同步順序呼叫改為異步呼叫和回呼的機制,
由于流程的時序和同步方式相同,在低請求量的場景下,平均回應時延還是100ms,在超高請求量的場景下,異步機制不需要執行緒等待執行結果,只需要個位數的執行緒,即可實作同步場景大量執行緒一樣的吞吐量,
在異步模式下,由于沒有了執行緒數量上的限制,總體吞吐量上限會大大超過同步實作,并且在服務器CPU、網路帶寬資源達到極限之前,回應時延不會隨著請求數量增加而顯著升高,幾乎可以一直保持100ms左右的回應時延,
異步框架:CompletableFuture
Java語言中常用的異步框架包括CompletableFuture和RxJava,我們主要來看CompletableFuture,
它是Java 8中新增的一個強大的異步編程的類,包括了我們在開發異步程式程序中需要的大部分功能,
針對上述的轉賬場景,我們來看一下如何使用CompletableFuture實作,
首先定義2個介面:
public interface AccountService {
CompletableFuture<Void> add(int account, int amount);
}
public interface TransferService {
CompletableFuture<Void> transfer(int fromAccount, int toAccount, int amount);
}
然后實作轉賬功能:
public class TransferServiceImpl implements TransferService {
@Inject
private AccountService accountService;
@Override
public CompletableFuture<Void> transfer(int fromAccount, int toAccount, int amount) {
return accountService.add(fromAccount, -1 * amount)
.thenCompose(v -> accountService.add(toAccount, amount));
}
}
客戶端呼叫TransferService時,可以使用同步方式,也可以使用異步方式:
public class Client {
@Inject
private TransferService transferService;
private final static int A = 1000;
private final static int B = 1001;
public void syncInvoke() throws ExecutionException, InterruptedException {
// 同步呼叫
transferService.transfer(A, B, 100).get();
System.out.println("轉賬完成!");
}
public void asyncInvoke() {
// 異步呼叫
transferService.transfer(A, B, 100)
.thenRun(() -> System.out.println("轉賬完成!"));
}
}
異步設計的思想:當我們要執行一項比較耗時的操作時,不去等待操作結束,而是給這個操作一個命令:“當操作完成后,接下來去執行什么”,
使用異步編程帶來的好處是可以減少或者避免執行緒等待,只用很少的執行緒就可以達到超高的吞吐能力,
使用異步編程帶來的問題是復雜度增加,代碼可讀性和可維護性會下降,
作者:李潘 出處:http://wing011203.cnblogs.com/ 本文著作權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利,轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/546578.html
標籤:架構設計
上一篇:企業應該擁有自己的代碼平臺-程式員應該寫有能力的代碼
下一篇:怎么處理訊息積壓問題?
