老版本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;
        }
    }
}
posted @ 2022-08-03 19:01  keqinglee  阅读(625)  评论(0编辑  收藏  举报