NIO框架之MINA原始碼解析(一):背景

chaofanwei發表於2014-09-01


“你們的agent佔了好多系統的埠,把我們的很多業務系統都給整死了,給我們造成了很大的損失,要求你們的相關領導下週過來道歉”   --   來自我們的一個客戶。


 怎麼可能呢,我們都不相信,我們的agent只佔一個埠啊!


事實勝過雄辯,經過查證,確實是由於我們的agent佔了好多系統的埠,我看了一下日誌,基本把系統可用的埠占完了!




為什麼呢?MINA框架私自開的!


由於我們的agent端使用了NIO通訊框架MINA,但並沒有使用好,造成了這一幾乎毀滅行的災難。


還是先看程式碼吧。

/**
 * 非同步傳送訊息
 * @param agent
 * @param request
 */
public void sendMessageToAgent(Agent agent, HyRequest request) {
	IoSession session = null;
	IoConnector connector=null;
	long startTime = System.currentTimeMillis();
	try {
		// 建立一個非阻塞的客戶端程式
		 connector = new NioSocketConnector();
		// 設定連結超時時間
		connector.setConnectTimeoutMillis(connectTimeoutMillis);

		ObjectSerializationCodecFactory objsCodec = new ObjectSerializationCodecFactory();
		objsCodec.setDecoderMaxObjectSize(DEFAULTDECODER);
		objsCodec.setEncoderMaxObjectSize(DEFAULTDECODER);
		ProtocolCodecFilter codecFilter = new ProtocolCodecFilter(
				objsCodec);
		// 資料轉換,編碼設定
		connector.getFilterChain()
				.addLast("codec", codecFilter);
		// 訊息
		connector.setHandler(clientHandler);
		
		SocketAddress socketAddress = new InetSocketAddress(
				agent.getIpAddr(), agent.getAgentPort());
		ConnectFuture future = connector.connect(socketAddress);
		future.awaitUninterruptibly();
		session = future.getSession();
		String json = mapper.writeValueAsString(request);
		session.write(json);
		
		long endTime = System.currentTimeMillis();
		
		logerr.debug("send-time:" + (endTime - startTime));
		
	} catch (Exception e) {
		logerr.error("host:" + agent.getIpAddr() + ", AgentPORT:" + agent.getAgentPort()
				+ ", 連線異常..."+e.getMessage());
		clientHandler.handlerConnectError(agent, request);
		
	}
}


public class MinaClientHandler extends IoHandlerAdapter {
	// 日誌
	private Logger log = Logger.getLogger(getClass());
	
	private MinaResponseProcesser minaResponseProcesser;
	
	ObjectMapper mapper=null;
	
	@Override
	public void messageReceived(IoSession session, Object message)
			throws Exception {
		String msg = message.toString();
		log.info("receive message from " + session.getRemoteAddress().toString() + ",message:" + message);
		if(null == mapper){
			 mapper = new ObjectMapper();
		}
		//請求訊息轉換為HyResponse物件
		HyResponse response = mapper.readValue(msg, HyResponse.class);		
		String remoteIp= ((InetSocketAddress)session.getRemoteAddress()).getAddress().getHostAddress();		
		response.setRemoteIp(remoteIp);
		HyRequest request = minaResponseProcesser.processResponse(response);
		if(request == null){
			//關閉當前session
			closeSessionByServer(session,response);
		}else{
			session.write(mapper.writeValueAsString(request));
		}
	}
}


上面的邏輯就是,當要傳送一個訊息時,建立一個新的connector,並獲取一個session傳送訊息後直接返回,在MinaClientHandler類的messageReceived裡面處理接受到的響應資料,並進行業務處理,最後如果不需要再次傳送請求,則關閉當前session。


其實出現本文一開始的問題就是在這裡造成的。


在出現我們的agent佔用大量埠後,我們這邊的工程人員就迅速定位到了這個問題,並很快修復了,但修復並不理想,但修復過後的程式碼。


/**
 * 非同步傳送訊息
 * @param agent
 * @param request
 */
public void sendMessageToAgent(Agent agent, HyRequest request) {
	IoSession session = null;
	IoConnector connector=null;
	long startTime = System.currentTimeMillis();
	try {
		// 建立一個非阻塞的客戶端程式
		 connector = new NioSocketConnector();
		// 設定連結超時時間
		connector.setConnectTimeoutMillis(connectTimeoutMillis);

		ObjectSerializationCodecFactory objsCodec = new ObjectSerializationCodecFactory();
		objsCodec.setDecoderMaxObjectSize(DEFAULTDECODER);
		objsCodec.setEncoderMaxObjectSize(DEFAULTDECODER);
		ProtocolCodecFilter codecFilter = new ProtocolCodecFilter(
				objsCodec);
		// 資料轉換,編碼設定
		connector.getFilterChain()
				.addLast("codec", codecFilter);
		// 訊息
		connector.setHandler(clientHandler);
		
		SocketAddress socketAddress = new InetSocketAddress(
				agent.getIpAddr(), agent.getAgentPort());
		ConnectFuture future = connector.connect(socketAddress);
		future.awaitUninterruptibly();
		session = future.getSession();
		String json = mapper.writeValueAsString(request);
		session.write(json);
		// 等待斷開連線
		session.getCloseFuture().awaitUninterruptibly();
		long endTime = System.currentTimeMillis();
		
		logerr.debug("send-time:" + (endTime - startTime));
		//connector.dispose();
	} catch (Exception e) {
		logerr.error("host:" + agent.getIpAddr() + ", AgentPORT:" + agent.getAgentPort()
				+ ", 連線異常..."+e.getMessage());
		clientHandler.handlerConnectError(agent, request);
		
	}finally{
		if(null!=session){
			session.close(true);
			session=null;
		}
		if(null !=connector){
			connector.dispose();
		}
	}
}


只改了一個地方,就是在傳送完訊息後,加了一個等待斷開連線語句和finally語句塊-關閉session和connector。


雖然不會出現程式佔用大量的系統埠這個問題,但會造成另外一個問題-當有一個訊息佇列需要非同步呼叫上面語句傳送訊息時,有原來的非同步(傳送完直接返回,相當於快速併發傳送)變成偽非同步(傳送完訊息後並等待訊息返回處理後返回,相當於順序處理佇列裡面的訊息)。


上面的修改並不是我們想要的結果,但至少修復了佔用大量埠的問題。


由於懷著想徹底修復這個問題的想法,我想還是深入瞭解一下MINA原始碼吧。



相關文章