r_.putInt(frameSize);
this.state_ = AbstractNonblockingServer.FrameBufferState.READING_FRAME;
}
if (this.state_ == AbstractNonblockingServer.FrameBufferState.READING_FRAME) {
if (!this.internalRead()) {
return false;
} else {
if (this.buffer_.remaining() == 0) {
this.selectionKey_.interestOps(0);
this.state_ = AbstractNonblockingServer.FrameBufferState.READ_FRAME_COMPLETE;
}
return true;
}
} else {
this.LOGGER.error("Read was called but state is invalid (" + this.state_ + ")");
return false;
}
}
**说明:**
>MAX_READ_BUFFER_BYTES这个值即为对读取的包的长度限制,如果超过长度限制,就不会再读了/
>这个MAX_READ_BUFFER_BYTES是多少呢,thrift代码中给出了答案:
public abstract static class AbstractNonblockingServerArgs<T extends AbstractNonblockingServer.AbstractNonblockingServerArgs<T>> extends AbstractServerArgs<T> {<br>
public long maxReadBufferBytes = 9223372036854775807L;
public AbstractNonblockingServerArgs(TNonblockingServerTransport transport) {
super(transport);
this.transportFactory(new Factory());
}
}
>从上面源码可以看出,默认值居然给到了long的最大值9223372036854775807L。
所以thrift的开发者是觉得使用thrift程序员不够觉得内存不够用吗,这个换算下来就是1045576TB,这个太夸张了,这等于没有限制啊,所以肯定不能用默认值的。
步骤七.通信数据抓包分析
需要可靠的证据证明一个客户端通信的数据包的大小。
这个是我抓到包最大的长度,最大一个包长度只有215B,所以需要限制一下读取大小
步骤八:踏破铁鞋无觅处
在论坛中,看到有人用http请求thrift服务端出现了内存溢出的情况,所以我抱着试试看的心态,在浏览器中发起了http请求,
果不其然,出现了内存溢出的错误,和客户现场出现的问题一摸一样。这个读取内存的时候数量过大,超过了256MB。
> 很明显的一个问题,正常的一个HTTP请求不会有256MB的,考虑到thrift在处理请求的时候有反序列化这个操作。
> 可以做出假设是不是反序列化的问题,不是thrift IDL定义的不能正常的反序列化?
> 验证这个假设,我用Java socket写了一个tcp客户端,向thrift服务端发送请求,果不其然!java.lang.OutOfMemoryError: Java heap space。
> 这个假设是正确的,客户端请求数据不是用thrift IDL定义的话,无法正常序列化,序列化出来的数据会异常的大!大到超过1个G的都有。
步骤九. 找到原因
某些客户端没有正常的序列化消息,导致服务端在处理请求的时候,序列化出来的数据特别大,读取该数据的时候出现的内存溢出。
查看维护记录,在别的客户那里也出现过内存溢出导致服务端崩溃的情况,通过重新安装客户端,就不再复现了。
所以可以确定,客户端存在着无法正常序列化消息的情况。考虑到,客户端量比较大,一个一个排除,再重新安装比较困难,工作量很大,所以可以从服务端的角度来解决问题,减少维护工作量。
最后可以确定解决方案了,真的是废了很大的劲,不过也是颇有收获
问题解决方案
非常简单
在构造TThreadedSelectorServer的时候,增加args.maxReadBufferBytes = 1*1024 * 1024L;也就是说修改maxReadBufferBytes的大小,设置为1MB。
客户端与服务端通过thrift通信的数据包,最大十几K,所以设置最大1MB,是足够的。代码部分修改完成,版本不做改变**
修改完毕后,这次进行了异常流测试,发送了http请求,使服务端无法正常序列化。
服务端处理结果如下:
thrift会抛出错误日志,并直接没有读这个消息,返回false,不处理这样的请求,将其视为错误请求。
3.国外有人对thrift一些server做了压力测试,如下图所示:
使用thrift中的TThreadedSelectorServer吞吐量达到18000以上
由于高性能,申请内存和清除内存的操作都是非常快的,平均3ms就处理了一个请求。
所以是推荐使用TThreadedSelectorServer
4.修改启动脚本,增大堆内存,分配单独的直接内存。
修改为java -server -Xms512m -Xmx768m -XX:MaxPermSize=256m -XX:NewSize=256m -XX:MaxNewSize=512m -XX:MaxDirectMemorySize=128M。
设置持久代最大值 MaxPermSize:256m
设置年轻代大小 NewSize:256m
年轻代最大值 MaxNewSize:512M
最大堆外内存(直接内存)MaxDirectMemorySize:128M
5.综合论坛中,StackOverflow一些同僚的意见,在使用TThreadedSelectorServer时,将读取内存限制设置为1MB,最为合适,正常流和异常流的情况下不会有内存溢出的风险。
之前启动脚本给服务端分配的堆内存过小,考虑到是NIO,所以在启动服务端的时候,有必要单独分配一个直接内存供NIO使用.修改启动参数。
增加堆内存大小直接内存,防止因为服务端缓存太大,导致thrift服务没有内存可申请,无法处理请求。
总结:
真的是一次非常酸爽的过程,特此发个博客记录一下,如果有说的不对的对方,欢迎批评斧正!如果觉得写的不错,欢迎给我点个推荐,您的一个推荐是我莫大的动力!