RestTemplate使用不当引发的线上问题
背景
- 系统: SpringBoot开发的Web应用;
- ORM: JPA(Hibernate)
- 接口功能简述: 根据实体类ID到数据库中查询实体信息,然后使用RestTemplate调用外部系统接口获取数据。
问题现象
- 浏览器页面有时报504 GateWay Timeout错误,刷新多次后,则总是timeout
- 数据库连接池报连接耗尽异常
- 调用外部系统时有时报502 Bad GateWay错误
分析过程
为便于描述将本系统称为A,外部系统称为B。
这三个问题环环相扣,导火索是第3个问题,然后导致第2个问题,最后导致出现第3个问题;
原因简述: 第3个问题是由于Nginx负载下没有挂系统B,导致本系统在请求外部系统时报502错误,而A没有正确处理异常,导致http请求无法正常关闭,而springboot默认打开openInView, 导致调用A的请求关闭时才会关闭数据库连接。
这里主要分析第1个问题:为什么请求A的连接出现504 Timeout.
AbstractConnPool
通过日志看到A在调用B时出现阻塞,直到timeout,打印出线程堆栈查看:

可以看到线程阻塞在AbstractConnPool类getPoolEntryBlocking方法中。
private E getPoolEntryBlocking(
final T route, final Object state,
final long timeout, final TimeUnit timeUnit,
final Future<E> future) throws IOException, InterruptedException, TimeoutException {
Date deadline = null;
if (timeout > 0) {
deadline = new Date (System.currentTimeMillis() + timeUnit.toMillis(timeout));
}
this.lock.lock();
try {
//根据route获取route对应的连接池
final RouteSpecificPool<T, C, E> pool = getPool(route);
E entry;
for (;;) {
Asserts.check(!this.isShutDown, "Connection pool shut down");
for (;;) {
//获取可用的连接
entry = pool.getFree(state);
if (entry == null) {
break;
}
// 判断连接是否过期,如过期则关闭并从可用连接集合中删除
if (entry.isExpired(System.currentTimeMillis())) {
entry.close();
}
if (entry.isClosed()) {
this.available.remove(entry);
pool.free(entry, false);
} else {
break;
}
}
// 如果从连接池中获取到可用连接,更新可用连接和待释放连接集合
if (entry != null) {
this.available.remove(entry);
this.leased.add(entry);
onReuse(entry);
return entry;
}
// 如果没有可用连接,则创建新连接
final int maxPerRoute = getMax(route);
// 创建新连接之前,检查是否超过每个route连接池大小,如果超过,则删除可用连接集合相应数量的连接(从总的可用连接集合和每个route的可用连接集合中删除)
final int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute);
if (excess > 0) {
for (int i = 0; i < excess; i++