1. 背景
隨著公司業務的發展,核心服務流量越來越大,使用到的資源也越來越多。在微服務架構體系中,大部分的業務是基于Java 語言實現的,受限于Java 的線程實現,一個Java 線程映射到一個kernel 線程,造成了高并發場景下線程資源的極大浪費,線程成為提高系統并發和吞吐量的瓶頸。
在微服務架構下,使用同步編程模式時不僅造成了資源的極大浪費,并且在流量發生激增波動的時候,受制于系統資源而無法快速的擴容。本文將探索服務異步化在并發、吞吐量方面對系統帶來的提升。
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:
并發壓測客戶端8000,服務耗時50~500ms,分別對同步和異步模式進行壓測,對比TPS、服務耗時、CPU 上下文切換;同步模式下服務端8000 線程;異步模式下,使用框架默認的200 線程。
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/projects/loom/。
主要有以下幾個概念:
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 而不阻塞所在線程,從而避免了過多的上下文切換提升 了吞吐量,達到了和異步模式一樣的效果。
☆?END?☆
招聘信息OPPO互聯網基礎技術團隊招聘一大波崗位,涵蓋C++、Go、OpenJDK、Java、DevOps、Android、ElasticSearch等多個方向,請點擊這里查看詳細信息及JD。
你可能還喜歡OPPO自研ESA DataFlow架構與實踐
Dubbo協議解析與ESA RPC實踐
自研代碼審查系統火眼Code Review實踐
OPPO異地多活實踐——緩存篇
更多技術干貨
掃碼關注
OPPO互聯網技術

