一、问题描述
线上的一个服务A连续出现rpc调用服务B超时报错。
仔细看了一下报警发现超时的rpc服务B都是在集中在一台服务器h上,并且都是一个接口访问失败。初步排除网络抖动原因和受诉宿主机影响,因为异常还在出现,根据该接口平常qps情况应该是该接口所有请求都超时了。
二、原因分析
1. 查看系统各项监控指标:
CPU:

最上边的线就是该服务器,可以看出cpu使用率很高远超出其他机器。又看了一下这台机器刚刚发布过新版本代码。我的感觉可能是哪个小伙伴挖的坑。其他指标如网络、gc都正常。那么就顺着这条线查下去。
2. 登上服务器,使用top查看下cpu在忙活什么?
发现一个java线程用了90的cpu。
3. 然后通过jstack看一下这个线程在做什么。
看了一下jdk版本,嗯是1.7 ,应该就是著名的hashMap死循环了。
4. 接下来我们跟一下代码看看到底那里出了问题。
static {
schemes.put(StandardScheme.class, new ThriftService.getName_result.getName_resultStandardSchemeFactory(null));
schemes.put(TupleScheme.class, new ThriftService.getAllWmPoiRule_result.getName_result_resultTupleSchemeFactory(null));
Map<ThriftService.getName_result._Fields, FieldMetaData> tmpMap = new EnumMap(ThriftService.getAllWmPoiRule_result._Fields.class);
tmpMap.put(ThriftService.getName_result._Fields.SUCCESS, new FieldMetaData("success", (byte)3, new ListMetaData((byte)15, new StructMetaData((byte)12, WmPoiRule.class))));
metaDataMap = Collections.unmodifiableMap(tmpMap);
FieldMetaData.addStructMetaDataMap(ThriftService.getName_result.class, metaDataMap);
}
public class FieldMetaData implements Serializable {
private static Map<Class<? extends TBase>, Map<? extends TFieldIdEnum, FieldMetaData>> structMap = new HashMap();
...
public FieldMetaData(String name, byte req, FieldValueMetaData vMetaData) {
this.fieldName = name;
this.requirementType = req;
this.valueMetaData = vMetaData;
}
public static void addStructMetaDataMap(Class<? extends TBase> sClass, Map<? extends TFieldIdEnum, FieldMetaData> map) {
structMap.put(sClass, map);
}
...
}
熟悉thrift的同学应该知道以上的代码是thrift自动生成的代码。这段代码里在静态块中使用了HashMap,但是不同类的<clinit>又是多线程并发执行。同一个类的<clinit>又是同步执行的,那么在jdk1.7的情况下并发的put造成了HashMap的死循环的,而<clinit>的同步又block住了访问这一类的所有线程,导致了该接口的所有线程block住不能返回最终导致超时。
既然查到了问题就好解决了!重启万能大法!!!
三、后续TODO
这么明显的bug我想应该早就有人发现了吧,查了一下thrift的issues列表结果真的发现了
https://issues.apache.org/jira/browse/THRIFT-1618
emmmmm事实证明我们该升级了。。。重启服务搞定。