Q:為何要采用異步編程
A:異步編程首先不會節約線程,因為異步操作都會重新開一個線程。異步編程是提高了CPU的使用率,采用同步編程的方式,整個服務器的所有線程大部分都沒有在工作,而是在等待。因為線程同步操作
要等整個事件處理完成才能提交,所以CPU的利用率很低;當采用異步編程,線程不需要等待,減少時間分片的占用,提高系統的吞吐量。在超高請求數量場景下,異步的實現不再需要線程等待執行結果,只需要個位數量的線程,即可實現同步場景大量線程一樣的吞吐量。
舉例:
實現一個轉賬的微服務 Transfer( accountFrom, accountTo, amount),這個服務有三個參數:
分別是轉出賬戶、轉入賬戶和轉賬金額。
實現過程也比較簡單,我們要從賬戶 A 中轉賬 100 元到賬戶 B 中:先從 A 的賬戶中減去 100 元;再給 B 的賬戶加上 100 元,轉賬完成。
采用同步操作:
Transfer(accountFrom, accountTo, amount) {
// 先從accountFrom的賬戶中減去相應的錢數
Add(accountFrom, -1 * amount)
// 再把減去的錢數加到accountTo的賬戶中
Add(accountTo, amount)
return OK
}
同步操作我們很容易想到的,那同步操作的效率怎么樣呢?
假設內部add方法執行效率在50ms,那一個線程執行燒水泡茶的時延在100ms,按照這種實現,每個線程每分鍾最多處理10個請求,假設我們使用的服務器同時打開的線程數量上限是 10,000,
可以計算出這台服務器每秒鍾可以處理的請求上限是: 10,000 (個線程)* 10(次請求每秒) = 100,000 次每秒。如果請求速度超過這個值,那么請求就不能被馬上處理,只能阻塞或者排隊,
這時候 Transfer 服務的響應時延由 100ms 延長到了:排隊的等待時延 + 處理時延 (100ms)。也就是說,在大量請求的情況下,我們的微服務的平均響應時延變長了。 這已經到CPU的極限了嗎?
其實不然,如果我們監測一下服務器的各項指標,會發現無論是 CPU、內存,還是網卡流量或者是磁盤的 IO 都空閑的很,多大部分線程都在等待每一個服務返回結果。
采用異步操作:
是在線程模型上由同步順序調用改為了異步調用和回調的機制。
接下來,我們來看下,如何用 CompletableFuture 實現的轉賬服務。
首先,我們用 CompletableFuture 定義 2 個微服務的接口:
/** * 賬戶服務 */ public interface AccountService { /** * 變更賬戶金額 * @param account 賬戶ID * @param amount 增加的金額,負值為減少 */ CompletableFuture<Void> add(int account, int amount); } /** * 轉賬服務 */ public interface TransferService { /** * 異步轉賬服務 * @param fromAccount 轉出賬戶 * @param toAccount 轉入賬戶 * @param amount 轉賬金額,單位分 */ 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) { // 異步調用add方法從fromAccount扣減相應金額 return accountService.add(fromAccount, -1 * amount) // 然后調用add方法給toAccount增加相應金額 .thenCompose(v -> accountService.add(toAccount, amount)); } }
我們先調用一次賬戶服務 accountService.add() 方法從 fromAccount 扣減響應的金額,因為 add() 方法返回的就是一個 CompletableFeture 對象,可以用 CompletableFeture 的 thenCompose() 方法將下一次調用 accountService.add() 串聯起來,實現異步依次調用兩次賬戶服務完整轉賬。