同事刘阳使用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;
经过测试,问题得以解决