Netty乾貨分享:京東京麥的生產級TCP閘道器技術實踐總結
1、引言
京東的京麥商家後臺2014年構建閘道器,從HTTP閘道器發展到TCP閘道器。在2016年重構完成基於Netty4.x+Protobuf3.x實現對接PC和App上下行通訊的高可用、高效能、高穩定的TCP長連線閘道器。
早期京麥搭建HTTP和TCP長連線功能主要用於訊息通知的推送,並未應用於API閘道器。隨著逐步對NIO的深入學習和對Netty框架的瞭解,以及對系統通訊穩定能力的愈加高要求,採用NIO技術應用閘道器實現API請求呼叫的想法,最終在2016年實現,並完全支撐業務化執行。由於諸多的改進,包括TCP長連線容器、Protobuf的序列化、服務泛化呼叫框架等等,效能比HTTP閘道器提升10倍以上,穩定性也遠遠高於HTTP閘道器。
本文重點介紹京麥TCP閘道器的技術架構及Netty的應用實踐。
簡單介紹一下京麥是什麼:
京麥工作臺是京東商城為京東的商家準備的一款後臺管理工具,它可以使您不登陸商家後臺就能進行訂單生產,快速實現訂單下載發貨流程。類似於淘寶的旺旺商家版(現在叫淘寶千牛)這樣的東西。
學習交流:
– 即時通訊開發交流群:320837163 [推薦]
– 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
(本文同步釋出於:http://www.52im.net/thread-1243-1-1.html )
2、本文作者
– 京東商家研發部架構師;
– 豐富的構建高效能高可用大規模分散式系統的研發、架構經驗;
– 2013年加入京東,目前負責京麥服務閘道器和京麥服務市場的系統研發工作。
3、TCP閘道器的網路結構
基於Netty構建京麥TCP閘道器的長連線容器,作為閘道器接入層提供服務API請求呼叫。
客戶端通過域名+埠訪問TCP閘道器,域名不同的運營商對應不同的VIP,VIP釋出在LVS上,LVS將請求轉發給後端的HAProxy,再由HAProxy把請求轉發給後端的Netty的IP+Port。
LVS轉發給後端的HAProxy,請求經過LVS,但是響應是HAProxy直接反饋給客戶端的,這也就是LVS的DR模式。
4、TCP閘道器長連線容器架構
TCP閘道器的核心元件是Netty,而Netty的NIO模型是Reactor反應堆模型(Reactor相當於有分發功能的多路複用器Selector)。每一個連線對應一個Channel(多路指多個Channel,複用指多個連線複用了一個執行緒或少量執行緒,在Netty指EventLoop),一個Channel對應唯一的ChannelPipeline,多個Handler序列的加入到Pipeline中,每個Handler關聯唯一的ChannelHandlerContext。
TCP閘道器長連線容器的Handler就是放在Pipeline的中。我們知道TCP屬於OSI的傳輸層,所以建立Session管理機制構建會話層來提供應用層服務,可以極大的降低系統複雜度。所以,每一個Channel對應一個Connection,一個Connection又對應一個Session,Session由Session Manager管理,Session與Connection是一一對應,Connection儲存著ChannelHandlerContext(ChannelHanderContext可以找到Channel),Session通過心跳機制來保持Channel的Active狀態。
每一次Session的會話請求(ChannelRead)都是通過Proxy代理機制呼叫Service層,資料請求完畢後通過寫入ChannelHandlerConext再傳送到Channel中。資料下行主動推送也是如此,通過Session Manager找到Active的Session,輪詢寫入Session中的ChannelHandlerContext,就可以實現廣播或點對點的資料推送邏輯。如下圖所示。
京麥TCP閘道器使用Netty Channel進行資料通訊,使用Protobuf進行序列化和反序列化,每個請求都將被封裝成Byte二進位制位元組流,在整個生命週期中,Channel保持長連線,而不是每次呼叫都重新建立Channel,達到連結的複用。
我們接下來來看看基於Netty的具體技術實踐。
5、TCP閘道器Netty Server的IO模型
具體的實現過程如下:
1)建立ServerBootstrap,設定BossGroup與WorkerGroup執行緒池;
2)bind指定的port,開始偵聽和接受客戶端連結(如果系統只有一個服務端port需要監聽,則BossGroup執行緒組執行緒數設定為1);
3)在ChannelPipeline註冊childHandler,用來處理客戶端連結中的請求幀。
6、TCP閘道器的執行緒模型
TCP閘道器使用Netty的執行緒池,共三組執行緒池,分別為BossGroup、WorkerGroup和ExecutorGroup。其中,BossGroup用於接收客戶端的TCP連線,WorkerGroup用於處理I/O、執行系統Task和定時任務,ExecutorGroup用於處理閘道器業務加解密、限流、路由,及將請求轉發給後端的抓取服務等業務操作。
NioEventLoop是Netty的Reactor執行緒,其角色:
1)Boss Group:作為服務端Acceptor執行緒,用於accept客戶端連結,並轉發給WorkerGroup中的執行緒;
2)Worker Group:作為IO執行緒,負責IO的讀寫,從SocketChannel中讀取報文或向SocketChannel寫入報文;
3)Task Queue/Delay Task Queu:作為定時任務執行緒,執行定時任務,例如鏈路空閒檢測和傳送心跳訊息等。
7、TCP閘道器執行時序圖
如上圖所示,其中步驟一至步驟九是Netty服務端的建立時序,步驟十至步驟十三是TCP閘道器容器建立的時序。
步驟一:建立ServerBootstrap例項,ServerBootstrap是Netty服務端的啟動輔助類。
步驟二:設定並繫結Reactor執行緒池,EventLoopGroup是Netty的Reactor執行緒池,EventLoop負責所有註冊到本執行緒的Channel。
步驟三:設定並繫結伺服器Channel,Netty Server需要建立NioServerSocketChannel物件。
步驟四:TCP連結建立時建立ChannelPipeline,ChannelPipeline本質上是一個負責和執行ChannelHandler的職責鏈。
步驟五:新增並設定ChannelHandler,ChannelHandler序列的加入ChannelPipeline中。
步驟六:繫結監聽埠並啟動服務端,將NioServerSocketChannel註冊到Selector上。
步驟七:Selector輪訓,由EventLoop負責排程和執行Selector輪詢操作。
步驟八:執行網路請求事件通知,輪詢準備就緒的Channel,由EventLoop執行ChannelPipeline。
步驟九:執行Netty系統和業務ChannelHandler,依次排程並執行ChannelPipeline的ChannelHandler。
步驟十:通過Proxy代理呼叫後端服務,ChannelRead事件後,通過發射排程後端Service。
步驟十一:建立Session,Session與Connection是相互依賴關係。
步驟十二:建立Connection,Connection儲存ChannelHandlerContext。
步驟十三:新增SessionListener,SessionListener監聽SessionCreate和SessionDestory等事件。
8、TCP閘道器原始碼分析
8.1 Session管理
Session是客戶端與服務端建立的一次會話連結,會話資訊中儲存著SessionId、連線建立時間、上次訪問事件,以及Connection和SessionListener,在Connection中儲存了Netty的ChannelHandlerContext上下文資訊。Session會話資訊會儲存在SessionManager記憶體管理器中。
建立Session的原始碼:
通過原始碼分析,如果Session已經存在銷燬Session,但是這個需要特別注意,建立Session一定不要建立那些斷線重連的Channel,否則會出現Channel被誤銷燬的問題。因為如果在已經建立Connection(1)的Channel上,再建立Connection(2),進入session.close方法會將cxt關閉,Connection(1)和Connection(2)的Channel都將會被關閉。在斷線之後再建立連線Connection(3),由於Session是有一定延遲,Connection(3)和Connection(1/2)不是同一個,但Channel可能是同一個。
所以,如何處理是否是斷線重練的Channel,具體的方法是在Channel中存入SessionId,每次事件請求判斷Channel中是否存在SessionId,如果Channel中存在SessionId則判斷為斷線重連的Channel,程式碼如下圖所示。
8.2 心跳
心跳是用來檢測保持連線的客戶端是否還存活著,客戶端每間隔一段時間就會傳送一次心跳包上傳到服務端,服務端收到心跳之後更新Session的最後訪問時間。在服務端長連線會話檢測通過輪詢Session集合判斷最後訪問時間是否過期,如果過期則關閉Session和Connection,包括將其從記憶體中刪除,同時登出Channel等。如下圖程式碼所示。
通過原始碼分析,在每個Session建立成功之後,都會在Session中新增TcpHeartbeatListener這個心跳檢測的監聽,TcpHeartbeatListener是一個實現了SessionListener介面的守護執行緒,通過定時休眠輪詢Sessions檢查是否存在過期的Session,如果輪訓出過期的Session,則關閉Session。如下圖程式碼所示。
同時,注意到session.connect方法,在connect方法中會對Session新增的Listeners進行新增時間,它會迴圈呼叫所有Listner的sessionCreated事件,其中TcpHeartbeatListener也是在這個過程中被喚起。如下圖程式碼所示。
8.3 資料上行
資料上行特指從客戶端傳送資料到服務端,資料從ChannelHander的channelRead方法獲取資料。資料包括建立會話、傳送心跳、資料請求等。這裡注意的是,channelRead的資料包括客戶端主動請求服務端的資料,以及服務端下行通知客戶端的返回資料,所以在處理object資料時,通過資料標識區分是請求-應答,還是通知-回覆。如下圖程式碼所示。
8.4 資料下行
資料下行通過MQ廣播機制到所有伺服器,所有伺服器收到訊息後,獲取當前伺服器所持有的所有Session會話,進行資料廣播下行通知。如果是點對點的資料推送下行,資料也是先廣播到所有伺服器,每天伺服器判斷推送的端是否是當前伺服器持有的會話,如果判斷訊息資料中的資訊是在當前服務,則進行推送,否則拋棄。如下圖程式碼所示。
通過原始碼分析,資料下行則通過NotifyProxy的方式傳送資料,需要注意的是Netty是NIO,如果下行通知需要獲取返回值,則要將非同步轉同步,所以NotifyFuture是實現java.util.concurrent.Future的方法,通過設定超時時間,在channelRead獲取到上行資料之後,通過seq來關聯NotifyFuture的方法。如下圖程式碼所示。
下行的資料通過TcpConnector的send方法傳送,send方式則是通過ChannelHandlerContext的writeAndFlush方法寫入Channel,並實現資料下行,這裡需要注意的是,之前有另一種寫法就是cf.await,通過阻塞的方式來判斷寫入是否成功,這種寫法偶發出現BlockingOperationException的異常。如下圖程式碼所示。
使用阻塞獲取返回值的寫法:
關於BlockingOperationException的問題我在StackOverflow進行提問,非常幸運的得到了Norman Maurer(Netty的核心貢獻者之一)的解答:
最終結論大致分析出,在執行write方法時,Netty會判斷current thread是否就是分給該Channe的EventLoop,如果是則行執行緒執行IO操作,否則提交executor等待分配。當執行await方法時,會從executor裡fetch出執行執行緒,這裡就需要checkDeadLock,判斷執行執行緒和current threads是否時同一個執行緒,如果是就檢測為死鎖丟擲異常BlockingOperationException。
9、本文小結
本篇文章粗淺的向大家介紹了京麥TCP閘道器中使用的Netty實現長連線容器的架構,涉及TCP長連線容器搭建的關鍵點一一進行了闡述,以及對原始碼進行簡單的分析。在京麥發展過程裡Netty還有很多的實踐應用,例如Netty4.11+HTTP2實現APNs的訊息推送等等。
(本文同步釋出於:http://www.52im.net/thread-1243-1-1.html)
附錄:更多精編資料彙總
[1] 網路程式設計基礎資料:
《技術往事:改變世界的TCP/IP協議(珍貴多圖、手機慎點)》
《通俗易懂-深入理解TCP協議(下):RTT、滑動視窗、擁塞處理》
《理論聯絡實際:Wireshark抓包分析TCP 3次握手、4次揮手過程》
《P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介》
《P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解》
《P2P技術詳解(三):P2P技術之STUN、TURN、ICE詳解》
《高效能網路程式設計(一):單臺伺服器併發TCP連線數到底可以有多少》
《高效能網路程式設計(二):上一個10年,著名的C10K併發連線問題》
《高效能網路程式設計(三):下一個10年,是時候考慮C10M併發問題了》
《高效能網路程式設計(四):從C10K到C10M高效能網路應用的理論探索》
《不為人知的網路程式設計(一):淺析TCP協議中的疑難雜症(上篇)》
《不為人知的網路程式設計(二):淺析TCP協議中的疑難雜症(下篇)》
《不為人知的網路程式設計(三):關閉TCP連線時為什麼會TIME_WAIT、CLOSE_WAIT》
《不為人知的網路程式設計(四):深入研究分析TCP的異常關閉》
《不為人知的網路程式設計(六):深入地理解UDP協議並用好它》
《網路程式設計懶人入門(一):快速理解網路通訊協議(上篇)》
《網路程式設計懶人入門(二):快速理解網路通訊協議(下篇)》
《網路程式設計懶人入門(四):快速理解TCP和UDP的差異》
《Netty乾貨分享:京東京麥的生產級TCP閘道器技術實踐總結》
[2] NIO非同步網路程式設計資料:
《Java新一代網路程式設計模型AIO原理及Linux系統AIO介紹》
《開源NIO框架八卦——到底是先有MINA還是先有Netty?》
《NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示》
《NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示》
《NIO框架入門(三):iOS與MINA2、Netty4的跨平臺UDP雙向通訊實戰》
《NIO框架入門(四):Android與MINA2、Netty4的跨平臺UDP雙向通訊實戰》
《Netty 4.x學習(二):Channel和Pipeline詳解》
《Apache Mina框架高階篇(一):IoFilter詳解》
《Apache Mina框架高階篇(二):IoHandler詳解》
《Apache MINA2.0 開發指南(中文版)[附件下載]》
《解決MINA資料傳輸中TCP的粘包、缺包問題(有原始碼)》
《實踐總結:Netty3.x升級Netty4.x遇到的那些坑(執行緒篇)》
《實踐總結:Netty3.x VS Netty4.x的執行緒模型》
《Twitter:如何使用Netty 4來減少JVM的GC開銷(譯文)》
《Netty乾貨分享:京東京麥的生產級TCP閘道器技術實踐總結》
[3] 有關IM/推送的通訊格式、協議的選擇:
《強列建議將Protobuf作為你的即時通訊應用資料傳輸格式》
《全方位評測:Protobuf效能到底有沒有比JSON快5倍?》
《詳解如何在NodeJS中使用Google的Protobuf》
[4] 有關IM/推送的心跳保活處理:
《應用保活終極總結(一):Android6.0以下的雙程式守護保活實踐》
《應用保活終極總結(二):Android6.0及以上的保活實踐(程式防殺篇)》
《應用保活終極總結(三):Android6.0及以上的保活實踐(被殺復活篇)》
《Android端訊息推送總結:實現原理、心跳保活、遇到的問題等》
《微信團隊原創分享:Android版微信後臺保活實戰分享(程式保活篇)》
《微信團隊原創分享:Android版微信後臺保活實戰分享(網路保活篇)》
《移動端IM實踐:WhatsApp、Line、微信的心跳策略分析》
[5] 有關WEB端即時通訊開發:
《Web端即時通訊技術盤點:短輪詢、Comet、Websocket、SSE》
《Comet技術詳解:基於HTTP長連線的Web端實時通訊技術》
《WebSocket詳解(一):初步認識WebSocket技術》
《WebSocket詳解(二):技術原理、程式碼演示和應用案例》
《WebSocket詳解(三):深入WebSocket通訊協議細節》
《LinkedIn的Web端即時通訊實踐:實現單機幾十萬條長連線》
《Web端即時通訊技術的發展與WebSocket、Socket.io的技術實踐》
《Web端即時通訊安全:跨站點WebSocket劫持漏洞詳解(含示例程式碼)》
《開源框架Pomelo實踐:搭建Web端高效能分散式IM聊天伺服器》
《詳解Web端通訊方式的演進:從Ajax、JSONP 到 SSE、Websocket》
[6] 有關IM架構設計:
《一套海量線上使用者的移動端IM架構設計實踐分享(含詳細圖文)》
《騰訊QQ1.4億線上使用者的技術挑戰和架構演進之路PPT》
[7] 有關IM安全的文章:
《即時通訊安全篇(一):正確地理解和使用Android端加密演算法》
《即時通訊安全篇(四):例項分析Android中金鑰硬編碼的風險》
《即時通訊安全篇(五):對稱加密技術在Android平臺上的應用實踐》
《傳輸層安全協議SSL/TLS的Java平臺實現簡介和Demo演示》
《理論聯絡實際:一套典型的IM通訊協議設計詳解(含安全層設計)》
《微信新一代通訊安全解決方案:基於TLS1.3的MMTLS詳解》
《來自阿里OpenIM:打造安全可靠即時通訊服務的技術實踐分享》
《Web端即時通訊安全:跨站點WebSocket劫持漏洞詳解(含示例程式碼)》
[8] 有關實時音視訊開發:
《即時通訊音視訊開發(五):認識主流視訊編碼技術H.264》
《即時通訊音視訊開發(九):實時語音通訊的迴音及迴音消除�概述》
《即時通訊音視訊開發(十):實時語音通訊的迴音消除�技術詳解》
《即時通訊音視訊開發(十一):實時語音通訊丟包補償技術詳解》
《即時通訊音視訊開發(十三):實時視訊編碼H.264的特點與優勢》
《即時通訊音視訊開發(十五):聊聊P2P與實時音視訊的應用情況》
《即時通訊音視訊開發(十六):移動端實時音視訊開發的幾個建議》
《即時通訊音視訊開發(十七):視訊編碼H.264、VP8的前世今生》
《學習RFC3550:RTP/RTCP實時傳輸協議基礎知識》
《開源實時音視訊技術WebRTC中RTP/RTCP資料傳輸協議的應用》
《基於RTMP資料傳輸協議的實時流媒體技術研究(論文全文)》
《還在靠“喂喂喂”測試實時語音通話質量?本文教你科學的評測方法!》
《實現延遲低於500毫秒的1080P實時音視訊直播的實踐分享》
《技術揭祕:支援百萬級粉絲互動的Facebook實時視訊直播》
《理論聯絡實際:實現一個簡單地基於HTML5的實時視訊直播》
《淺談實時音視訊直播中直接影響使用者體驗的幾項關鍵技術指標》
《首次披露:快手是如何做到百萬觀眾同場看直播仍能秒開且不卡頓的?》
《開源實時音視訊技術WebRTC在Windows下的簡明編譯教程》
[9] IM開發綜合文章:
《IM訊息送達保證機制實現(一):保證線上實時訊息的可靠投遞》
《開源IM工程“蘑菇街TeamTalk”的現狀:一場有始無終的開源秀》
《QQ音樂團隊分享:Android中的圖片壓縮技術詳解(上篇)》
《QQ音樂團隊分享:Android中的圖片壓縮技術詳解(下篇)》
《騰訊原創分享(一):如何大幅提升行動網路下手機QQ的圖片傳輸速度和成功率》
《騰訊原創分享(二):如何大幅壓縮行動網路下APP的流量消耗(上篇)》
《騰訊原創分享(二):如何大幅壓縮行動網路下APP的流量消耗(下篇)》
《如約而至:微信自用的移動端IM網路層跨平臺元件庫Mars已正式開源》
《基於社交網路的Yelp是如何實現海量使用者圖片的無失真壓縮的?》
[10] 開源移動端IM技術框架資料:
《開源移動端IM技術框架MobileIMSDK:常見問題解答》
《開源移動端IM技術框架MobileIMSDK:壓力測試報告》
[11] 有關推送技術的文章:
《iOS的推送服務APNs詳解:設計思路、技術原理及缺陷等》
《信鴿團隊原創:一起走過 iOS10 上訊息推送(APNS)的坑》
《Android端訊息推送總結:實現原理、心跳保活、遇到的問題等》
《一個基於MQTT通訊協議的完整Android推送Demo》
《求教android訊息推送:GCM、XMPP、MQTT三種方案的優劣》
《掃盲貼:淺談iOS和Android後臺實時訊息推送的原理和區別》
《移動端IM實踐:谷歌訊息推送服務(GCM)研究(來自微信)》
《從HTTP到MQTT:一個基於位置服務的APP資料通訊實踐概述》
《基於WebSocket實現Hybrid移動應用的訊息推送實踐(含程式碼示例)》
《Go語言構建千萬級線上的高併發訊息推送系統實踐(來自360公司)》
[12] 更多即時通訊技術好文分類:
(本文同步釋出於:http://www.52im.net/thread-1243-1-1.html)
相關文章
- 宜人貸蜂巢API閘道器技術解密之Netty使用實踐API解密Netty
- 微服務閘道器Gateway實踐總結微服務Gateway
- 技術乾貨 | Flutter線上程式設計實踐總結Flutter程式設計
- 微服務實踐分享(2)api閘道器微服務API
- 長連線閘道器技術專題(九):去哪兒網酒店高效能業務閘道器技術實踐
- 乾貨分享:容器 PaaS 新技術架構下的運維實踐架構運維
- 技術乾貨|品高雲的SDN實踐
- 乾貨 | 京東技術中臺的Flutter實踐之路Flutter
- netty搭建Tcp伺服器實踐NettyTCP伺服器
- 前端閘道器踩坑實踐前端
- 技術乾貨|如何實現分鐘級故障管理
- 生產環境中AI呼叫的最佳化:AI閘道器高價值應用實踐AI
- 開放API閘道器實踐(一) ——設計一個API閘道器API
- EDAS 流量入口閘道器最佳實踐
- [乾貨分享]1000篇乾貨好文!量子技術——資訊篇
- SSLO如何實現會話保持?技術乾貨線上分享會話
- [乾貨分享]1000篇乾貨好文!量子技術——進階篇
- 【SpringCloud技術專題】「Gateway閘道器係列」(3)微服務閘道器服務的Gateway全流程開發實踐指南(2.2.X)SpringGCCloudGateway微服務
- 開放API閘道器實踐(三) —— 限流API
- API閘道器,企業級閘道器可擴充套件API套件
- 微服務技術棧:API閘道器中心,落地實現方案微服務API
- IP、子網掩碼、預設閘道器/預設閘道器、DNS、伺服器、埠的總結DNS伺服器
- 技術乾貨收集
- 微服務與閘道器技術(SIA-GateWay)微服務Gateway
- 奈學乾貨分享:分散式CAP實踐分析分散式
- Canvas乾貨總結Canvas
- 乾貨分享:18道Spring面試題Spring面試題
- 【乾貨】區塊鏈技術生態的設計|《白話區塊鏈》作者蔣勇分享實錄區塊鏈
- 乾貨:記一次JavaWeb網站技術架構總結JavaWeb網站架構
- 個推微服務閘道器架構實踐微服務架構
- 高德渲染閘道器Go語言重構實踐Go
- 得物自研API閘道器實踐之路API
- 網禦威五安全閘道器的技術創新
- 技術乾貨 | AB測試和灰度釋出探索及實踐
- 【騰訊Bugly乾貨分享】React Native專案實戰總結React Native
- [乾貨分享]1000篇乾貨好文!量子技術——專家觀點篇
- Modbus TCP轉Profinet閘道器配置案例TCP
- 生產服務GC調優實踐基本流程總結GC