背景
某广告引擎服务的负载很高,之前用top、jstack来抓高负载的线程,发现高负载的线程都是没有自定义名称的,用的还是默认的命名方式,如下:
48385 www-data 20 0 17.0t 216.8g 209.6g S 1.6 172 111:21.51 pool-14-thread-
48389 www-data 20 0 17.0t 216.8g 209.6g S 1.6 172 111:21.88 pool-14-thread-
48391 www-data 20 0 17.0t 216.8g 209.6g S 1.6 172 111:26.01 pool-14-thread-
48393 www-data 20 0 17.0t 216.8g 209.6g S 1.6 172 111:31.96 pool-14-thread-
48405 www-data 20 0 17.0t 216.8g 209.6g S 1.6 172 111:26.11 pool-14-thread-
47510 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 26:31.58 feature-task-8-
48355 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:02.62 pool-14-thread-
48357 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:01.62 pool-14-thread-
48359 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:04.75 pool-14-thread-
48360 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:08.55 pool-14-thread-
48361 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:06.72 pool-14-thread-
48362 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:02.16 pool-14-thread-
48363 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:07.89 pool-14-thread-
48368 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:09.15 pool-14-thread-
48371 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 110:59.45 pool-14-thread-
48373 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:23.91 pool-14-thread-
48374 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:19.99 pool-14-thread-
48375 www-data 20 0 17.0t 216.8g 209.6g S 1.3 172 111:16.03 pool-14-thread-
我们目前知道的是,高负载线程叫 pool-14-thread-1
这种。那只能用MAT分析JVM的dump文件,MAT有分析对象间的引用关系的功能。
准备工作
因为生产的机器都是70G内存,会导致dump文件太大,mac没有那么大的内存来分析几十G的内存文件。
所以找了预发环境的机器来dump,命令如下:
jmap -dump:format=b,file=heap.bin [pid]
如果预发环境的机器内存还是太大,只能改-Xms -Xmx了,改成4G左右。
dump好以后gzip做压缩,便于文件传输,从500M压缩到90M左右。
安装lrzsz,通过sz -be 将压缩文件从预发环境下载到本地。(大文件传输很困难,失败了n次)
下载eclipse,下载MAT插件。
分析
在MAT的线程视图里找到了pool-14-thread-1
,如下图:
点击线程,Incomming References可以查到引用了该对象的对象。如下图:
通过两次的查询,可以定位到是aa.bb.ccc.hystrix.FallbackExecutor
这个对象。
看下该类的源码:
public class FallbackExecutor {
private static final Logger LOG = LoggerFactory.getLogger(FallbackExecutor.class);
// 这里定义线程池,且没有自定义线程名字!!!
private static final ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
public FallbackExecutor() {
}
public static void execute() {
RecoveryStrategy recoveryStrategy = null;
try {
String strategy = ConfigProperties.getProperties().getProperty("recovery.strategy", "aa.bb.xxx.hystrix.DefaultRecoveryStrategy");
recoveryStrategy = (RecoveryStrategy)Class.forName(strategy).newInstance();
LOG.info("instance recoveryStrategy class:{}", recoveryStrategy);
} catch (Exception var4) {
var4.printStackTrace();
LOG.error("recovery.strategy not exist");
}
int interval = Integer.parseInt(ConfigProperties.getProperties().getProperty("recovery.stategy.interval.minutes", "5"));
executor.scheduleWithFixedDelay(recoveryStrategy, (long)interval, (long)interval, TimeUnit.MINUTES);
try {
KafkaSDKFace.storeStrategy = (StoreStrategy)Class.forName(ConfigProperties.getProperties().getProperty("store.strategy", "aa.bb.xxx.hystrix.DefaultStoreStrategy")).newInstance();
LOG.info("instance storeStrategy class:{}", KafkaSDKFace.storeStrategy);
} catch (Exception var3) {
var3.printStackTrace();
LOG.error("store.strategy not exist");
}
executor.scheduleWithFixedDelay(KafkaSDKFace.storeStrategy, (long)interval, (long)interval, TimeUnit.MINUTES);
}
}
该类主要用于fallback降级。
在repo里查找使用了这个包的地方, xx.yy.zzz.rank.processor.helper.KafkaSdkHelper
这个类,代码如下:
public void writeKafka(Object object, String topic, String type) {
try {
if (object != null) {
KafkaSDKFace sdkFace = getKafkaSDKFace(type);
// KafkaSDKFace是那个Jar包里的类!!!
if(sdkFace != null){
Message message = new Message(System.currentTimeMillis(), MapperUtil.MAPPER.writeValueAsString(object));
sdkFace.send(topic, null, message);
}
else {
logger.error("sdkFace is null");
}
}
} catch (Exception e) {
logger.error("kafka error : " + e.getMessage());
logger.error("kafka stack : " + ExceptionUtils.getStackTrace(e));
}
}
到此已经找到了高负载线程的地方
总结
在服务中自定义线程池的时候,一定要进行重命名,不要使用默认的线程名生成方式,否则对于后期的问题定位是个极大的干扰。