使用Netty,我們到底在開發些什麼?

小姐姐味道發表於2019-02-26

在java界,netty無疑是開發網路應用的拿手菜。你不需要太多關注複雜的nio模型和底層網路的細節,使用其豐富的介面,可以很容易的實現複雜的通訊功能。

和golang的網路模組相比,netty還是太過臃腫。不過java類框架就是這樣,屬於那種離了IDE就無法存活的編碼語言。

最新的netty版本將模組分的非常細,如果不清楚每個模組都有什麼內容,直接使用netty-all即可。

單純從使用方面來說,netty是非常簡單的,掌握ByteBuf、Channel、Pipeline、Event模型等,就可以進行開發了。你會發現面試netty相關知識,沒得聊。但Netty與其他開發模式很大不同,最主要的就是其非同步化。非同步化造成的後果就是程式設計模型的不同,同時有除錯上的困難,對編碼的要求比較高,因為bug的代價與業務程式碼的bug代價不可同日而語。

但從專案來說,麻雀雖小五臟俱全,從業務層到服務閘道器,以及各種技術保障,包括監控和配置,都是需要考慮的因素。netty本身佔比很小。

使用Netty,我們到底在開發些什麼?
本文將說明使用netty開發,都關注哪些通用的內容,然後附上單機支援100w連線的linux配置。本文並不關注netty的基礎知識。

協議開發

網路開發中最重要的就是其通訊格式,協議。我們常見的protobuf、json、avro、mqtt等,都屬於此列。協議有語法、語義、時序三個要素。

使用Netty,我們到底在開發些什麼?

我見過很多中介軟體應用,採用的是redis協議,而後端落地的卻是mysql;也見過更多的採用mysql協議實現的各種自定義儲存系統,比如proxy端的分庫分表中介軟體、tidb等。

我們常用的redis,使用的是文字協議;mysql等實現的是二進位制協議。放在netty中也是一樣,實現一套codec即可(繼承Decoder或Encoder系列)。netty預設實現了dns、haproxy、http、http2、memcache、mqtt、redis、smtp、socks、stomp、xml等協議,可以說是很全了,直接拿來用很爽。

一個可能的產品結構會是這樣的,對外提供一致的外觀,核心儲存卻不同:

使用Netty,我們到底在開發些什麼?
文字協議在除錯起來是比較直觀和容易的,但安全性欠佳;而二進位制協議就需要依賴日誌、wireshark等其他方式進行分析,增加了開發難度。傳說中的粘包拆包,就在這裡處理。而造成粘包的原因,主要是由於緩衝區的介入,所以需要約定雙方的傳輸概要等資訊,netty在一定程度上解決了這個問題。

每一個想要開發網路應用的同學,心裡都埋了一顆重新設計協議的夢想種子。但協議的設計可以說是非常困難了,要深耕相應業務,還要考慮其擴充套件性。如沒有特別的必要,建議使用現有的協議。

連線管理功能

做Netty開發,連線管理功能是非常重要的。通訊質量、系統狀態,以及一些黑科技功能,都是依賴連線管理功能。

使用Netty,我們到底在開發些什麼?
無論是作為服務端還是客戶端,netty在建立連線之後,都會得到一個叫做Channel的物件。我們所要做的,就是對它的管理,我習慣給它起名叫做ConnectionManager

管理類會通過快取一些記憶體物件,用來統計執行中的資料。比如面向連線的功能:包傳送、接收數量;包傳送、接收速率;錯誤計數;連線重連次數;呼叫延遲;連線狀態等。這會頻繁用到java中concurrent包的相關類,往往也是bug集中地。

但我們還需要更多,管理類會給予每個連線更多的功能。比如,連線建立後,想要預熱一些功能,那這些狀態就可以參與路由的決策。通常情況下,將使用者或其他元資訊也attach到連線上,能夠多維度的根據條件篩選一些連線,進行批量操作,比如灰度、過載保護等,是一個非常重要的功能。

管理後臺可以看到每個連線的資訊,篩選到一個或多個連線後,能夠開啟對這些連線的流量錄製、資訊監控、斷點除錯,你能體驗到掌控一切的感覺。

管理功能還能夠看到系統的整個執行狀態,及時調整負載均衡策略;同時對擴容、縮容提供資料依據。

心跳檢測

應用協議層的心跳是必須的,它和tcp keepalive是完全不同的概念。

應用層協議層的心跳檢測的是連線雙方的存活性,兼而連線質量,而keepalive檢測的是連線本身的存活性。而且後者的超時時間預設過長,完全不能適應現代的網路環境。

使用Netty,我們到底在開發些什麼?
心跳就是靠輪訓,無論是服務端,還是客戶端比如GCM等。保活機制會在不同的應用場景進行動態的切換,比如程式喚起和在後臺,輪訓的策略是不一樣的。

Netty內建通過增加IdleStateHandler產生IDLE事件進行便捷的心跳控制。你要處理的,就是心跳超時的邏輯,比如延遲重連。但它的輪訓時間是固定的,無法動態修改,高階功能需要自己定製。

在一些客戶端比如Android,頻繁心跳的喚起會浪費大量的網路和電量,它的心跳策略會更加複雜一些。

邊界

優雅退出機制

Java的優雅停機通常通過註冊JDK ShutdownHook來實現。

Runtime.getRuntime().addShutdownHook();
複製程式碼

一般通過kill -15進行java程式的關閉,以便在程式死亡之前進行一些清理工作。

注意:kill -9 會立馬殺死程式,不給遺言的機會,比較危險。

雖然netty做了很多優雅退出的工作,通過EventLoopGroupshutdownGracefully方法對nio進行了一些狀態設定,但在很多情況下,這還不夠多。它只負責單機環境的優雅關閉。

流量可能還會通過外層的路由持續進入,造成無效請求。我的通常做法是首先在外層路由進行一次本地例項的摘除,把流量截斷,然後再進行netty本身的優雅關閉。這種設計非常簡單,即使沒有重試機制也會執行的很好,前提是在路由層需要提前暴露相關介面。

使用Netty,我們到底在開發些什麼?

異常處理功能

netty由於其非同步化的開發方式,以及其事件機制,在異常處理方面就顯得異常重要。為了保證連線的高可靠性,許多異常需要靜悄悄的忽略,或者在使用者態沒有感知。

netty的異常會通過pipeline進行傳播,所以在任何一層進行處理都是可行的,但程式設計習慣上,習慣性拋到最外層集中處理。

為了最大限度的區別異常資訊,通常會定義大量的異常類,不同的錯誤會丟擲不同的異常。發生異常後,可以根據不同的型別選擇斷線重連(比如一些二進位制協議的編解碼紊亂問題),或者排程到其他節點。

功能限制

指令模式

網路應用就該幹網路應用的事,任何通訊都是昂貴的。在《Linux之《荒島餘生》(五)網路篇》中,我們談到百萬連線的伺服器,廣播一個1kb訊息,就需要1000M的頻寬,所以並不是什麼都可以放在網路應用裡的。

一個大型網路應用的合理的思路就是值傳送相關指令。客戶端在收到指令以後,通過其他方式,比如http,進行大型檔案到獲取。很多IM的設計思路就是如此。

指令模式還會讓通訊系統的擴充套件性和穩定性得到保證。增加指令可以是配置式的,立即生效,服務端不需要編碼重啟。

穩定性保證

網路應用的流量一般都是非常大的,並不適合全量日誌的開啟。應用應該只關注主要事件的日誌,關注異常情況下的處理流程,日誌要列印有度。

網路應用也不適合呼叫其他緩慢的api,或者任何阻塞I/O的介面。一些實時的事件,也不應該通過呼叫介面吐出資料,可以走高速mq等其他非同步通道。

快取可能是網路應用裡用的最多的元件。jvm內快取可以儲存一些單機的統計資料,redis等儲存一些全域性性的統計和中間態資料。

使用Netty,我們到底在開發些什麼?
網路應用中會大量使用redis、kv、高吞吐的mq,用來快速響應使用者請求。總之,儘量保持通訊層的清爽,你會省去很多憂慮。

單機支援100萬連線的Linux配置

單機支援100萬連線是可行的,但頻寬問題會成為顯著的瓶頸。啟用壓縮的二進位制協議會節省部分頻寬,但開發難度增加。

《LWP程式資源耗盡,Resource temporarily unavailable》中提到的ES配置一樣,優化都有類似的思路。這份配置,可以節省你幾天的時間,請收下!

作業系統優化

更改程式最大檔案控制程式碼數

ulimit -n 1048576
複製程式碼

修改單個程式可分配的最大檔案數

echo 2097152 > /proc/sys/fs/nr_open
複製程式碼

修改/etc/security/limits.conf檔案

*   soft nofile  1048576
*   hard nofile 1048576
*   soft nproc unlimited
root soft nproc unlimited
複製程式碼

記得清理掉/etc/security/limits.d/*下的配置

網路優化

開啟/etc/sysctl.conf,新增配置 然後執行,使用sysctl生效

#單個程式可分配的最大檔案數
fs.nr_open=2097152

#系統最大檔案控制程式碼數
fs.file-max = 1048576

#backlog 設定
net.core.somaxconn=32768
net.ipv4.tcp_max_syn_backlog=16384
net.core.netdev_max_backlog=16384

#可用知名埠範圍配置
net.ipv4.ip_local_port_range='1000 65535'

#TCP Socket 讀寫 Buffer 設定
net.core.rmem_default=262144
net.core.wmem_default=262144
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.core.optmem_max=16777216
net.ipv4.tcp_rmem='1024 4096 16777216'
net.ipv4.tcp_wmem='1024 4096 16777216'

#TCP 連線追蹤設定
net.nf_conntrack_max=1000000
net.netfilter.nf_conntrack_max=1000000
net.netfilter.nf_conntrack_tcp_timeout_time_wait=30

#TIME-WAIT Socket 最大數量、回收與重用設定
net.ipv4.tcp_max_tw_buckets=1048576

# FIN-WAIT-2 Socket 超時設定
net.ipv4.tcp_fin_timeout = 15

複製程式碼

總結

netty的開發工作並不集中在netty本身,更多體現在保證服務的高可靠性和穩定性上。同時有大量的工作集中在監控和除錯,減少bug修復的成本。

深入瞭解netty是在系統遇到疑難問題時能夠深入挖掘進行排查,或者對苛刻的效能進行提升。但對於廣大應用開發者來說,netty的上手成本小,死挖底層並不會產生太多收益。

它只是個工具,你還能讓它怎樣啊。 0.jpeg

使用Netty,我們到底在開發些什麼?

相關文章