一、前言
異步是一種程序設計的思想,使用異步模式設計的程序可以顯著減少線程等待,從而在高吞吐量的場景中,極大提升系統的整體性能,顯著降低時延。因此,像消息隊列這種需要超高吞吐量和超低時延的中間件系統,其核心流程中,一定會大量采用異步的設計思想。
二、異步設計如何提升系統性能?
假設我們要實現一個轉賬的微服務 Transfer( accountFrom, accountTo, amount),這個服務有三個參數:分別是轉出賬戶、轉入賬戶和轉賬金額。

1. 同步實現的性能瓶頸
首先從 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 服務返回結果。
所以,采用同步實現的方式,整個服務器的所有線程大部分時間都沒有在工作,而是都在等待。如果我們能減少或者避免這種無意義的等待,就可以大幅提升服務的吞吐能力,從而提升服務的總體性能。
2. 采用異步實現解決等待問題
異步的實現過程相對於同步來說,稍微有些復雜。我們先定義 2 個回調方法:
- OnDebit():扣減賬戶 accountFrom 完成后調用的回調方法;
- OnAllDone():轉入賬戶 accountTo 完成后調用的回調方法。
整個異步實現的語義相當於:
1、異步從 accountFrom 的賬戶中減去相應的錢數,然后調用 OnDebit 方法;
2、在 OnDebit 方法中,異步把減去的錢數加到 accountTo 的賬戶中,然后執行 OnAllDone 方法;
3、在 OnAllDone 方法中,調用 OnComplete 方法。

異步化實現后,整個流程的時序和同步實現是完全一樣的,區別只是在線程模型上由同步順序調用改為了異步調用和回調的機制。
接下來我們分析一下異步實現的性能,由於流程的時序和同步實現是一樣,在低請求數量的場景下,平均響應時延一樣是 100ms。在超高請求數量場景下,異步的實現不再需要線程等待執行結果,只需要個位數量的線程,即可實現同步場景大量線程一樣的吞吐量。由於沒有了線程的數量的限制,總體吞吐量上限會大大超過同步實現,並且在服務器 CPU、網絡帶寬資源達到極限之前,響應時延不會隨着請求數量增加而顯著升高,幾乎可以一直保持約 100ms 的平均響應時延。
三、總結
異步思想就是:當我們要執行一項比較耗時的操作時,不去等待操作結束,而是給這個操作一個命令:“當操作完成后,接下來去執行什么。”
使用異步編程模型,雖然並不能加快程序本身的速度,但可以減少或者避免線程等待,只用很少的線程就可以達到超高的吞吐能力。
同時我們也需要注意到異步模型的問題:相比於同步實現,異步實現的復雜度要大很多,代碼的可讀性和可維護性都會顯著的下降。雖然使用一些異步編程框架會在一定程度上簡化異步開發,但是並不能解決異步模型高復雜度的問題。
異步性能雖好,但一定不要濫用,只有類似在像消息隊列這種業務邏輯簡單並且需要超高吞吐量的場景下,或者必須長時間等待資源的地方,才考慮使用異步模型。如果系統的業務邏輯比較復雜,在性能足夠滿足業務需求的情況下,采用符合人類自然的思路且易於開發和維護的同步模型是更加明智的選擇。
