restTemplate未设置连接数导致服务雪崩问题
创始人
2025-05-29 19:42:19

背景

昨天发版遇到个线上问题,由于运维操作放量时隔离机器过多,导致只有大概三分之一的机器承载全部流量,等于单台机器的流量突增至正常时候的三倍。前置对外的api服务开始疯狂报错:

ConnectionPoolTimeoutException:Timeout warning for connection from pool

问题分析

连接池满了。查看下相关代码,用了restTemplate去调用另外一个子系统,继续查看关于连接池的信息:

org.apache.http.conn.ConnectionPoolTimeoutException 是 Apache HttpClient 抛出的一种 山异常,表示从连接池获取连接超时。
这种异常通常是由以下原因导致:
1.连接池中没有可用的连接。当请求到达时,如果连接池中没有可用的连接,就会尝试创建新的连接。如果创建连接的速度很慢,或者连接池中的连接已经用完了,就会出现ConnectionPoolTimeoutException 异常。
2.连接池中的连接都被占用。如果连接池中的所有连接都正在被占用,而且没有连接释放回池中,就会导致连接池超时异常。3.请求超时时间设置过短。如果设置的请求超时时间过短,就可能在等待连接的过程中超时。

迅速去查看连接池大小配置了多少,发现并没有进行相关的配置,那默认的就是2,这样远远不够应对当前瞬时的大并发流量的。

问题解决

立刻进行了相关配置:

@Bean
public RestTemplate buildRestTemplate(){final ConnectionKeepAliveStrategy myStrategy = (response, context) -> {return 5 * 1000;//设置一个链接的最大存活时间};MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);// 总连接数pollingConnectionManager.setMaxTotal(1000);// 同路由的并发数pollingConnectionManager.setDefaultMaxPerRoute(1000);HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(pollingConnectionManager).setKeepAliveStrategy(myStrategy).build();HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);factory.setConnectTimeout(3000);factory.setReadTimeout(5000);return new RestTemplate(factory);
}

直接设置成了1000,交给测试压测,瞬时200的并发量压测也没有出现ConnectionPoolTimeoutException的问题,看来成功解决了。

restTemplate优化点补充

另外,restTemplate还有个点能够优化。

1. RestTemplate 介绍

RestTemplate和Spring提供的JdbcTemplate类似,对象一旦构建(使用过程中不对其属性进行修改)就是线程安全的,多线程环境下可以安全使用。

2. 场景描述

A系统接口需要访问B系统接口,正常请求时,这部分代码耗时不是很明显(约10ms)。后来,进行接口压力测试,发现请求耗时长达500ms,导致整个接口的tps很难上去,调整线程池参数效果努力无果,后来对请求B系统接口做了内存级别的缓存(guava),tps增长了3倍左右(约500)。

3.问题分析

B系统给出的压测数据显示,单实例接口的tps在1000+,因此初步排除了B系统接口性能差的可能。于是,开始深究系统本身代码可能存在的问题。

最开始的代码编写方式如下:

String url = "xxx.com/api"';
RestTemplate restTemplate = new RestTemplate();
MemeberCardCodeRspDTO result = restTemplate.getForObject(url, MemeberCardCodeRspDTO.class);
System.out.println(result != null ? result.getMsg() : "null");

看上去简单明了,两行代码搞定,非常优雅。后来在组长大大的帮助下,大致定位了问题点,觉得该部分代码可能存在部分性能问题,应该抽取成单实例,即不能每次使用时重新new一个新的对象。改造后的代码为:

@Bean
public RestTemplate buildRestTemplate(){final ConnectionKeepAliveStrategy myStrategy = (response, context) -> {return 5 * 1000;//设置一个链接的最大存活时间};MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);// 总连接数pollingConnectionManager.setMaxTotal(1000);// 同路由的并发数pollingConnectionManager.setDefaultMaxPerRoute(1000);HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(pollingConnectionManager).setKeepAliveStrategy(myStrategy).build();HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);factory.setConnectTimeout(3000);factory.setReadTimeout(5000);return new RestTemplate(factory);
}

直接依赖spring bean注解将其定义成单实例对象,其他地方直接属性注入后使用。

4. 性能分析

问题得到解决后,课余时间又对该部分代码做了一个粗略的定量分析,本机跑的数据,还是能比较清晰地得出结论。

private final String url = "xxxx.com/api";
private int loopCount = 400;
private int concurrentThread = 400;
private RestTemplate restTemplate;@BeforeTest
public void init() {final ConnectionKeepAliveStrategy myStrategy = (response, context) -> {return 5 * 1000;//设置一个链接的最大存活时间};PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);// 总连接数pollingConnectionManager.setMaxTotal(1000);// 同路由的并发数pollingConnectionManager.setDefaultMaxPerRoute(1000);HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(pollingConnectionManager).setKeepAliveStrategy(myStrategy).build();HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);factory.setConnectTimeout(3000);factory.setReadTimeout(5000);restTemplate = new RestTemplate(factory);
}
@Test
public void testHttp1() {long start = System.currentTimeMillis();ExecutorService executor = Executors.newFixedThreadPool(concurrentThread);for (int i =0 ; i< loopCount; i++){executor.submit(() -> {MemeberCardCodeRspDTO result = restTemplate.getForObject(url, MemeberCardCodeRspDTO.class);System.out.println(result != null ? result.getMsg() : "null");});}try {executor.shutdown();executor.awaitTermination(30, TimeUnit.MINUTES); // or longer.} catch (InterruptedException e) {e.printStackTrace();}long time = System.currentTimeMillis() - start;System.out.printf("Tasks1 took %d ms to run%n", time);
}@Test
public void testHttp2() {long start = System.currentTimeMillis();ExecutorService executor = Executors.newFixedThreadPool(concurrentThread);for (int i =0 ; i< loopCount; i++){executor.submit(() -> {RestTemplate restTemplate = new RestTemplate();MemeberCardCodeRspDTO result = restTemplate.getForObject(url, MemeberCardCodeRspDTO.class);ystem.out.println(result != null ? result.getMsg() : "null");});}try {executor.shutdown();executor.awaitTermination(30, TimeUnit.MINUTES); // or longer.} catch (InterruptedException e) {e.printStackTrace();}long time = System.currentTimeMillis() - start;System.out.printf("Tasks2 took %d ms to run%n", time);
}

调整threads数量,跑了6组数据,结果对比如下:
在这里插入图片描述
图中比较清晰的可以看出,优化过后的代码性能提升比较明显,且随着并发任务数增加,耗时波动不会太大。

5. 问题总结

第三方库提供的各种方便的类,简化了编码复杂度,方便了开发者。使用不恰当时,细微的编码可能埋藏着大的隐患。

精雕细琢,精益求精。

相关内容

热门资讯

实用的 PyYAML 使用技巧 AML 是一个被广泛使用的数据序列化和配置语言,作为一个开发者,总是不免...
C语言学习之路--指针篇 目录一、前言二、指针一、指针是什么1、指针的重要理解2、指针变量3、其他问题二、指针和指针类型1、指...
最牛,狂赚近70%! 最牛,狂... 【导读】前5月主动权益类基金平均净值增长率为2.43%,最牛业绩逼近70%中国基金报记者 方丽 曹雯...
电容笔和Apple penci... 跟Apple Pencil最主要不同,市场上大部分的电容笔都没有重力的压感࿰...
便携制氧机方案——PCBA/芯...   便携制氧机采用分子筛的吸附性能,通过物理原理,以无油压缩机为动力&#...
考研复试——离散数学 三年疫情都没有笔试,今年恢复,大概率会有笔试。 2023年3月19号&#...
分布式任务处理XXL-JOB 分布式任务处理XXL-JOB 什么是分布式任务调度 对一个视频的转码可以理解为一个任务的执行...
DirectX12(D3D12... 目录1、前言1.1、一些感慨1.2、运行效果展示1.3、示例简介1.4、示例操作说明1.5、本章内容...
小红书运营工具有哪些?新手运营...   很多人多多少少都会觉得小红书运营有一定的难度。但是其实在解决这些难题的时候,我们也...
Jetson NX 配置 py... Jetson NX 配置pytorch 参考文档 官方教程–还得是官方啊 参考博客1 参考博客...
c++ error:cross... 最近在写代码的时候,碰到了 crosses initialization of ......
XShell安装配置教程及云服... 目录一、 XShell的作用二、 下载XShell1.访问XShell官网,填写姓名和...
五年五任!道通科技任命90后董... 【高管动态】深圳市道通科技股份有限公司的董秘,也许是最不好干的岗位之一了。根据道通科技(600208...
C++初阶——前言 目录 1. 什么是C++ 2. C++的发展史 3. C+&...
每周股票复盘:西部矿业(601... 截至2025年5月30日收盘,西部矿业(601168)报收于15.8元,较上周的16.07元下跌1....
特朗普癫了?突然反咬中美贸易协... 知道他会反口,没想到这么快反口。不然为什么叫“疯王”呢?从昨晚到现在,特朗普又搞出三件大事:反咬中国...
DevOps是什么?DevOp... 目录专栏导读一、DevOps是什么?二、为什么会出现DevOps?1、容...
“石家庄富豪”要签50亿美元大... 频繁资产腾挪,收效如何? 作者 | 伍玥 编辑丨高岩 来源 | 野马财经 创新生物医药的出海热潮正在...
【零基础入门SpringBoo... 一、上手第一个程序 1、系统要求 此处以我自己使用的版本为例,在后期学习过程中遇到一...
马斯克黯然下课,临走前给特朗普... 黯然下课,马斯克要走了。5月底,马斯克正式宣布,即将卸任“政府效率部”部长职务,为其在特朗普政府的任...
Scala函数式编程 一、基本函数编程 在Scala 中函数式编程和面向对象编程完美融合在一起了 1 基础概念 1&#...
ChatGPT-4 前两天推出 ChatGPT-4,其创建者 OpenAI 展示了该机器人的增强功能——包...
UEFI 基础教程 (十三) ... 一、 修改UEFI UiApp源代码 修改 FrontPageStrings.uni 与 Front...
首进品牌超30%,餐饮选址新风... 总第4232期作者 |餐饮老板内参内参君逃离商场的餐饮人,正把店开到“高质价比商业体”“可能要撤店了...
94:二叉树的中序遍历 94:二叉树的中序遍历 总结 给定一个二叉树的根节点 root ,返...
03 - 初识Linux进程 ---- 整理自狄泰软件唐佐林老师课程 查看所有文章链接:(更新中&...
中年男人又一宝?新型护肝科技一... 凌晨一点的上海陆家嘴,写字楼里依然灯火通明。30岁互联网大厂产品经理张磊目不转睛盯着电脑屏幕,手机健...
零入门kubernetes网络... 《零入门kubernetes网络实战》视频专栏地址 https://www.ixigua.com/7...
“一部手机游景区”,带你玩转V... “一部手机游景区”已经不再是一个概念,现在各地纷纷大力发展VR智慧景区,...
Pytorch学习笔记--多G... 目录 1--前言 2--报错代码 3--解决方法 1--前言         最近在复现一篇 Pa...