from:http://my.oschina.net/aruan/blog/351594

同事刘阳使用dubbo服务器中配置mina作为网络传输层,发现大并发情况下,解码发生如下异常

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 

经过对比netty3和netty4作为传输层,却都没有发现类似的问题。

首先排除不是mina本身的问题,mina也没有爆出有这个问题,初步判断dubbo在使用mina时机制有问题

经过对比发现

1.netty是为每一个channel分配了一个NettyCodecAdapter, mina确实在服务器监听前配置了MinaCodecAdapter

2.也就是说,netty的每一个独立的通道的Codec(encoder/decoder)是通道安全的

3.mina的所有通道是共享相同的codec(encoder/decoder)的,因此,解码器中的实例数据时非channel安全的

因此解码器中与netty相同的解码器的缓冲数据算法在并发情况下将会产生数据覆盖问题。

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;


经过测试,问题得以解决