閱讀691 返回首頁    go 阿裏雲 go 技術社區[雲棲]


老樹新花-Java異步服務開發

u_1705419240_2440941745_fm_214_gp_0

內容來源:2017年5月13日,餓了麼資深Java工程師朱傑在“Java開發者大會 | Java之美【上海站】”進行《老樹新花-Java異步服務開發》演講分享。IT大咖說作為獨家視頻合作方,經主辦方和講者審閱授權發布。
閱讀字數: 1901 用時: 13分鍾

嘉賓演講視頻地址:https://t.cn/RKtxNEE

同步模型

以前在並發量很低的情況下,是通過線程去收取數據並發送數據給客戶端。但是當並發量和客戶端連接數比較高的時候,服務器會出現明顯的瓶頸。

1

阻塞模型比較符合人的思考邏輯,但它會有線程阻塞的問題。阻塞模型會讓出CPU,不適用於高並發。

線程池或連接池隻能解決一部分問題。因為線程池和連接池的本質作用並不是能直接提高QPS,而是減少或銷毀線程的連接處以及開銷。

我們平時調用阻塞API的一個問題就在於單機的線程數是有限的。所以如果要提高服務端性能的話,首先就要去阻塞。

異步模型

異步阻塞模型處理I/O時大部分時間是非阻塞的(監聽時除外),它調用的API會立即返回,這點是需要注意的。此外,處理結果的程序並不保證調用API當前的線程,這點在處理線程安全的問題上尤其要注意。

Java中很多API都是基於操作係統底層API的模型,並沒有做更高層次的封裝。

Java把一層阻塞異部I/O做了封裝,這些就是Java或C語言異步模型的基石。

少數線程等待事件發生,再根據對應類型處理相關事件。

2

最近“協程”這個詞比較火,看上去能解決異步模型的大部分問題。它是一個輕量級線程,可以直接當作線程來用。還能阻塞I/O API,阻塞的是協程而非線程。

協程是用戶態資源,用戶態調度,消耗極低,可以啟動數十萬個協程。

它的實現和線程不是1對1 的關係,難點在於編程語言的內部實現。

Python雖然支持協程,但是由於全局解釋鎖的關係,同一時刻隻有單個CPU在運行。所以python選擇多進程+協程的做法。

Go語言完美解決了支持不阻塞線程的I/O操作,並支持多線程。

要能像同步I/O一樣編寫代碼,不會創造過多數量的線程。盡量讓CPU處於忙碌狀態而非等待,並尋找滿足以上條件的Java庫。但是Java由於本身語言的問題,即使是Java協程三方庫也隻能部分支持協程。

退而求其次,我們隻能使用Java異步工具庫。如果要提高並發量,可以使用異步JDBC和異步HTTP CLIENT,這個庫基於NETTY。

做到服務異步化,要查看接口是否可支持異步。還可以使用Java的異步工具庫,比如Java的異步數據訪問方式和異步HTTP CLIENT。如果使用的是三方框架,可以修改調用方式,有的框架支持異步回調和事件監聽。最重要的是要注意線程安全問題。

異步化的優勢就是極大提高了I/O密集型業務的性能,保守估計有10~100倍,也就解決了線程數創建過多的問題。

而它的缺點是增加了編程難度,包括狀態保存、回調處理以及線程安全等。也存在壓垮下遊服務的問題:)

老樹新花-基於Netty的Java模型

Netty是基於原生的異步模型,封裝並優化。它修複JDK中的一些BUG,提供了多種輔助類方便開發。編程模型高效簡單,開發者隻需關心具體實現邏輯即可,基本不用花精力做Java網絡層麵的優化。此外,Netty成熟穩定,業界使用多,我們能夠相信,使用它不會遇到難以解決的大問題。

Netty基於事件連接,如果有數據傳入以及連接上有異常事件或自定義事件,隻需複寫它的回調函數就能做相應的處理。

Netty所有I/O API全都是消除阻塞異步化。線程模式也非常好,它單個連接上的所有I/O事件都由同一個線程執行,避免了線程安全問題。

3

Netty的單個鏈接綁定一個線程,EVENTLOOP即一個線程,EVENTLOOPGROUP是一個線程組。

Netty任何的I/O API都是產生一個任務,放入該連接對應的線程裏執行,做到局部串行化。

Netty一切操作都是以事件驅動來執行,所有I/O API都是用異步+回調監聽的方式來處理消息。單一的連接處理都在一個線程裏,來避免線程安全問題。

案例-餓了麼數據庫中間件

我們是一個實現了MYSQL協議的中間代理服務。上遊至少要支持上萬客戶端的連接,下遊要支持上千數據庫連接。

為了快速實現功能,我們最早是找了一個基於GITHUB阻塞I/O開源庫,首要任務是把同步改為了異步。

線程模型的上下遊各有Netty的一個線程組,中間件內部還有一個處理業務的線程組。

4

但有一個最本質的問題在於這三個線程組之間的線程安全得不到保證。

因為這個中間件前後端都是異步的,所以按正常流程是由協議來保證順序執行,而異常中斷是並發執行的。

參考Netty,我們把每一個連接綁定到一個單線程池,保證Task串行執行。

5

前後端異步的好處在於模型簡單,便於後續修改。

我個人認為,在程序裏過多的使用WAIT/NOTIFY/LOCK不一定代表良好的多線程編程能力,卻可能代表這是不夠優雅的設計。

6

除了前後端異步和信號量控製異步,在中間件中我們還用到了日誌異步、心跳異步、JOB異步和配置變更異步。

在餓了麼數據庫中間件開發過程中,異步化所有API以去除阻塞,局部串行化解決線程安全問題,模型簡單,易於修改和理解。

今天的分享就到這裏,謝謝大家!
_

最後更新:2017-07-25 18:02:51

  上一篇:go  有了人臉識別和虹膜掃描,指紋傳感器是不是就OUT了?究竟哪個更安全?
  下一篇:go  不懂技術如何判斷一個頁麵的開發複雜度