天天看點

大并發量 socket 通信的解決方案大并發量 socket 通信的解決方案What is MINAMINA快速入門MINA深度了解選擇MINA的理由擴充知識

筆者之前的工作主要是做java的web端開發,後因工作原因參與了一個國家級的大項目,主要負責其中底層通訊的前置機子產品。幾經波折,将該系統完成後,結果在第一輪的測試中就慘敗退回。其根本原因就在于原設計文檔的要求單“通信機”與“終端”(注一)之間的并發量要達到2W以上的連接配接通信,而實際運作并發量隻能達到2600個相差了近十倍左右。經過代碼調優、擴充JVM記憶體等等手段,但因基礎資料相差過大,所取得的優化效果十分有限。後考慮在根本着手,隻有更改整個系統的通信接口,才有可能達到設計文檔上的要求。某天在某個技術QQ群裡一次讨論中,有網友向我推薦了一個架構,這就是本文要介紹的主角-MINA。

注一:前置機分成了三個部分,其設計的結構圖如下所示:

大并發量 socket 通信的解決方案大并發量 socket 通信的解決方案What is MINAMINA快速入門MINA深度了解選擇MINA的理由擴充知識

Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Apache 組織一個較新的項目,它為開發高性能和高可用性的網絡應用程式提供了非常便利的架構。目前發行的 MINA 版本支援基于 Java NIO 技術的 TCP/UDP 應用程式開發、序列槽通訊程式(隻在最新的預覽版中提供),MINA 所支援的功能也在進一步的擴充中

目前正在使用 MINA 的軟體包括有:Apache Directory Project、AMQP(Advanced Message Queuing Protocol)、RED5 Server(Macromedia Flash Media RTMP)、ObjectRADIUS、Openfire 等等。

FTPServer,AsyncWeb,SSHD

其實在有人推薦了MINA之後,本人就上網google了一把,搜尋一番下來之後才發現自己的眼界過于狹隘了,原來有相同功能的開源架構還真不少,看來以後得繼續多泡論壇和QQ了(有正當理由上班泡壇子,聊QQ了,偷笑一個^_^。。。。。。)

Netty2:具有很好的構架,并且該架構非常有名,使用該架構的項目不少,這意味着開發團隊持續更新的動力非常大。同時Netty2的文檔非常齊全,并且支援JMX。Netty2的缺點就是實作代碼的品質不是非常好,最大的缺點就是隻支援普通的TCP。

Cindy:起源于Netty2之後,借鑒了Netty2中MessageRecognizer類的設計,在目前的版本中已經全面支援普通TCP/Secure TCP/UDP單點傳播/UDP多點傳播/Pipe,可以使用同一個模型進行同步/異步IO處理。Cindy目前缺點是文檔相對較少以及應用的項目比較少。

Grizzl:的設計與一般的nio架構相比是比較不同的,主要不同點在于讀和寫都是采用blocking方式,并且使用臨時selector;線程模型高度可配置。性能據說比MINA還高,但是學習曲線很高。QuickServer: http://www.quickserver.org/

Xscocket:是一個輕量級的解決方案,核心思想是屏蔽,簡化nio方式的的開發,并不需要過多的學習。

對于這些架構的基本使用和基礎架構,本人都經過了一番研究,發現這些架構要麼重者過重,要麼輕者過輕。鑒于項目的規模及時間的緊迫,再三比較之下,本人還是選擇了學習曲線低,性能優異的MINA. 至于這些優點是如何展現出來的,本文以下内容将繼續為您解讀。

閑話少說,借用大師級的寫書經驗,咱們先來一個hello world。當然在這前我們還是得先做一些準備的。

JAVA NIO

多線程

Socket

以上知識對本文的閱讀了解有一定幫助,但并非一定必需。

MINA2.0:暫時分為1.x和2.x兩個主版本,本文隻涉及2.X的版本,至于為什麼隻講2.X而不講1.X,比較冠冕堂皇的回答是----因為有一位偉人曾經說過:要以發展的眼光看世界。。。。。。而真實原因嘛。。。。。。咳咳。。。。大家都知道的。。我就不便多言了。下載下傳位址:http://mina.apache.org/downloads.html。項目中使用的是2.03,截止本文發稿為止,最新版本為:2.04

log4j:因為其中缺少log4j的包,是以做試驗的朋友還需要去下一個log4j的包。

開發工具:eclipse

Jdk:1.6x

監視測試工具:Oracle JRockit Mission Control 4.0.1 強烈推薦,簡稱JRMC,開發過程中,用它解決了很多性能瓶頸的問題,具體使用方法,因為篇幅所限在此不做詳述,請自行查詢相關文檔。

Server端的Main函數:

IoAcceptor acceptor = new NioSocketAcceptor();// 建立監控器

//acceptor.getSessionConfig().setReadBufferSize(2048);

//acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);

acceptor.getFilterChain().addLast("codec",

New ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"),

LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue())));//加載解/編碼工廠

acceptor.setHandler(newStandBaowenHandler()); //設定處理類,其主要内容見下。

acceptor.bind(new InetSocketAddress(9998));//綁定的監聽端口,可多次綁定,也可同時綁定多個。

StandBaowenHandler的關鍵代碼

public void messageReceived(IoSession session, Object message) throws Exception {           session.getService().getManagedSessions();

String baowenSrc = message.toString();// 原始封包

System.out.println(baowenSrc);

}

鑒于篇幅關系,類沒有寫全,更具體的内容,請大家參考mina壓縮包裡自帶的demo。但實際上服務端需要手寫的部分确實隻有以上二塊内容,服務端就算是寫好了,由次可以看出mina的入門之快。現在我們來測試。打開cmd(這個對于同仁們來說,就不用詳述了吧。。。- -!!),輸入telnet 127.0.0.1 9998(這裡的9998要與上面代碼綁定的端口一緻)随便輸入一些字元,敲下回車後就會在控制台顯示您所輸入的資訊了。

做得這一步的童鞋可以說對MINA的使用可以說已經初步入門了,是不是覺得太簡單了?這也太假了吧,這就算入門了?呵呵。。。個人認為這就是MINA的強悍之一,簡單的幾行代碼就搞定了一個服務端,要入門簡直是太簡單了。但任何事物都有其二面性,真要把一個東西學好,用好,還要不出錯,我們需要了解的東西就太多太多了。下面我們就從MINA架構的底層說起,因為在實際應用當中,要解決一些碰到的難點及問題,對架構的整個運作體系必須要有個全面深入的了解。隻有這樣才能在碰到問題時,有目的,有針對性的去找到症結所在。

大并發量 socket 通信的解決方案大并發量 socket 通信的解決方案What is MINAMINA快速入門MINA深度了解選擇MINA的理由擴充知識

圖1

一個設計成熟的開源架構,總是會僅可能的減少侵入性,并在整個項目中找到合适的位置,而不應對整個項目的構架設計産生過多的影響,圖1就是MINA的應用層示意圖。從圖中和上節的DEMO中我們可以看到,MINA很好的把業務代碼和底層的通信隔離了開來,我們要做的僅僅是建立好監聽,然後寫上我們需要實作的業務邏輯就OK了。

大并發量 socket 通信的解決方案大并發量 socket 通信的解決方案What is MINAMINA快速入門MINA深度了解選擇MINA的理由擴充知識

圖2

(1) IoService:這個接口在一個線程上負責套接字的建立,擁有自己的Selector,監聽是否有連接配接被建立。

(2) IoProcessor:這個接口在另一個線程上負責檢查是否有資料在通道上讀寫,也就是說它也擁有自己的Selector,這是與我們使用JAVA NIO 編碼時的一個不同之處,

通常在JAVA NIO 編碼中,我們都是使用一個Selector,也就是不區分IoService與IoProcessor 兩個功能接口。另外,IoProcessor也是MINA架構的核心元件之一.在MINA架構啟動時,會用一個線程池來專門生成線程,來負責調用注冊在IoService 上的過濾器,并在過濾器鍊之後調用IoHandler。在預設情況IoProcessor 會用N+1個線程來輪流詢問監視的端口是否有資料傳送,其中n為cpu的核心個數。按一般的多線程設計概念來說,IoProcessor的線程數是越多越好,但實際上并非如此,因為大家都知道,IO的操作是非常占用資源的,是以項目中的IoProcessor的線程數應該根據實際需要來定,而這個數字可以在生成IoAcceptor對象時進行設定。Eg IoAcceptor acceptor = newNioSocketAcceptor(N);

(3.) IoFilter:這個接口定義一組攔截器,這些攔截器可以包括日志輸出、黑名單過濾(參見之前代碼示例的注釋部份),甚至是在過濾器鍊中利用AOP寫上權限控制(筆者負責的部分沒有涉及到這權限部分,但根據筆者對架構的了解要實作這一點應該問題不大,有需要的可以自行試驗)。資料的編碼(write 方向)與解碼(read 方向)等功能,其中資料的encode 與decode是最為重要的、也是您在使用Mina 時最主要關注的地方(筆者曾經為了decode的解碼編寫,重寫了不下十幾種實作方式才找到準确無誤适合項目的解碼方法)。

(4.) IoHandler:這個接口負責編寫業務邏輯,也就是接收、發送資料的地方。隻本文的代碼執行個體中,可以看到真正的業務代碼隻有一句:System.out.println(str);真實項目中當然不可能如此簡單,但如果大家把業務處理寫好,并寫好業務接口,真正要用時,呆需要在此處替換即可,再次見證了MINA分層的徹底。

說了這麼多,以上内容也隻能讓大家對MINA有個基礎的了解,對于MINA架構優勢的認識可能還不是很多,那麼下面的内容,就此展開對比讨論,以便大家對MINA的适用場景及優點有個更全面的了解。

在傳統I/O中,最簡單實作高并發伺服器的程式設計方式就是對每一個客戶開啟一個線程。但是這種方式有如下幾個弊端:

用戶端上限很大的情況下不能及時響應

伺服器硬體資源受限,性能也會急劇下降

受制于作業系統的限制

但這種設計方式優點還是有的:

編碼簡單,實作容易

一定數量的連接配接性能比較好。

筆者的項目開發中,最開始就是采用的這種方式,寫起來友善,控制起來也友善,但遺憾的是JVM最多隻能開到2K多的線程,就會報- can not create new thread的錯誤。

(實作結構圖見下:)

大并發量 socket 通信的解決方案大并發量 socket 通信的解決方案What is MINAMINA快速入門MINA深度了解選擇MINA的理由擴充知識

圖3(一對一的結構圖。)

為了解決每一個線程一個連接配接的模型,筆者最開始想到用多個線程處理N個使用者,這樣既可以保證處理多個使用者的同時,線程開銷降到系統的臨界點。

這樣的方式與前一個模型優勢在于同樣的多線程,但線程數固定,充分運用系統的優勢性能,又不存在多餘的開銷。但是缺點也是顯而易見的:

輪詢的消耗不可避免。

一但産生io阻塞,其阻塞的時間純屬浪費。

客戶數量固定的時候沒有前一模型響應快

編碼更加複雜。

大并發量 socket 通信的解決方案大并發量 socket 通信的解決方案What is MINAMINA快速入門MINA深度了解選擇MINA的理由擴充知識

圖4(一對多)

為了解決上述的沖突,最終的解決方案隻能是異步的NIO,而随着筆者對JAVA  NIO的研究發現,要實作異步的NIO,并應用到實際項目中,必須對NIO有着比較深刻的了解和把握,筆者曾嘗試着利用JAVA 原生 NIO接口寫了一個DEMO(具體的使用方法,感興趣的童鞋可以GOOGLE一把,你會發現用原生NIO寫程式與使用MINA寫程式對比起來是多麼的痛苦。。。。 -_-!!),但由于筆者在這方面的底子過薄,試驗結果不如人意,但要對NIO進行更為深入的學習,時間上面也不允許。直到MINA架構的映入眼簾,以上難題不再是問題。。。。以下是利用MINA的實作方式的一個簡圖。

大并發量 socket 通信的解決方案大并發量 socket 通信的解決方案What is MINAMINA快速入門MINA深度了解選擇MINA的理由擴充知識

圖5

其中IoService接口會專門起一個線程來輪詢是否有新的連接配接産生,一旦有連接配接産生則通知IoProcessor,而IoProcessor則起n+1個線程來檢查連接配接是否有資料在上面讀寫(注二)。一旦有連接配接産生,并有資料讀寫,則通知decode或ENCODE,進行封包的解碼或編碼,将處理後的封包再交給業務類進行業務處理。其中IoProcessor是處理請求的配置設定,包括選擇Selector,逾時驗證,狀态記錄等。總之這個類和IoService一起配合工作,封裝了NIO底層的實作以及MINA架構内部的功能的支援.由于過于複雜,篇幅所限是以不作詳細講解.

結合執行個體,并根據以上的圖文講解,我們可以很輕易的總結出利用MINA程式設計的幾個大緻步驟:

建立一個實作了IoService接口的類

設定一個實作了IoFilter接口的過濾器(如果有需要的情況下)

設定一個IoHandler 接口實作的處理類,用于處理事件(必須)

對IoService綁定一個端口開始工作

關于MINA的大緻運作流程及使用步驟,我們就暫時分析到這,具體更細節的關于一些核心類的使用方法及自定義編碼器的方法,大家可以直接參考mina中所帶的幾個案例,寫得非常詳細,足夠解決大家在項目中碰到的大部分問題,接下來要與大家交流的是使用MINA時非常有可能遇到的一些擴充知識。

注二:這一點請特别注意,因IoProcessor也是相當于輪詢機制,這導緻在封包過長時,或其它原因導緻封包不能一次傳輸完畢的情況下,必須儲存同一連接配接(在MINA中是以IoSession類生成的對象)下的上一次狀态,這樣才能截取到一個完成的封包,而這也是Decode(編碼器)需要做的核心工作,新手往往就在這上面要跌跟鬥。

這部分的内容要說起來跟MINA的使用關聯不大,但實際情況是用上了MINA架構的項目基本上多多少少都會涉及到這一塊,那就是多線程的程式設計。多線程的程式設計曆來是JAVA程式設計中的重難點,很多新手碰到此類程式設計問題,往往都找不出原因所在,甚至一些有多年程式設計經驗的程式員也會在這上面偶而犯下錯,在這裡筆者也沒有能力通過很短的篇來解說多線程,那麼就向大家介紹幾個類及一些小常識吧,希望能給大家帶來幫助。

繼續閱讀