关于memcache client CPU占用率过高的问题排查和解决
现象:
在 手机浏览器的网页代理(类似 UCWeb) 项目试运行的这段日子里,客户抱怨服务器在使用一段时间后会越来越慢,导致网页抓取和转换的运行的非常慢,到系统里面一查CPU的资源使用情况,被占用了90%以上。客户提供的硬件 可是 16G的内存 和 8核cpu ,这明显是代码 太不给力了。
排查:
首先想到的是 代码流程中分块、排序和频繁的数据库操作 导致cpu占用过高,但是使用jmeter渗透测试后发现 存在缓存的流程也是不给力,于是将不相干代码剥离,最后定位到一句代码
CacheObject cacheObject = cacheManager.get(key);
这句代码 核心是调用memcached 客户端的Memcache 的get方法于是精确定位到问题所在:Memcache 在取元素出的问题!!!
定位:
1.一开始偷个懒 检查版本 server端为最新1.4.5版本 客户端为whalin 1.xx 版本 我觉得是客户端版本过老(依赖于jdk 1.4),所以切换客户端为最新的2.5.1 whalin以及net.spy版本的客户端 依然不给力。
2.于是怀疑是memcached 分布式导致,但是现在是单一memcached server环境,又怀疑是网络延迟,把memcached server移到本地测试,不给力啊!!!
3.继续排查,老老实实跟踪代码,下面简单介绍 get时 客户端做的事:
// get SockIO obj using cache key
SockIOPool.SockIO sock = pool.getSock(key, hashCode); //根据 key和 hashcode 到sock池中取得sock
String cmd = "get " + key + "\r\n";
sock.write(cmd.getBytes());
sock.flush();
底层操作还是 sock写入命令方式写入命令后 memcached server 会返回一些信息
正常情况是:
VALUE 1755631828 10 359137
内容
END
第一行 分别为关键字“VALUE” key值 flag值 内容字节长度
第二行 为内容字节
第三行 为关键字 “END”
客户端代码 的流程是在一个死循环中 ,先读取第一行,拿到内容字节长度然后根据这个长度去读取内容碰到 end 时 再退出死循环。仔细检查并且通过jmeter测试多线程log记录循环次数发现这个流程没问题并没有发现死循环。嗯!问题不在此处?
4. 继续排查,发现代码如下
if ((flag & F_COMPRESSED) == F_COMPRESSED) {
try {
// read the input stream, and write to a byte array
// output stream since
// we have to read into a byte array, but we don't
// know how large it
// will need to be, and we don't want to resize it a
// bunch
GZIPInputStream gzi = new GZIPInputStream(
new ByteArrayInputStream(buf));
ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.length);
int count;
byte[] tmp = new byte[2048];
while ((count = gzi.read(tmp)) != -1) {
bos.write(tmp, 0, count);
}
// store uncompressed back to buffer
buf = bos.toByteArray();
gzi.close();
原来是压缩,这个压缩 是通过 实例memcacheclient时设置的
mcc = new MemCachedClient(POOL_NAME);
mcc.setCompressEnable(true);
mcc.setCompressThreshold(8 * 1024);
默认是压缩的 而且是在 8*1024大小时进行压缩在get元素时如果为压缩的元素 则Flag即为 10 ,则通过gizinputstream拿到数据流测试了下 百度的首页 发现点问题
|
|
是否压缩 |
大小 |
10线程渗透测试 cpu占用率 |
|
Baidu |
否 |
29.5K |
37%-51% |
|
Baidu |
是 |
8.3K |
71%-82% |
这边有一定的影响考虑加大 压缩临界点 ,
由于客户的服务器的内存很大 16G 所以改代码为mcc.setCompressThreshold(640 * 1024);这样只有元素大于640K时才进行压缩,但是cpu占用还是很大!!!
5. 查了资料,发现序列化也是影响cpu占用的因素之一,于是检查代码,发现压缩代码后面的代码紧跟着就是 序列化/反序列化:
// we can only take out serialized objects
if ((flag & F_SERIALIZED) != F_SERIALIZED) {
if (primitiveAsString || asString) {
// pulling out string value
log.info("++++ retrieving object and stuffing into a string.");
o = new String(buf, defaultEncoding);
} else {
// decoding object
try {
o = NativeHandler.decode(buf, flag);
} catch (Exception e) {
// if we have an errorHandler, use its hook
if (errorHandler != null)
errorHandler.handleErrorOnGet(this, e, key);
log.error("++++ Exception thrown while trying to deserialize for key: "
+ key, e);
throw new NestedIOException(e);
}
} else {
// deserialize if the data is serialized
ContextObjectInputStream ois = new ContextObjectInputStream(
new ByteArrayInputStream(buf), classLoader);
try {
o = ois.readObject();
} catch (ClassNotFoundException e) {
// if we have an errorHandler, use its hook
if (errorHandler != null)
errorHandler.handleErrorOnGet(this, e, key);
log.error("++++ ClassNotFoundException thrown while trying to deserialize for key: "
+ key, e);
throw new NestedIOException("+++ failed while trying to deserialize for key: "
+ key, e);
}
}
}
为了证明序列化影响的程度测试了一个38K的字符串 和 一个 37.6K的对象,测试结果:
|
|
大小 |
10线程渗透测试 cpu占用率 |
|
对象 |
37.6K |
22%-39% |
|
字符串 |
38K |
5%-11% |
很给力,明白了,字符串不会反序列化,只是简单的转码,还是传输字符串效率高!!!!
6. 随后发现memcache client 在read内容的时候,是这样写的:
int count = 0;
while ( count < b.length ) {
int cnt = in.read( b, count, (b.length – count) );
count += cnt;
}
一眼看上去 没什么问题 但是 我们再循环中加入一个计数标识,然后10个线程循环10次测试发现问题了 10*10 get元素时 应该是 执行100但是却执行了 1934次 即每次取元素时 要读将近20次!!!
于是修改了下代码,在循环中加入
if(a%3==0 || (b.length>2000 && cnt<1000)){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
即每3次 或者 读取量小于1000B时 sleep 20 ms
这样做的好处是 让出CPU占用 减少循环计算的次数 留出更多的时间让client一次能read更多的bytes最后对比测试了下
|
|
10线程渗透cpu占用率 |
|
Client源码 |
77%-91% |
|
Client修改的代码 |
34%-45% |
以上内容纯属本人虚构,如果上述有相同的事情,纯属巧合。
–end–

本文由J2ee企业顾问-黄毅创作,并已采用创作共用署名2.5中国大陆版许可证授权。





