老版本Unirest.setTimeouts导致OOM
Unirest:简化的、轻量级的HTTP客户端库,Kong团队维护,该团队维护着著名的开源网关项目API Gateway Kong。
公司一同学在使用Unirest请求三方资源时,因错误使用超时时间(setTimeouts)导致应用OOM。
挂掉的是个基础服务,上游服务产生不同程度的震荡,影响严重。在此分析此事故的原因及解决方案,供参考。
问题代码
@GetMapping("test")
public String test() {
try {
//设置超时时间
Unirest.setTimeouts(1000, 5000);
//发起http请求
HttpResponse<String> resp = Unirest.get(url).asString();
//todo
} catch (Exception e) {
}
return "执行完毕";
}
分析
错误的使用Unirest.setTimeouts,此方法每次调用都会创建一个SyncIdleConnectionMonitorThread线程一直等待,导致线程数量越来越多,最终OOM。
解决
- 使用同步代码块仅设置一次(老版本未找到单独设置超时时间的方法,所以有些场景是解决不了的)
static {
Unirest.setTimeouts(1000, 5000);
}
- 更换工具包(即使此错误已经写到了公司开发手册,你还是无法确保有同学再来次这种操作,因为没犯过错的永远不知道后果多么严重)
- 升级Unirest版本
源码分析
源码:https://github.com/kong/unirest-java
文档:http://kong.github.io/unirest-java
公司使用版本1.4.9,现在最新版本3.13.10
public static void setTimeouts(long connectionTimeout, long socketTimeout) {
Options.setOption(Option.CONNECTION_TIMEOUT, connectionTimeout);
Options.setOption(Option.SOCKET_TIMEOUT, socketTimeout);
Options.refresh();
}
public static void refresh() {
//省略若干行
SyncIdleConnectionMonitorThread syncIdleConnectionMonitorThread = new SyncIdleConnectionMonitorThread(syncConnectionManager);
setOption(Option.SYNC_MONITOR, syncIdleConnectionMonitorThread);
syncIdleConnectionMonitorThread.start();
//省略若干行
}
public class SyncIdleConnectionMonitorThread extends Thread {
private final HttpClientConnectionManager connMgr;
public SyncIdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
super.setDaemon(true);
this.connMgr = connMgr;
}
public void run() {
while(true) {
try {
if (!Thread.currentThread().isInterrupted()) {
synchronized(this) {
this.wait(5000L);
this.connMgr.closeExpiredConnections();
this.connMgr.closeIdleConnections(30L, TimeUnit.SECONDS);
continue;
}
}
} catch (InterruptedException var4) {
}
return;
}
}
}