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的请求关闭时才会关闭数据库连接,而此时调用A的请求没有关闭,导致数据库连接没有关闭。

这里主要分析第1个问题:为什么请求A的连接出现504 Timeout.

AbstractConnPool

通过日志看到A在调用B时出现阻塞,直到timeout,打印出线程堆栈查看:

RestTemplate使用不当引发的问题分析


可以看到线程阻塞在AbstractConnPool类getPoolEntryBlocking方法中。


getPoolEntryBlocking方法用于获取连接,主要有三步:(1). 检查可用连接集合中是否有可重复使用的连接,如果有则获取连接,返回. (2). 创建新连接,注意同时需要检查可用连接集合(分为每个route的和全局的)是否有多余的连接资源,如果有,则需要释放。(3). 加入队列等待;

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++) { final E lastUsed = pool.getLastUsed(); if (lastUsed == null) { break; } lastUsed.close(); this.available.remove(lastUsed); pool.remove(lastUsed); } } if (pool.getAllocatedCount() < maxPerRoute) { //比较总的可用连接数量与总的可用连接集合大小,释放多余的连接资源 final int totalUsed = this.leased.size(); final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0); if (freeCapacity > 0) { final int totalAvailable = this.available.size(); if (totalAvailable > freeCapacity - 1) { if (!this.available.isEmpty()) { final E lastUsed = this.available.removeLast(); lastUsed.close(); final RouteSpecificPool<T, C, E> otherpool = getPool(lastUsed.getRoute()); otherpool.remove(lastUsed); } } // 真正创建连接的地方 final C conn = this.connFactory.create(route); entry = pool.add(conn); this.leased.add(entry); return entry; } } //如果已经超过了每个route的连接池大小,则加入队列等待有可用连接时被唤醒或直到某个终止时间 boolean success = false; try { if (future.isCancelled()) { throw new InterruptedException("Operation interrupted"); } pool.queue(future); this.pending.add(future); if (deadline != null) { success = this.condition.awaitUntil(deadline); } else { this.condition.await(); success = true; } if (future.isCancelled()) { throw new InterruptedException("Operation interrupted"); } } finally { //如果到了终止时间或有被唤醒时,则出队,加入下次循环 pool.unqueue(future); this.pending.remove(future); } // 处理异常唤醒和超时情况 if (!success && (deadline != null && deadline.getTime() <= System.currentTimeMillis())) { break; } } throw new TimeoutException("Timeout waiting for connection"); } finally { this.lock.unlock(); } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdyzs.html