一、問題描述
線上的一個服務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事實證明我們該更新了。。。重新開機服務搞定。