自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

基于MINA構(gòu)建高性能的NIO應用

開發(fā) 后端
MINA是非常好的C/S架構(gòu)的java服務器,這里轉(zhuǎn)了一篇關(guān)于它的使用感受。

MINA是非常好的C/S架構(gòu)的java服務器,這里轉(zhuǎn)了一篇關(guān)于它的使用感受。

前  言

MINA是Trustin Lee最新制作的Java通訊框架。通訊框架的主要作用是封裝底層IO操作,提供高級的操作API。比較出名的通訊框架有C++的ACE、Python的Twisted,而Java的通訊框架還有QuickServer、Netty2、Cindy、Grizzly等。

2004年6月,Trustin Lee發(fā)布了一個通訊框架Netty2,是Java界第一個事件模型架構(gòu)的通訊框架,Cindy也從中借鑒了不少思想。由于Netty2的架構(gòu)不是很好,Trustin Lee在2004年底加入Apache Directory組之后,重寫了整個框架,取名為MINA。MINA是一個基于Java NIO的通訊框架,Java從1.4開始引入NIO,提供了一個非阻塞、高性能的IO底層。

目前使用MINA的產(chǎn)品并不是很多,比較出名的就有Apache Directory、Openfire(Jive出品的一個XMPP產(chǎn)品)、red5(研究flash流媒體flv技術(shù)的朋友應該很清楚這個東西,adobe fms的競爭者,國內(nèi)也有視頻網(wǎng)站在使用)等等。

筆者在07年初的時候,公司新項目需要用Java實現(xiàn)一個Socket Server,對比了Netty2、Cindy、QuickServer和MINA。當時Netty2已經(jīng)停止開發(fā),也找不到官方網(wǎng)站和代碼,比較了另外三個框架之后,毅然選擇了當時文檔比較缺乏和使用群較少的MINA,一年以來的使用經(jīng)驗來看,感覺還是很不錯的,MINA有著清晰的架構(gòu),很方便做自定義的擴充。在1.0發(fā)布之后,官方網(wǎng)站充實了很多,增加了不少文檔,也聽到越來越多的朋友開始使用MINA。后來專門針對JDK 1.5發(fā)布了1.1的版本,使用JDK內(nèi)置的concurrent代替backport-util-concurrent。目前1.0和1.1同時存在,但已經(jīng)不再增加新功能,僅僅發(fā)布bug fix的版本,新功能都在2.0中實現(xiàn),2.0調(diào)整了架構(gòu),性能有更大的提升,目前還在開發(fā)中。

基本特性

通過Java NIO支持TCP和UDP協(xié)議,另外還支持RS232和VM內(nèi)通訊。由于MINA有清晰的架構(gòu),你也能很簡單地實現(xiàn)一個底層網(wǎng)絡協(xié)議。目前不支持阻塞IO,似乎還沒有計劃支持,當然你可以在其之上實現(xiàn)一個阻塞的模型,不過按照筆者的經(jīng)驗來說,非阻塞IO更適合Server端編程。

一個類似ServletFilter的過濾器模型。這是筆者認為MINA的精髓所在,通過引入過濾器模型,可以將一些非業(yè)務的功能獨立開來,層次更清晰,很有AOP的思想,可以很方便地進行日志、協(xié)議轉(zhuǎn)換、壓縮等等功能,還能在運行中動態(tài)增加或去掉功能。

可以直接使用底層的ByteBuffer,也可以使用用戶定義的消息Object和編碼方式。

高度可定制的線程模型,單線程、一個線程池,或者類似SEDA的多個線程池。

SSL支持,攻擊防御和流量控制,mock測試友好,JMX支持,Spring集成,你還需要更多嗎。

一個簡單的例子

MINA使用非常簡單,筆者以前做過一段時間傳統(tǒng)的Java Socket開發(fā),不過一直對Java NIO不是很理解,但是MINA很快就上手了,MINA封裝了NIO繁瑣的部分,使你可以更專注于業(yè)務功能實現(xiàn)。話不多說,讓我們來看一個簡單的例子,一個很常見的例子,時間服務器。

我們的實現(xiàn)目標是一個能響應多個客戶端的請求,然后返回服務器當前的系統(tǒng)時間的功能。傳統(tǒng)的Java Socket程序,我們需要每accept一個客戶端連接,就創(chuàng)建一個新的線程來響應,這會令到系統(tǒng)整體負載能力有較大的限制,而且我們必須手工編寫連接管理等代碼。讓我們來看看MINA是怎么處理的。

首先我們從官方網(wǎng)站下載MINA 1.1,這里我們假設JDK為1.5以上的版本,如果你使用的是JDK 1.4,請下載MINA 1.0,MINA 1.0跟1.1幾乎一樣,但是強烈建議使用JDK 1.5以上以獲得更好的性能。

解開壓縮包之后,能看見很多jar包,這里暫不介紹每個包的具體作用,可以把所有包都導入項目。值得留意的是MINA使用了一個slf4j的日志庫,該日志庫大有取締common-logging之勢。 這里是我們的主程序,非常簡單。

首先我們需要一個IoAcceptor,這里我們選擇了一個SocketAcceptor,也就是TCP協(xié)議。

然后,我們給應用加上日志過濾器和協(xié)議編碼過濾器。

最后,我們把acceptor bind到本機的8123端口,并且使用TimeServerHandler來實現(xiàn)協(xié)議。

TimeServerHandler是我們實現(xiàn)具體業(yè)務功能的地方。 IoHandlerAdapter提供了7個事件方法,我們要做的事情僅僅是挑選我們需要做出響應的事件進行重載。在我這個例子了,我重載了兩個方法。sessionCreated會在客戶端連接的時候調(diào)用,通常我們會在這里進行一些初始化操作,我這里僅僅是打印一條信息。messageReceived就是整個Handler的中心部分,每一個從客戶端發(fā)過來的消息都會轉(zhuǎn)化成對該方法的調(diào)用。由于我們加入了協(xié)議編碼過濾器,因此這里獲得的Object msg是一個String,而不是默認的ByteBuffer(下文會詳細介紹ProtocolCodecFilter)。這里我們實現(xiàn)了一個很簡單的業(yè)務功能,如果用戶輸入的是quit,就斷開連接,否則就輸入當前時間??梢钥闯觯琁oSession封裝了對當前連接的操作。

至此,我們就實現(xiàn)了一個時間服務器。

  1. public class TimeServer {   
  2. public static void main(String[] args) throws IOException {   
  3. IoAcceptor acceptor = new SocketAcceptor();   
  4. SocketAcceptorConfig cfg = new SocketAcceptorConfig();   
  5. cfg.getFilterChain().addLast( "logger"new LoggingFilter() );   
  6. cfg.getFilterChain().addLast( "codec"new ProtocolCodecFilter( new TextLineCodecFactory()));   
  7. acceptor.bind( new InetSocketAddress(8123), new TimeServerHandler(), cfg);   
  8. System.out.println("Time server started.");   
  9. }  
  1. public class TimeServerHandler extends IoHandlerAdapter {   
  2.  public void messageReceived(IoSession session, Object msg) throws Exception {   
  3.  String str = (String) msg;   
  4.  if"quit".equalsIgnoreCase(str) ) {   
  5.  session.close();   
  6.  return;   
  7.  }   
  8.  
  9.  Date date = new Date();   
  10.  session.write( date.toString() );   
  11.  System.out.println("Message written...");   
  12.  }   
  13.    
  14.  public void sessionCreated(IoSession session) throws Exception {   
  15.  System.out.println("Session created...");   
  16.  }   

MINA架構(gòu)

這里,我借用了一張Trustin Lee在Asia 2006的ppt里面的圖片來介紹MINA的架構(gòu)。

Remote Peer就是客戶端,而下方的框是MINA的主要結(jié)構(gòu),各個框之間的箭頭代表數(shù)據(jù)流向。

大家可以對比剛剛的例子來看這個架構(gòu)圖,IoService就是整個MINA的入口,負責底層的IO操作,客戶端發(fā)過來的消息就是由它處理。剛剛我們使用的IoAcceptor就是一個IoService,之所以抽象成IoService,是因為MINA用同樣的架構(gòu)來處理服務器和客戶端編程,IoService的另一個子類就是IoConnector,用于客戶端。不過根據(jù)筆者的使用經(jīng)驗,使用非阻塞的模型進行客戶端編程非常的不方便,你最好尋求其他的阻塞通訊框架。

IoService把數(shù)據(jù)轉(zhuǎn)化成一個一個的事件,傳遞給IoFilterChain。你可以加入一連串的IoFilter,進行各種功能。筆者的建議是將一些功能性的,業(yè)務不相關(guān)的代碼,用IoFilter來實現(xiàn),使得整個應用結(jié)構(gòu)更清晰,也方便代碼重用。

被IoFilter處理過的事件,發(fā)送給 IoHandler,然后我們在這里實現(xiàn)具體的業(yè)務邏輯。這個部分很簡單,如果你有Swing的使用經(jīng)驗的話,你會發(fā)現(xiàn)它跟Swing的事件非常相像,你要做的事情,僅僅是重載你需要的方法,然后編寫具體的業(yè)務功能。在這其中,最重要的一個方法就是messageReceived了。

值得留意的是一個IoSession的類,每一個IoSession實例代表這一個連接,我們需要對連接進行的任何操作都通過這個類來實現(xiàn)。

從IoHandler通過調(diào)用IoSession.write等方法向客戶端發(fā)送的消息,會通過跟輸入數(shù)據(jù)相反的次序依次傳遞,直至由IoService負責把數(shù)據(jù)發(fā)送給客戶端。

這就已經(jīng)是MINA的全部,是不是很簡單。

接下來,我會詳細介紹我們編寫具體代碼的時候主要涉及到的三個類,IoHandler、IoSession和IoFilter。

IoHandler

MINA的內(nèi)部實現(xiàn)了一個事件模型,而IoHanlder則是所有事件最終產(chǎn)生響應的位置。每一個方法的名字很明確表明該事件的含義。messageReceived是接收客戶端消息的事件,我們應該在這里實現(xiàn)業(yè)務邏輯。messageSent是服務器發(fā)送消息的事件,一般情況下我們不會使用它。sessionClosed是客戶端斷開連接的事件,可以在這里進行一些資源回收等操作。值得留意的是,客戶端連接有兩個事件,sessionCreated和sessionOpened,兩者稍有不同,sessionCreated是由I/O processor線程觸發(fā)的,而sessionOpened在其后,由業(yè)務線程觸發(fā)的,由于MINA的I/O processor線程非常少,因此如果我們真的需要使用sessionCreated,也必須是耗時短的操作,一般情況下,我們應該把業(yè)務初始化的功能放在sessionOpened事件中。

細心的讀者可能會發(fā)現(xiàn),我們剛剛的例子繼承的是IoHandlerAdapter,IoHandlerAdapter其實就是一個IoHanlder的空的實現(xiàn),這樣我們就可以不用重載不感興趣的事件。

IoSession

IoSession是一個接口,MINA里很多的地方都使用接口,很好地體現(xiàn)了面向接口編程的思想。它提供了對當前連接的操作功能,還有用戶定義屬性的存儲功能,這點非常重要。IoSession是線程安全的,也就是我們能夠在多線程環(huán)境中隨意操作IoSession,這點給開發(fā)帶來很大的好處。我們來看看具體提供的方法,筆者列舉一些比較常用和重要的方法

在這里,筆者把IoSession的方法大致分成三類

第一類,連接操作功能。

最主要的方法有兩個,向客戶端發(fā)送消息和斷開連接??梢钥吹某觯瑆rite接受的變量是一個Object,但是實際上應該傳入什么類型呢?具體還得看你是否使用了ProtocolCodecFilter(下面會詳細介紹),如果使用了ProtocolCodecFilter,那這個message將可能是一個String,或者是一個用戶定義的JavaBean。默認的情況,message是一個ByteBuffer。ByteBuffer是MINA的一個類,跟java.nio.ByteBuffer類同名,MINA 2.0將會將它改成IoBuffer,以避免討論上的誤會。

另一個值得留意的是Future類,MINA是一個非阻塞的通信框架,其中一個明顯的體現(xiàn)就是調(diào)用IoSession.write方法是不會阻塞的。用戶調(diào)用了write方法之后,消息內(nèi)容會發(fā)到底層等候發(fā)送,至于什么時候發(fā)出,就不得而知了。當然,實際上調(diào)用了write之后,數(shù)據(jù)幾乎是立刻發(fā)出的,這得益與NIO的高性能。但是,如果我們必須確認了消息發(fā)出,然后進行某些處理,我們就需要使用Future類,以下是一個很常見的代碼。

通過調(diào)用future.join,程序就會阻塞,直至消息處理結(jié)束。我們還能通過future.isWritten得知消息是否成功發(fā)送。

在這里,筆者順便說一個實際使用的發(fā)現(xiàn),消息發(fā)送是會自動合并的,簡單來說,如果在很短的時間里,對同一個IoSession進行了兩次write操作,客戶端有可能只收到一條消息,而這條消息就是服務器發(fā)出的兩條消息前后接起來。這樣的設計可以在高并發(fā)的時候節(jié)省網(wǎng)絡開銷,而筆者的實際使用過程中,效果也相當好。但是如果這樣行為會導致客戶端工作不正常,你也可以通過參數(shù)關(guān)閉它。

第二類,屬性存儲操作。

通常來說,我們的系統(tǒng)是有用戶狀態(tài)的,我們就需要在連接上存儲用戶屬性,IoSession的Attribute就是這樣一個功能。例如兩個連接同時連入服務器,一個連接是用戶A,用戶ID是13,另一個連接是用戶B,用戶ID是14,我們就可以在用戶登錄成功之后,調(diào)用IoSession.setAttribute(“login_id”,13),然后在其后的操作中,通過IoSession.getAttribute(“login_id”)獲得當前登錄用戶ID,并進行相應的操作。簡單來說,就是一個類似HttpSession的功能,當然具體的實現(xiàn)方法不一樣。

第三類,連接狀態(tài)。

這里就不多說了,從方法名上我們就能知道它具體的功能。

IoFilter

過濾器是MINA的一個很重要的功能。IoFilter也是一個接口,但是相對比較復雜,這里就不列舉它的方法了。簡單來說IoFilter就像ServletFilter,在事件被IoHandler處理之前或之后進行一些特定的操作,但是它比ServletFilter復雜,可以處理很多種事件,除了包括IoHandler的7個事件以外,還有一些內(nèi)部的事件可以進行操作。

MINA提供了一些常用的IoFilter實現(xiàn),例如有LoggingFilter(日志功能)、BlacklistFilter(黑名單功能)、CompressionFilter(壓縮功能)、SSLFilter(SSL支持),這些過濾器比較簡單,通過閱讀它們的源代碼,能夠更進一步理解過濾器的實現(xiàn)。筆者在這里要重點介紹兩個過濾器,ProtocolCodecFilter和ExecutorFilter

ProtocolCodecFilter

網(wǎng)絡傳輸?shù)膬?nèi)容其實本質(zhì)是一個二進制流,但是我們的業(yè)務功能不會,或者說不應該去直接操作二進制流。MINA默認向IoHandler傳入的message是一個ByteBuffer,如果我們直接在IoHandler操作ByteBuffer,會導致大量協(xié)議分析的代碼和實際的業(yè)務代碼混雜在一起。最適合的做法,就是在IoFilter把ByteBuffer轉(zhuǎn)換成String或者JavaBean,ProtocolCodecFilter正是這樣的一個功能的過濾器。

使用ProtocolCodecFilter很簡單,我們只要把ProtocolCodecFilter加入到FilterChain就可以了,但是我們需要提供一個ProtocolCodecFactory。其實ProtocolCodecFilter僅僅是實現(xiàn)了過濾器部分的功能,它會將最終的轉(zhuǎn)換工作,交給從ProtocolCodecFactory獲得的Encode和Decode。如果我們需要編寫自己的ProtocolCodec,就應該從ProtocolCodecFactory入手。MINA內(nèi)置了幾個ProtocolCodecFactory,比較常用的就是ObjectSerializationCodecFactory和TextLineCodecFactory。

ObjectSerializationCodecFactory是Java Object序列化之后的內(nèi)容直接跟ByteBuffer互相轉(zhuǎn)化,比較適合兩端都是Java的情況使用。TextLineCodecFactory就是String跟ByteBuffer的轉(zhuǎn)化,說白了就是文本,例如你要實現(xiàn)一個SMTP服務器,或者POP服務器,就可以使用它。而筆者的實際使用,大多數(shù)情況都是使用

TextLineCodecFactory

這里提及一下IoFilter的順序問題,IoFilter是有加入順序的,例如,先加入LoggingFilter再加入ProtocolCodecFilter,和先加入ProtocolCodecFilter再加入LoggingFilter的效果是不一樣的,前者LoggingFilter寫入日志的內(nèi)容是ByteBuffer,而后者寫入日志的是轉(zhuǎn)換后具體的類,例如String。實際使用的時候,一定要處理好過濾器的順序。

ExecutorFilter

另一個重要的過濾器就是ExecutorFilter。這里,我需要先說明一下MINA的線程工作模式,MINA默認是單線程處理所有客戶端的消息,也就是說,即使你在一臺8CPU的機器上面跑,可能也只用到一個CPU,另外,如果某次消息處理太耗時,就會導致其他消息等待,整體的吞吐量下降。很多朋友抱怨MINA的性能差,其實是因為他們沒有加入ExecutorFilter的緣故。ExecutorFilter設計的很精巧,大家可以仔細閱讀一下源代碼,它會將同一個連接的消息合并起來按順序調(diào)用,不會出現(xiàn)兩個線程同時處理同一個連接的情況。

  1. 1.IoAcceptor acceptor = ...;   
  2. 2.IoServiceConfig acceptorConfig = acceptor.getDefaultConfig();   
  3. 3.acceptorConfig.setThreadModel(ThreadModel.MANUAL); 

這里再次提及IoFitler的順序問題,一般情況下,我們會將ExecutorFilter放在ProtocolCodecFilter之后,因為我們不需要多線程地執(zhí)行ProtocolCodec操作,用單一線程來進行ProtocolCodec性能會比較高,而具體的業(yè)務邏輯可能還設計數(shù)據(jù)庫操作,因此更適合放在不同的線程中運行。

優(yōu)化指南

MINA默認配置的性能并不是很高的,部分原因是MINA目前還保留初期版本的架構(gòu),另外一個原因是因為JVM的發(fā)展。

1.IoAcceptor acceptor = new SocketAcceptor(Runtime.getRuntime().availableProcessors() + 1, Executors.newCachedThreadPool());
首先我們關(guān)閉默認的ThreadModel設置 ThreadModel是一個很簡單的線程實現(xiàn),用于IoService。但是它實在太弱,以至于在并發(fā)環(huán)境產(chǎn)生大量問題。在MINA 2.0中,ThreadModel直接被取消。你應該使用ExecutorFilter來實現(xiàn)線程。

  1. acceptor.getDefaultConfig().getFilterChain().addLast("threadPool"new ExecutorFilter(Executors.newCachedThreadPool()); 

然后我們增加I/O處理線程

每一個Acceptor/Connector都使用一個線程來處理連接,然后把連接發(fā)送給I/O processor進行讀寫操作,我們只可以修改I/O processor使用的線程數(shù),用以下代碼設置 當然是要將ExecutorFilter加入,上文已經(jīng)很詳細地描述了 筆者在開發(fā)過程中,多次遇到OutOfMemoryError,經(jīng)過研究之后才發(fā)現(xiàn)原因。MINA默認是使用direct memory實現(xiàn)ByteBuffer池的方案(以下簡稱direct buffer),通過JNI在內(nèi)存開辟一段空間來使用,該方案在早期的MINA版本中是一個非常好的特性,那是因為MINA開發(fā)初期,JVM并沒有現(xiàn)在的強大,帶有池效果的direct buffer性能比較好。但是當我們使用-Xms -Xmx等指令增加JVM可使用的內(nèi)存,那僅僅增加了堆的內(nèi)存空間,而direct memory的空間并沒有增加,導致MINA實際使用的時候經(jīng)常出現(xiàn)OutOfMemoryError。如果你的確想使用direct memory,可以通過-XX:MaxDirectMemorySize選項來設置。不過筆者不建議這樣做,因為最新的測試表明,在現(xiàn)代的JVM里面,direct memory比堆的表現(xiàn)更差。這里可能有讀者會覺得奇怪,為什么不用池,而要用堆呢,而且還需要gc。那是因為現(xiàn)在的JVM gc能力已經(jīng)很強了,而且在并發(fā)環(huán)境里面,pool的同步也是一個性能的問題。我們可以通過這樣的代碼進行設置 MINA 2.0已經(jīng)默認把直接內(nèi)存分配改成堆,為了提供最好的性能和穩(wěn)定性。

  1. ByteBuffer.setUseDirectBuffers(false);   
  2. ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); 

最后一條優(yōu)化技巧就是,把你的應用部署在Linux上,并且打開Java NIO使用Linux epoll的功能??赡苣氵€沒聽過epoll,但是你應該聽過Lighttpd、Nginx、Squid等,得益于epoll,它們提供很高的網(wǎng)絡性能,還占用非常少的系統(tǒng)資源。JDK6已經(jīng)默認把epoll配置打開,因此筆者建議把你的應用部署在JDK6上面,也同時因為JDK6還有別的優(yōu)化特性。如果你的應用必須部署在JDK5上,你也可以通過參數(shù)把epoll支持打開。

原文鏈接:http://blog.csdn.net/chenyi8888/article/details/5341916

【編輯推薦】

  1. Java NIO如何處理慢速的連接
  2. Java NIO2 AIO開發(fā)核心流程
  3. Java NIO開發(fā)實例
  4. Java NIO 聊天室實例
  5. 多線程NIO客戶端實例
責任編輯:林師授 來源: chenyi8888的博客
相關(guān)推薦

2009-06-03 14:24:12

ibmdwWebSphere

2025-02-05 12:09:12

2023-10-26 08:35:53

2023-12-26 00:58:53

Web應用Go語言

2016-05-20 14:20:31

ASP.NET建議

2023-09-04 14:52:48

2011-03-11 09:51:47

Java NIO

2023-07-12 08:24:19

Java NIO通道

2012-02-14 14:10:16

ibmdw

2020-06-05 07:20:41

測試自動化環(huán)境

2023-09-04 08:32:43

web開發(fā)圖像

2024-12-27 09:37:51

2022-12-09 08:40:56

高性能內(nèi)存隊列

2011-10-21 14:20:59

高性能計算HPC虛擬化

2011-10-25 13:13:35

HPC高性能計算Platform

2009-11-30 09:40:23

Java 7 NIO2HTTP Server

2025-03-04 08:00:00

機器學習Rust開發(fā)

2009-10-29 09:11:50

Juniper高性能網(wǎng)絡

2024-02-20 09:00:00

2024-12-02 14:28:17

JavaScriptWeb開發(fā)
點贊
收藏

51CTO技術(shù)棧公眾號