微服務全鏈路異步化實踐

本文來自OPPO互聯網基礎技術團隊,轉載請註名做者。同時歡迎關注咱們的公衆號:OPPO_tech,與你分享OPPO前沿互聯網技術及活動。

1. 背景

隨着公司業務的發展,核心服務流量愈來愈大,使用到的資源也愈來愈多。在微服務架構體系中,大部分的業務是基於Java 語言實現的,受限於Java 的線程實現,一個Java 線程映射到一個kernel 線程,形成了高併發場景下線程資源的極大浪費,線程成爲提升系統併發和吞吐量的瓶頸。java

在微服務架構下,使用同步編程模式時不只形成了資源的極大浪費,而且在流量發生激增波動的時候,受制於系統資源而沒法快速的擴容。本文將探索服務異步化在併發、吞吐量方面對系統帶來的提高。數據庫

2. 如何快速提升服務吞吐量

首先,以微服務架構中的RPC 服務調用舉例,測試和探索在微服務架構中,異步架構如何提升服務的吞吐量和併發。ESA Stack 是OPPO 自研的基礎框架技術棧,ESA RPC 是自研的RPC 框架。本節測試服務咱們使用ESA RPC 搭建。編程

關於ESA RPC的詳情,能夠參考咱們以前發佈的文章《Dubbo協議解析及ESA RPC實踐》。服務器

2.1 服務架構

下圖所示爲測試環境架構。其中Service A 既是服務端也是客戶端,它模擬了生產環境中大部分服務的角色。咱們對Service A 分別採用同步模型和純異步模型進行壓測,其中純異步模型包含了客戶端、服務端邏輯的異步處理。Service B 模擬一個耗時爲N ms 的下游服務,爲Service A 的調用提供固定的延時響應。架構

測試服務器的配置爲8 核16G,千兆網卡。併發

2.2 同步異步模型對比

測試場景1:框架

併發壓測客戶端200~8000,服務耗時50ms,分別對同步和異步架構進行壓測,對比TPS、服務耗時、CPU 上下文切換;同步模式下,線程數和併發客戶端相同;異步模式下,使用框架默認的200 線程。測試數據以下。異步

測試場景2:socket

併發壓測客戶端8000,服務耗時50~500ms,分別對同步和異步模式進行壓測,對比TPS、服務耗時、CPU 上下文切換;同步模式下服務端8000 線程;異步模式下,使用框架默認的200 線程。async

2.3 服務擴展性對比

併發指服務瞬時同時處理的任務數(包含處於IO 等待狀態的任務)。服務端設置業務處理線程200,那麼同步模式下能提供的併發爲200;純異步模式下服務併發不受線程限制,IO密集型服務尤爲收益。在系統流量突增的情景下,異步模式具備更強的可擴展性(Scalability)。

2.4 結論

根據上面的測試數據能夠作出如下對比:

  • 同步模式,線程數與併發成正比,併發越高對線程的消耗越多
  • 異步模式,提升併發不須要線程增長
  • 同步模式,系統Context Switch 次數隨併發提升而快速增長
  • 異步模式,系統Context Switch 次數明顯小於同步模式
  • 同步模式,併發超過某個臨界點後,服務耗時快速上升,系統吞吐量急劇降低
  • 異步模式,吞吐量隨着併發增長,服務耗時上升速度明顯低於同步模式

從而得出如下結論:

  • 能夠經過異步化微服務架構,提升相同資源配置下的服務吞吐量
  • 隨着下游平均耗時的增長,異步化帶來的吞吐和耗時的提高做用減少
  • 線程資源有限(內核、內存),不能無限增長來提升併發能力,異步化能極大提升系統瞬時併發能力(Scalability)

結論分析:

  • 高併發下同步模型大量線程在內核態度/用戶態、不一樣CPU 核之間進行切換,Context Switch 增長,系統性能降低
  • 下游平均耗時增長時,系統CPU 繁忙程度下降,Context Switch 對性能系統影響降低

3. 異步模型探索

3.1 阻塞與非阻塞

在操做系統中,線程是CPU 調度的基本單位;阻塞調用是指發起調用後,線程進入阻
塞狀態(讓出CPU),直到得到結果或異常返回;非阻塞調用是指不等待結果,調用不阻塞線程直接返回。

3.2 同步與異步

同步和異步關注的是消息通訊機制;同步就是在發起調用後就獲得返回結果(未必是完整結果),也就是由調用者主動等待結果;異步則是調用在發出以後直接返回,經過信號通知、回調函數處理來通知結果。

3.3 四種IO 模型

非IO 系統調用層面, 阻塞/非阻塞和同步/異步基本是同義詞;在IO 系統調用層面,同步/異步和阻塞/非阻塞有如下組合:

  • 同步阻塞調用,線程同步等待阻塞調用結果
  • 同步非阻塞調用,線程經過輪訓獲取非阻塞調用結果
  • 異步阻塞調用,IO 事件阻塞,IO 操做不阻塞
  • 異步非阻塞調用,調用當即返回,信號/回調處理結果

咱們經過一個簡單的客戶端來介紹四種IO 模型的代碼寫法:


同步阻塞IO

非同步阻塞IO

多路複用IO

Asynchnorous IO

對四中IO 模型,有如下的對比:

  • 同步阻塞式IO 模型,編程簡單但線程阻塞,資源利用率低;
  • 同步非阻塞式IO 模型,須要輪訓CPU,浪費資源;
  • 異步非阻塞AIO 模型,不阻塞線程,使用回調方式處理數據,可是編程難度高;
  • 多路複用IO 模型,可以實現異步非阻塞IO,且編程簡單,方便實現同步和異步調用,所以成爲RPC 框架的首選。

4. 全鏈路異步編程指南

4.1 全鏈路組成及現狀

微服務架構下的全鏈路包含了網關層、WEB 服務、RPC 服務、數據層等。目前公司的網關層已經實現了純異步架構,Web 框架和RPC 框架支持純異步編程,數據存儲層目前異步方案還不成熟。

4.2 網關異步化

網關層因爲其特殊性,不須要訪問業務數據庫只作協議轉換和流量轉發,目前已經使用了純異步的架構;其IO 密集型的特色,特別適合純異步的架構,能夠極大的節省資源。

4.3 Web 服務異步化

Web 服務做爲微服務體系內的重要組成,服務節點衆多,傳統的Web 服務框架SpringMVC 不支持純異步化編程,OPPO 自研Web 框架Restlight 支持純異步編程,且性能遠超SpringMVC。下面是性能對比及Restlight 異步實踐。

Restlight 框架異步編程實踐:經過Controller 方法返回值區分同步和異步調用,且支持三種異步調用方式,CompletableFuture、ListenableFuture(Guava)、Future(Netty)。

4.4 RPC 調用異步化

RPC 調用等待下游response 返回時,線程不該處於block 狀態;做爲微服務架構中數據流量最大的一部分,RPC 調用異步化的收益巨大;目前ESA RPC 已經具有了純異步化的能力,提供RPC 調用的服務通常既是客戶端也是服務端,所以包含了客戶端異步調用能力和服務端異步處理能力;爲了兼容存量接口,ESA RPC 既支持CompletableFuture 也支持普通返回值的接口。

客戶端異步化實踐:底層使用異步非阻塞IO 收發網路數據包,使用CompletableFUture傳遞IO 事件以實現響應式編程,客戶端不被RPC 調用阻塞,可繼續調用其餘服務。

接口返回CompletableFuture 來實現異步調用:


普通接口使用ESARpcContext::asyncCall 實現異步調用:


服務端異步化實踐:經過服務端異步功能返回CompletableFuture 給框架以釋放Biz 線程,自定義線程池或者IO 線程池收到下游response 後,完成返回給框架的Future。

接口定義返回CompletableFuture 來實現異步調用:


普通接口經過ESARpcContext::startAsync 開啓服務端異步:


4.5 存儲層異步化

數據操做是每一個請求調用鏈的終點,純異步的架構必須使用異步存儲層客戶端,目前OPPO 沒有自研的存儲層異步客戶端,但業界開源方案欣欣向榮:

  • 數據庫: Vert.x JDBC 客戶端
  • Redis:Redisson、Lettuce
  • Queue:基本都支持異步調用

4.6 純異步與僞異步

異步調用目的在於防止當前業務線程被阻塞。僞異步將任務包裝爲Runnable 放入另外一個線程執行並等待,當前Biz 線程不阻塞;純異步爲響應式編程模型,經過IO 實踐驅動任務完成。他們的區別不在因而否將請求放入另外一個線程池執行,而在因而否有線程阻塞等待Response。

5. 異步化將來發展

5.1 異步化帶來的問題

相比於同步模型,異步模型存在如下問題:

  • 代碼可讀性和可維護性較差,可能出現Callback Hell
  • 框架SDK 變得複雜,使用門檻增長
  • 業務可能不清楚代碼邏輯執行線程
  • 大量的ThreadLocal 須要手動export/import

簡單來講,異步編程就是以編程的簡單性(simplity)來交換性能(performance)。

5.2 使用協程實現異步非阻塞

目前在其餘語言中,Erlang、Go、Kotlin 等都支持了協程,使用攜程的好處是在語言層面支持了異步調用,業務代碼可使用同步的寫法達到異步的效果,線程不被阻塞,避免大量的CPU 上下文切換,提高系統的性能。

目前Java 對協程的支持也在進行中, Project Loom 就是Java 的協程項目:http://openjdk.java.net/proje...

主要有如下幾個概念:

  • Fiber,輕量級線程(用戶態線程),基於Continuation 實現
  • Continuation,指令執行單元, 阻塞時調用Continuation::yield , 恢復時調用Continuation::run
  • Scheduler,用戶態Fiber 調度器(ForkJoinPool),使用有限Workers 線程執行任意數量Fibers

開發者可使用Fiber 來執行業務代碼塊,當遇到LockSupport::park、socket io 等阻塞調用時,Fiber 中的代碼單元執行會被阻塞,可是底層的線程並不會被阻塞。由此達到了開發同步模式代碼,運行時達到異步執行的目的。

將來,ESAStack服務框架會支持協程。目前 Restlight框架已經支持協程並在內部開始試用,ESARPC也有支持協程的計劃。框架提供的服務線程使用 Fiber 執行業務邏輯,業務實現中數據庫請求、下游服務調用均在 Fiber 之中執行, 其包含的 IO 等阻塞調用只掛起 Fiber 而不阻塞所在線程,從而避免了過多的上下文切換提高 了吞吐量,達到了和異步模式同樣的效果。

相關文章
相關標籤/搜索