此次文章介紹如何用Spring WebFlux來構建響應式REST API,在了解這個之前,必須先對系統的開發、傳統REST的常見問題和API的普遍需求有一定的了解基礎,我在網上找到了關于介紹傳統應用和現代應用特點的分類圖:
從圖中可以看出,現代應用更偏向于分布式應用、云原生、高可用性和可擴展性,所以這就在系統有效資源利用上需要特別關注。
傳統REST API請求處理的工作原理圖:
從上面的工作原理可以分析得到:
- 無背壓支持。傳統的REST API是很難支持從客戶端或服務器位置施加背壓的,所以通常會造成負載過大的問題影響運行。這就意味著它不能支持短時間的大量請求,容易造成服務器崩潰,進而無法訪問。
- 阻塞和同步。一般情況下,請求的線路釋放會在I/O阻塞結束后,最后將響應返回給調用方。
- 單個請求的線程數。web容器會用到基于請求的線程模型,這個模型是存在影響API性能的,因為該模型會限制發請求的數量,讓其成為一種“排隊”的效果。
- 限制高并發用戶的處理。正是因為上段提到的模型限制, 所以對于高并發量時無法處理。
- 對系統資源的浪費。I/O會造成線路阻塞,這就會讓web容器沒辦法再處理更多的需求,對于系統資源的處理處于浪費階段。
為什么響應式的優勢可以解決上述問題:
- 異步無阻塞。響應式編程極大的為寫異步和無阻塞應用程序提升了靈活性。
- 事件、消息驅動。系統可以對應活動產生相呼應的事件或者消息。打個比方,來自數據庫的就會被看作事件流。
- 支持背壓。通過背壓,我們可以避免拒絕服務的出現,因為它可以很方便的處理掉一個系統到另一個系統的壓力。
- 應用響應的時間可以預測。由于線程屬于異步非阻塞,所以對于負載下應用的響應時間可以做到比較詳細的預測。
- 系統資源利用率高。它可以支持更多的用戶請求,因為其線路異步且非阻塞,因此各種線程不會被I/O所占用,其系統資源利用率就更高。
- 基于負載的擴容方式。
- 脫離基于請求的線程。借助于異步線程,基于請求的線程模型限制就可以得到脫離,因為模型會和服務器同時創建事件,并通過請求線程,處理其他請求。
了解了具體的優勢,那么到底響應式編程流程是怎么樣的?我在網上找到的圖片解釋的非常詳細,如果應用數據調用了數據源獲取數據的操作,就回立刻有一個線程返回,這是讓自來該數據源的數據作為數據流出現。當數據流完成的時候,就是onComplete事件觸發的時候。
當過程中出現了異常情況,則會出發onError事見。
有一些特殊的例子,比如刪除一個來自數據庫中的內容時,oncomplete/onError事件就會立即被觸發,因為它本身沒有數據可以返回來,所以調用onNext事件并不現實。
對上面部分了解之后,我們可以接著對背壓進行了解并考慮如何將其應用到響應流。舉個例子,假如說我們的客戶端在向另一個服務請求數據,假設這個服務發布的速率可以達到1000TPS,但是客戶端處理該應用的事件卻很低。正是為了應對這種情況的產生,客戶端需要利用緩沖來處理,也正因為如此,在緩沖過程中對于內存的占用會更大。這就造成了級聯效應,造成其他應用程序出問題,為了避免此類情況,可以調用服務在末尾緩沖,然后讓用客戶端應用去推送事件,這就是背壓。
之前看過的一篇關于響應流的文章寫的很好,我此次簡單介紹一下。先從發布者和訂閱者開始說明:
發布者:擁有無限數量順序元素的提供者稱之為發布者,它可以根據訂閱者要求發布。
訂閱者:無限數量順序元素相對應的使用者。
訂閱:訂閱者向發布者訂閱的某個一對一的周期。
處理器:訂閱者和發布者之間根據約定進行的處理階段。
明確了上述的定義后,在說回響應規范流,比如常見的Project Reactor是其中的一種,它的優點在于可以實現無阻塞、高效的請求管理,也可以提供響應式可組合的API。這可組合的API廣泛實現了響應式的拓展,而且提供了非阻塞的背壓式網絡引擎、TCP和UDP給HTTP,這對架構微服務是非常有效果的。
由于Reactor實施時大多數時候都需要涉及Spring 5.x,根據這個可以嘗試通過Spring servlet棧的命令式編程來構架REST API。
在這里舉例一個響應式REST API應用,在應用中使用到了帶有WebFlux的Spring Boot、具有響應式支持的Spring數據和Cassandra數據庫。
在構建響應式API的時候,我們可以不使用RestController,改為使用功能性樣式編程模型來構建API,當然,你需要擁有router和handler組件。到這個地步,對公布響應式REST API已經了解了不少。