技術文章第一時間送達!
為什麼慢?
多線程加速
異步調用
RPC 異步調用
總結
這周六參加了一個美團點評的技術沙龍,其中一位老師在介紹他們自研的 RPC 架構時提到一點:RPC 請求分為 sync,future,callback,oneway,并且需要遵循一個原則:能夠異步的地方就不要使用同步。正好最近在優化一個業務場景:在一次頁面展示中,需要調用 5 個 RPC 接口,導緻頁面響應很慢。正好啟發了我。
大多數開源的 RPC 架構實作遠端調用的方式都是同步的,假設 [ 接口1,…,接口5]的每一次調用耗時為 200ms (其中接口2依賴接口1,接口5依賴接口3,接口4),那麼總耗時為 1s,這整個是一個串行的過程。
第一個想到的解決方案便是多線程,那麼[1=>2]編為一組,[[3,4]=>5]編為一組,兩組并發執行,[1=>2]串行執行耗時400ms,[3,4]并發執行耗時200ms,[[3,4]=>5]總耗時400ms ,最終[[1=>2],[[3,4]=>5]]總耗時400ms(理論耗時)。相比較于原來的1s,的确快了不少,但實際編寫接口花了不少功夫,建立線程池,管理資源,分析依賴關系…總之代碼不是很優雅。
RPC中,多線程着重考慮的點是在用戶端優化代碼,這給用戶端帶來了一定的複雜性,并且編寫并發代碼對程式員的要求更高,且不利于調試。
如果有一種既能保證速度,又能像同步 RPC 調用那樣友善,豈不美哉?于是引出了 RPC 中的異步調用。
在 RPC 異步調用之前,先回顧一下 <code>java.util.concurrent</code> 中的基礎知識:<code>Callable</code> 和 <code>Future</code>
最終控制台列印:
result = 15, total cost 413 ms
五個接口,如果同步調用,便是串行的效果,最終耗時必定在 1s 之上,而異步調用的優勢便是,submit任務之後立刻傳回,隻有在調用 <code>future.get()</code> 方法時才會阻塞,而這期間多個異步方法便可以并發的執行。
我們的項目使用了 Motan 作為 RPC 架構,檢視其 changeLog ,0.3.0 (2017-03-09) 該版本已經支援了 async 特性。可以讓開發者很友善地實作 RPC 異步調用。
1 為接口增加 @MotanAsync 注解
2 添加 Maven 插件
安裝插件後,可以借助它生成一個和 DemoApi 關聯的異步接口 DemoApiAsync 。
3 注入接口即可調用
<1> DemoApiAsync 如何生成的已經介紹過,它和 DemoApi 并沒有功能性的差別,僅僅是同步異步調用的差距,而 DemoApiAsync 實作的的複雜性完全由 RPC 架構幫助我們完成,開發者無需編寫 Callable 接口。
<2> ResponseFuture 是 RPC 中 Future 的抽象,其本身也是 juc 中 Future 的子類,當 responseFuture.getValue() 調用時會阻塞。
在異步調用中,如果發起一次異步調用後,立刻使用 future.get() ,則大緻和同步調用等同。其真正的優勢是在submit 和 future.get() 之間可以混雜一些非依賴性的耗時操作,而不是同步等待,進而充分利用時間片。
另外需要注意,如果異步調用涉及到資料的修改,則多個異步操作直接不能保證 happens-before 原則,這屬于并發控制的範疇了,謹慎使用。查詢操作則大多沒有這樣的限制。
在能使用并發的地方使用并發,不能使用的地方才選擇同步,這需要我們思考更多細節,但可以最大限度的提升系統的性能。