凌晨4点运维老大收到NAT网关连接数打满报警(官网页面接口超时),运维自己先看了看服务器相关配置,先后还联系了阿里云的客服,客服建议升级NAT网络连接阈值,之前是1w升级到了5w,但后来还是给研发打电话确认问题。
大早上7点领导给打电话,那会儿娃还在睡觉...我还以为是垃圾电话,怕把孩子吵醒直接挂了,吃完早饭才看见技术群爆了,生产服务报警....几个研发运维小伙伴一起讨论方案,初步认为昨天发版导致的问题,优先恢复业务,最快就是回滚服务,但不严谨的是7:38只回退了 1个微服务节点,另一个节点后来也报警8:17回退,至此运维看阿里云监控tcp连接数才降下来,期间还有oom报警。具体原因还在定位。
原因分析:
微服务里发版的内容无非是增加了一个第三方服务的调用(腾讯云的推送服务),找到代码确认问题还比较好定位,调用im的http接口用的 cn.hutool.http 工具类,但并未设置连接超时和读超时。本地测试了一下 这个工具类默认超时时间20s多,而从凌晨4点开始腾讯云接口超时,导致我们自己的微服务一直等待20s后超时。
try {String response = HttpUtil.createPost("http://192.168.126.10:903/test").contentType(MediaType.APPLICATION_JSON_VALUE).body("{}").execute().body();} catch (Exception e) {e.printStackTrace();System.out.println(System.currentTimeMillis() - start);}
加上超时时间的设置 .timeout(3000)
String response = HttpUtil.createPost(requestUrl).contentType(MediaType.APPLICATION_JSON_VALUE).body(json.toJSONString()).timeout(3000).execute().body();
但腾讯云接口为什么会连接超时呢,如果接口本身是连通的且响应比较快应该也不会发生后面一系列问题。腾讯的im服务分级别,普通服务可能有限制,比如每秒200次请求;但我们的推送并没有达到腾讯的频率阈值,为什么报连接超时,即便达到阈值,对方也应该会立即返回一个错误码,而不会出现连接超时的错误,即便设置超时时间,好像还是不能说明到底为什么会有connect timeout的问题;所以只设置超时时间,还是不能完全解决连接超时的问题
问题回到运维接的报警(最初的连接数暴涨)上,查看我们自己的nat网关才发现连接数今天凌晨开始在持续增加,直到打满了达到了阈值(1w左右),达到阈值后,后面再调用外部接口获取不到可用连接,过了默认的连接超时时间,自然就连接超时了。
那为什么连接数会暴涨?理论上大厂腾讯的接口应该很快,不会出现响应慢一直占着连接的情况,那只有一种可能我们调用对方接口时,请求结束并未释放连接。那为什么没释放?
本地简单测试一下,这里起了2000个线程,每个线程内部逻辑去请求一个虚假的地址,通过netstat查看tcp连接情况
public class Main {public static void main(String[] args) {for (int i=0;i<2000;i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {long start = System.currentTimeMillis();try {String response = HttpUtil.createPost("http://192.168.12.10:903/am/testHis").contentType(MediaType.APPLICATION_JSON_VALUE).body("{}").execute().body();} catch (Exception e) {System.out.println(System.currentTimeMillis() - start);}System.out.println("线程结束"+ Thread.currentThread().getName());}});thread.start();}}
}
tcp链接信息如下:SYN_SENT,说明还在tcp连接建立阶段(因为虚假的接口地址,肯定是不能连通的,到了连接超时时间会报connect time out)。 没有进入ESTABLISHED状态。
但实际等线程都结束后 tcp连接是释放了的。
腾讯云接口对接情况是接口是连通的,但请求完接口连接未释放?
解决:
增加请求连接关闭代码
finally {
if (httpResponse != null) {
httpResponse.close();
}
}
经验教训:
(1)凡是对接第三方接口的需要设置超时时间
(2)操作资源时记得及时关闭,比如数据库连接,redis连接,文件句柄等等