dubbo中的那些「坑」(1) - 關於MINA傳輸協議的bug定位及修復

同事劉陽使用dubbo服務器中配置mina做爲網絡傳輸層,發現大併發狀況下,解碼發生以下異常java

014-12-01 18:00:44,652 [DubboServerHandler-10.1.19.13:20880-thread-164] WARN  alibaba.dubbo.remoting.exchange.codec.ExchangeCodec (ExchangeCodec.java:596) -  [DUBBO] Fail to encode response: Response [id=8119, version=2.0.0, status=40, event=false, error=Fail to decode request due to: RpcInvocation [methodName=null, parameterTypes=null, arguments=null, attachments={input=242}, headers=null], result=null], send bad_response info instead, cause: null, dubbo version: 2.5.5, current host: 127.0.0.1
java.lang.NullPointerException
    at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.encodeResponseData(DubboCodec.java:301)
    at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encodeResponse(ExchangeCodec.java:560)
    at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encode(ExchangeCodec.java:104)
    at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.encode(DubboCountCodec.java:39)
    at com.alibaba.dubbo.remoting.transport.mina.MinaCodecAdapter$InternalEncoder.encode(MinaCodecAdapter.java:79)
    at org.apache.mina.filter.codec.ProtocolCodecFilter.filterWrite(ProtocolCodecFilter.java:214)
    at org.apache.mina.common.support.AbstractIoFilterChain.callPreviousFilterWrite(AbstractIoFilterChain.java:361)
    at org.apache.mina.common.support.AbstractIoFilterChain.access$1300(AbstractIoFilterChain.java:53)
    at org.apache.mina.common.support.AbstractIoFilterChain$EntryImpl$1.filterWrite(AbstractIoFilterChain.java:659)
    at org.apache.mina.common.support.AbstractIoFilterChain$TailFilter.filterWrite(AbstractIoFilterChain.java:587)
    at org.apache.mina.common.support.AbstractIoFilterChain.callPreviousFilterWrite(AbstractIoFilterChain.java:361)
    at org.apache.mina.common.support.AbstractIoFilterChain.fireFilterWrite(AbstractIoFilterChain.java:355)
    at org.apache.mina.transport.socket.nio.SocketSessionImpl.write0(SocketSessionImpl.java:166)
    at org.apache.mina.common.support.BaseIoSession.write(BaseIoSession.java:177)
    at org.apache.mina.common.support.BaseIoSession.write(BaseIoSession.java:168)
    at com.alibaba.dubbo.remoting.transport.mina.MinaChannel.send(MinaChannel.java:95)
    at com.alibaba.dubbo.remoting.transport.AbstractPeer.send(AbstractPeer.java:51)
    at
web

通過對比netty3和netty4做爲傳輸層,卻都沒有發現相似的問題。算法

首先排除不是mina自己的問題,mina也沒有爆出有這個問題,初步判斷dubbo在使用mina時機制有問題apache

通過對比發現緩存

1.netty是爲每個channel分配了一個NettyCodecAdapter, mina確實在服務器監聽前配置了MinaCodecAdapter安全

2.也就是說,netty的每個獨立的通道的Codec(encoder/decoder)是通道安全的服務器

3.mina的全部通道是共享相同的codec(encoder/decoder)的,所以,解碼器中的實例數據時非channel安全的
網絡

所以解碼器中與netty相同的解碼器的緩衝數據算法在併發狀況下將會產生數據覆蓋問題。session

4.解決方案
併發

        1.配置acceptor的監聽器

codecAdapter = new MinaCodecAdapter(getCodec(), getUrl(), this);
        acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(codecAdapter));

    acceptor.addListener(new IoServiceListener(){
            @Override
            public void serviceActivated(IoService service,
                    SocketAddress serviceAddress, IoHandler handler,
                    IoServiceConfig config) {
            }

            @Override
            public void serviceDeactivated(IoService service,
                    SocketAddress serviceAddress, IoHandler handler,
                    IoServiceConfig config) {
            }

            @Override
            public void sessionCreated(IoSession session) {
                codecAdapter.sessionCreated(session);
            }

            @Override
            public void sessionDestroyed(IoSession session) {
                codecAdapter.sessionDestroyed(session);
            }
        });

    2.監聽session的create和destroy事件,傳遞到decoder中,decoder中,經過session和buffer的鍵值對保存對不一樣通道的數據的緩存,

private Map<IoSession, ChannelBuffer> buffers = new ConcurrentHashMap<IoSession, ChannelBuffer>();

        // ChannelBuffers.EMPTY_BUFFER;

        public void sessionCreated(IoSession session) {
            buffers.put(session, ChannelBuffers.EMPTY_BUFFER);
        }

        public void sessionDestroyed(IoSession session) {
            buffers.remove(session);
        }

    3.解碼時經過session得到當前channel的數據

ChannelBuffer buffer = buffers.get(session);
            if(buffer == null) return;


通過測試,問題得以解決

相關文章
相關標籤/搜索