澄迈县网站建设_网站建设公司_响应式网站_seo优化
2026/1/5 10:07:29 网站建设 项目流程

问题背景:一

个接口方法里,同步调用多个外部 HTTP 服务,在高并发下 JVM 被打爆

业务中一些接口需要接口需要调用多个外部服务,如果在高并发场景下,容易出现以下问题:

  • 堆内存爆炸:大量请求导致内存溢出
  • 连接数耗尽:数据库连接池、HTTP连接池被耗尽
  • 响应超时:外部服务响应慢,导致请求堆积
  • 系统崩溃:资源耗尽导致服务不可用
  • GC风暴:  大量请求导致创建大量http连接对象,接收返回值创建大量JSONObject、Response等常见临时对象,导致年轻代迅速填满,频繁minor GC(每秒多次),GC时间>业务执行时间,引起GC风暴。

典型特征

  • 单个接口调用多个外部服务
  • 每个请求需要进行多次数据库查询
  • 外部服务响应时间不确定
  • 高并发访问(QPS > 100)

”一个看起来正常的接口“,请求量不大的情况下正常运行,访问并发量一上来, jvm炸了。

第一反应:是不是响应体太大?

排查后发现:

  • HTTP 响应体并非很大,只有几KB

  • 不存在“大 JSON 导致内存暴涨”的问题

并非外部调用的响应体过。

根因分析

不是“数据大”,而是“并发 + 阻塞”
当前场景下,一个用户请求 = N 个外部 HTTP 请求

  • 假设:
  • QPS = 300
  • 每个请求调用 3 个外部服务
  • 外部平均耗时 200ms
  • 那么瞬间 JVM 内部会出现:
  • 300 × 3 = 900 个阻塞中的 HTTP 调用

这些调用

  • 占用 Tomcat 工作线程
  • 占用 Socket
  • 占用堆内对象(Request / Response / JSON)
  • 占用 CPU 调度

堆爆不是因为“单个请求大”,而是“同时活着的请求太多”。

1. 内存问题分析

问题表现

java.lang.OutOfMemoryError: Java heap space

根本原因

  • HTTP连接未复用:每次请求都创建新的HTTP连接
  • 连接未及时释放:连接泄漏导致内存占用持续增长
  • 大量对象创建:每个请求创建大量临时对象(JSONObject、List等)
  • 线程堆积:同步阻塞调用导致大量线程等待

内存占用计算

单次请求内存占用 ≈  HTTP连接对象 (50KB) × 连接数 + JSON对象 (10KB) × 对象数 + 线程栈 (1MB) × 线程数 + 其他临时对象 (20KB)
1000并发 ≈ 1000 × (50KB + 10KB × 5 + 20KB) = 120MB
实际可能更高(连接泄漏、对象未释放)

2. 连接池问题分析

问题表现

java.sql.SQLNonTransientConnectionException: No operations allowed after connection closed
HikariPool-1 - Connection is not available

根本原因

  • 连接池配置不当:连接池大小与实际需求不匹配
  • 连接泄漏:连接未正确关闭
  • 连接超时:网络慢导致连接超时
  • 无连接复用:每次都创建新连接

连接数计算

无连接池: 1000并发 × 每个请求3个外部服务 = 3000个连接有连接池(50个连接): 1000并发 → 50个连接复用 = 50个连接

3. 并发控制问题分析

问题表现

  • 系统响应变慢
  • 资源耗尽
  • 服务不可用

根本原因

  • 无并发限制:所有请求同时执行
  • 资源竞争:多个请求竞争同一资源
  • 级联故障:一个服务故障影响整个系统

原因梳理

核心问题总结

问题类型具体表现影响程度优先级
HTTP连接未复用 每次请求创建新连接 ⭐⭐⭐⭐⭐ P0
无并发控制 所有请求同时执行 ⭐⭐⭐⭐⭐ P0
连接泄漏 连接未正确关闭 ⭐⭐⭐⭐ P1
超时控制缺失 请求无限等待 ⭐⭐⭐⭐ P1
无重试机制 网络抖动导致失败 ⭐⭐⭐ P2

问题链路图

高并发请求↓
无并发控制(Semaphore)↓
大量线程同时执行↓
每个线程创建新的HTTP连接(无连接池)↓
连接数爆炸(1000并发 × 3个服务 = 3000连接)↓
内存占用激增↓
堆内存溢出(OOM)↓
系统崩溃

解决方案

方案一:HTTP连接池优化

使用连接池复用HTTP连接,避免每次请求都创建新连接。

 1 @Configuration
 2 public class HttpClientConfig {
 3     @Bean
 4     public OkHttpClient okHttpClient() {
 5         return new OkHttpClient.Builder()
 6                 .connectionPool(new ConnectionPool(50, 5, TimeUnit.MINUTES))
 7                 .connectTimeout(10, TimeUnit.SECONDS)
 8                 .readTimeout(30, TimeUnit.SECONDS)
 9                 .writeTimeout(30, TimeUnit.SECONDS)
10                 .retryOnConnectionFailure(true)
11                 .build();
12     }
13 }
  • 连接数减少:从3000个减少到50个(减少98%)
  • 内存占用降低:减少连接对象创建
  • 性能提升:连接复用,减少握手时间

方案二:Semaphore并发控制

使用信号量限制同时执行的请求数,保护系统资源。

private static final Semaphore querySemaphore = new Semaphore(50, true);
private static final int SEMAPHORE_TIMEOUT_SECONDS = 5;public Object query(HttpServletRequest request) {boolean acquired = false;try {acquired = queryMSemaphore.tryAcquire(SEMAPHORE_TIMEOUT_SECONDS, TimeUnit.SECONDS);if (!acquired) {// 返回限流错误return ResFlag.failureServer(dataMap);}// 业务逻辑} finally {if (acquired) {querySemaphore.release();}}
}
  • 资源保护:限制同时执行的请求数
  • 防止雪崩:避免系统资源耗尽
  • 优雅降级:超时直接拒绝,不阻塞

方案三:超时控制

设置合理的超时时间,防止请求无限等待。

实现方式

.connectTimeout(10, TimeUnit.SECONDS)   // 连接超时
.readTimeout(30, TimeUnit.SECONDS)        // 读取超时
.writeTimeout(30, TimeUnit.SECONDS)       // 写入超时
  • 防止阻塞:超时后立即释放资源
  • 快速失败:避免长时间等待
  • 资源释放:及时释放连接和线程

方案四:资源正确释放

使用try-with-resources确保资源正确关闭。

实现方式

try (Response response = okHttpClient.newCall(request).execute()) {// 处理响应if (response.isSuccessful() && response.body() != null) {String body = response.body().string();// ...
    }
} // 自动关闭Response,释放连接
  • 防止泄漏:确保连接正确关闭
  • 内存回收:及时释放资源
  • 连接复用:连接返回连接池

优化原则

连接池配置原则

连接池大小 = min(数据库连接池大小 / 每个请求平均占用连接数,HTTP连接池大小 / 每个请求平均占用连接数, 服务器可用内存 / 每个连接内存占用)

// 中小型应用
ConnectionPool(20-50, 5, TimeUnit.MINUTES)// 大型应用
ConnectionPool(50-100, 5, TimeUnit.MINUTES)// 超大型应用
ConnectionPool(100-200, 5, TimeUnit.MINUTES)

2. Semaphore并发数计算

Semaphore数量 = min(数据库连接池大小 / 每个请求平均占用连接数,HTTP连接池大小 / 每个请求平均占用连接数,服务器CPU核心数 × 2,服务器可用内存 / 每个请求内存占用)

保守策略:30-50(资源有限、外部服务不稳定)

平衡策略:50-80(资源充足、需要更高吞吐量)

激进策略:80-100(资源非常充足、数据库连接池很大)

3. 超时时间配置

// 内网服务
connectTimeout: 3-5秒
readTimeout: 10-15秒// 外网服务
connectTimeout: 10-15秒
readTimeout: 30-60秒// 慢速服务
connectTimeout: 30秒
readTimeout: 60-120秒

4. 错误处理原则

必处理

  • ✅ 连接异常
  • ✅ 超时异常
  • ✅ 资源释放(finally块)
  • ✅ 信号量释放(finally块)

注意事项

  1. Semaphore数量:需要根据实际资源情况调整
  2. 连接池大小:需要根据并发量调整
  3. 超时时间:需要根据外部服务响应时间调整
  4. 资源释放:必须确保在finally块中释放
  5. 监控告警:需要监控关键指标,及时发现问题

 

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询