返回

AWS ECS Spring Boot 访问 RDS MySQL 间歇性慢?排查指南

mysql

AWS ECS 上的 Spring Boot 应用访问 RDS MySQL 时快时慢?问题排查与解决指南

不少团队碰到过这种事儿:部署在 AWS ECS Fargate 上的 Spring Boot 应用,访问 RDS MySQL 数据库时,接口响应速度很不稳定,时快时慢。有时挺顺畅,有时就卡在那儿半天不动,浏览器开发者工具一看,请求卡在 "Waiting (TTFB)" 阶段老长。明明有时候跑得飞快,怎么就突然拉胯了呢?

咱先看看这个场景的基本信息:

  • 应用: Spring Boot
  • 部署: AWS ECS (Fargate 模式),配置 2 vCPU, 4GB RAM
  • 数据库: MySQL (跑在 Amazon RDS 上),实例规格 db.t3.medium (2 vCPU, 4GB RAM),网络带宽 2085 Mbps
  • 用户量: 注册用户 3000+

这问题让人头疼的地方在于它的间歇性发作。监控也看了,ECS 和 RDS 的 CPU、内存都没啥大毛病,也没有飙升。

问题出在哪儿?

咱们梳理下已知的现象和尝试过的操作:

  • 现象:
    • API 响应延迟,请求似乎在执行前排队了。
    • 浏览器 Network 面板显示请求卡在 "Waiting (TTFB)" 阶段,说明服务端处理慢了,或者连接建立慢了。
    • ECS 或 RDS 的 CPU/内存指标正常。
    • 问题不是一直有,随机出现。
  • 已尝试:
    • 翻了 AWS CloudWatch 的 CPU/内存/IO 指标,没发现异常尖峰。
    • 增加了 ECS 任务数量,想多扛点流量。
    • 检查了 MySQL 慢查询日志,没找到执行特别久的 SQL。
    • 通过 SHOW PROCESSLIST 确认了 RDS 的连接数在限制内,大概有 30 多个活跃连接。

根据这些线索,重点怀疑对象有这么几个:

  1. 数据库连接池出问题了? Spring Boot 默认用的 HikariCP,是不是池子里的连接不够用,或者获取连接太慢了?
  2. RDS 处理并发连接吃力? 虽然总连接数没超标,但 db.t3.medium 这个实例规格是不是在特定情况下处理 30 多个并发请求有点勉强?
  3. ECS 和 RDS 之间的网络传输抽风了? AWS 内部网络虽然通常很稳,但也架不住有特殊情况,比如配置问题或者偶发性网络抖动。

分析问题根源

要解决问题,得先定位到根子上。咱们挨个分析上面提到的疑点。

连接池是瓶颈吗?

Spring Boot 应用访问数据库,一般都会用连接池(默认是 HikariCP)。连接池就像个“连接储备库”,应用需要访问数据库时,不是每次都重新建立连接(这很耗时),而是从池子里借一个现成的连接,用完再还回去。

为什么会出问题?

如果某一瞬间,需要数据库连接的请求突然增多,超过了连接池里可用连接的数量,那后来的请求就得排队等着,直到有连接被释放回来。这就直接导致了请求处理延迟,反映到浏览器上就是漫长的 TTFB。

怎么查?

  1. 看 HikariCP 指标:

    • 如果你的 Spring Boot 应用集成了 Actuator,可以暴露 HikariCP 的监控端点 (/actuator/metrics/hikaricp.connections.*)。重点关注:
      • hikaricp.connections.active: 当前有多少连接正在被使用。
      • hikaricp.connections.idle: 池里有多少空闲连接。
      • hikaricp.connections.pending: 有多少线程在排队等待连接。如果这个值经常大于 0,那连接池很可能就是瓶颈了。
      • hikaricp.connections.max: 连接池最大连接数。
      • hikaricp.connections.min: 连接池最小连接数。
    • 可以通过 JMX 工具 (如 JConsole, VisualVM) 连接到你的 Spring Boot 应用进程,查看 HikariCP 的 MBean (com.zaxxer.hikari) 提供的实时数据。
  2. 看应用日志:

    • 在 HikariCP 配置里,可以适当调高日志级别(比如调到 DEBUG),观察连接获取和释放的日志。如果看到大量关于等待连接超时的警告或错误,那问题就比较明显了。
    • 注意:生产环境长时间开 DEBUG 日志可能会影响性能,用完记得调回去。

RDS 扛不住并发?

db.t3.medium 是个不错的入门级实例,但它属于 T 系列(Burstable performance instances),性能依赖于“CPU 积分”。

为什么会出问题?

  1. CPU 积分耗尽: T3 实例平时会积累 CPU 积分,当需要高性能时(比如并发请求增多),它会消耗积分来提升 CPU 性能。如果积分耗尽了,CPU 性能会被限制在一个较低的基准水平,导致数据库处理变慢,即使 CPU 使用率看起来可能不高(比如被限制在 20%)。
  2. 连接数上限: 虽然 SHOW PROCESSLIST 看到的连接数没超过 MySQL 的 max_connections 参数值,但不代表数据库内部处理这些连接就很轻松。max_connections 是个硬限制,但在此之前,过多的活跃连接同样会消耗数据库的 CPU、内存和 I/O 资源。db.t3.medium 的默认 max_connections 大概在几百个,但实际能高效处理的并发查询可能远低于这个数。
  3. 内存压力: 4GB 内存对于需要处理一定并发量和复杂查询的 MySQL 来说,不算特别宽裕。如果 innodb_buffer_pool_size 设置不当,或者查询导致大量临时表、排序操作,内存可能成为瓶颈。
  4. IOPS 限制: EBS 卷(RDS 存储)也有 IOPS 限制。虽然 db.t3.medium 关联的 GP2/GP3 卷性能不错,但在高并发读写下,也可能触及 IOPS 或吞吐量上限。

怎么查?

  1. AWS CloudWatch RDS 指标:

    • CPUUtilization: 查看 CPU 使用率。
    • CPUCreditUsage & CPUCreditBalance (针对 T 系列实例): 这两个非常关键! CPUCreditUsage 如果持续高于积分生成速率,CPUCreditBalance 就会下降。如果 CPUCreditBalance 趋近于零或为零,说明 CPU 性能被限制了。
    • DatabaseConnections: 再次确认连接数趋势。
    • ReadIOPS, WriteIOPS, ReadThroughput, WriteThroughput: 查看磁盘 I/O 是否达到瓶颈。
    • NetworkReceiveThroughput, NetworkTransmitThroughput: 查看网络流量。
    • FreeableMemory: 观察可用内存情况。
  2. RDS Performance Insights:

    • 这是一个非常强大的工具,必须用起来!它可以帮你看到数据库负载的来源,比如哪些 SQL 语句、哪些等待事件(Wait Events)消耗了最多的时间。
    • 重点关注 "Database load" 图表。如果 "CPU" 部分很高,说明 CPU 繁忙;如果 "IO" (如 wait/io/file) 部分很高,说明 I/O 是瓶颈;如果看到大量 wait/synch/mutexlock 相关的等待,可能是锁竞争激烈。如果 wait/io/socket/sql/client_connection 等待时间长,可能跟网络或客户端处理慢有关。

ECS 和 RDS 之间的网络有问题?

ECS Fargate 任务和 RDS 实例通常在同一个 VPC (Virtual Private Cloud) 内,但也可能因为配置不当或偶发问题导致网络延迟。

为什么会出问题?

  1. 安全组 (Security Groups) 配置: 配置错误或过于复杂的安全组规则可能导致数据包处理延迟,甚至丢包。比如,RDS 的安全组入站规则没有精确允许来自 ECS 任务安全组的流量,或者规则过多需要逐条匹配。
  2. 网络 ACL (Network Access Control Lists): NACL 是子网级别的防火墙,如果规则配置不当(特别是出站/入站规则不匹配),也可能导致连接问题。
  3. 子网路由: ECS 任务所在的子网和 RDS 实例所在的子网之间的路由是否正确?如果跨可用区 (AZ),是否存在额外的延迟?
  4. NAT Gateway 瓶颈 (如果使用): 如果 ECS 任务放在私有子网,需要通过 NAT Gateway 访问公网资源(比如第三方 API),并且数据库连接也走了这条路径(虽然通常直连更好),NAT Gateway 本身可能成为瓶颈(并发连接数限制、带宽限制)。但对于 VPC 内的 RDS 连接,一般不经过 NAT Gateway。
  5. DNS 解析延迟: 虽然少见,但 VPC 内的 DNS 解析偶尔也可能出现短暂延迟。

怎么查?

  1. 检查安全组和 NACL 配置:
    • 确保 RDS 实例的安全组入站规则允许来自 ECS Fargate 任务所使用的安全组在 MySQL 端口(默认 3306)上的 TCP 流量。规则越精确越好(只允许必要的源)。
    • 确保 ECS 任务的安全组出站规则允许到 RDS 端口的 TCP 流量。
    • 检查相关子网的 NACL 规则,确保入站和出站都允许 ECS 和 RDS 之间的通信。NACL 是无状态的,出入规则都要配对。
  2. 检查路由表: 确认 ECS 任务子网和 RDS 子网之间的路由是正确的,通常应该通过 VPC 的本地路由。
  3. VPC Flow Logs:
    • 给你的 VPC、子网或特定网络接口(ENI)启用 VPC Flow Logs,并将日志发送到 CloudWatch Logs 或 S3。
    • 分析 Flow Logs,查找从 ECS 任务 ENI 到 RDS 实例 ENI 的记录。关注 REJECT 状态的记录,这表示流量被安全组或 NACL 阻止了。关注 ACCEPT 记录的传输时间(虽然 Flow Logs 不直接提供延迟信息,但可以通过分析大量记录的模式判断是否有连接建立慢或丢包迹象)。
  4. 测试连接 (间接): 由于无法直接登录 Fargate 容器执行 pingtraceroute,可以尝试从同一子网或配置相似的 EC2 实例(如果方便创建的话)测试到 RDS Endpoint 的网络连通性和延迟。

动手解决:一步步优化

基于上面的分析,我们来制定一套解决策略。建议按顺序尝试。

方案一:优化 HikariCP 连接池配置

这是最容易调整也通常见效较快的方案。

原理: 合理配置连接池大小和超时时间,使其既能满足应用高峰期需求,又不会给数据库带来过大压力。

操作: 修改 application.propertiesapplication.yml 文件中的 HikariCP 配置。

spring:
  datasource:
    hikari:
      # 最大连接数:关键参数!需要根据 RDS 能力和应用实例数计算。
      # 一个初步估算公式:(RDS 可接受的总并发连接数 / 应用实例数) * 安全系数(略小于1)
      # db.t3.medium 的 max_connections 默认值可能几百,但实际高效并发可能就几十。
      # 假设我们有 3 个 ECS 任务,RDS 极限处理 60 个活跃查询。那每个实例大概 15-20 个连接。先保守点试试。
      maximum-pool-size: 15 
      
      # 最小空闲连接数:建议设置成和 maximum-pool-size 一样大,避免高峰期频繁创建连接。
      # 但如果应用负载波动很大,可以设小一点节约资源。
      minimum-idle: 10
      
      # 连接在池中最大空闲时间 (毫秒):超过这个时间没被使用的连接会被回收 (直到 minimum-idle)。
      # 设短一点可以更快释放不活跃连接,但可能增加高峰期创建连接的开销。30分钟(1800000)通常OK。
      idle-timeout: 1800000 
      
      # 获取连接的超时时间 (毫秒):客户端等连接的最长时间。
      # 如果经常因为等连接超时,这里适当调长一点可以缓解错误,但会增加请求的潜在等待时间。
      # 默认 30000ms (30秒)。如果 TTFB 长是因为等连接,说明池子小了或者 DB 慢了。先不急着调大它,优先解决池子大小和DB性能。
      connection-timeout: 30000 
      
      # 连接最大存活时间 (毫秒):一个连接在池里能活多久,到期会被回收重建。
      # 防止连接因为长时间使用变得不稳定(比如网络问题累积)。比数据库的 wait_timeout 短一点比较好。
      # 默认 1800000ms (30分钟)。
      max-lifetime: 1800000
      
      # (可选) 连接测试查询:确保从池里拿到的连接是有效的。
      # connection-test-query: SELECT 1
      
      # (可选) 泄露检测阈值 (毫秒):帮助发现连接未正确关闭导致的泄露。
      # leak-detection-threshold: 60000 # 例如 60 秒

解释:

  • maximum-pool-size 是核心。太小,应用排队;太大,压垮数据库。需要结合 RDS 的 DatabaseConnections、CPU 使用率、CPUCreditBalance 以及 Performance Insights 的负载来综合判断和调整。
  • minimum-idle 设置得接近 maximum-pool-size,可以在负载上升时更快响应,代价是平时维持更多连接。
  • connection-timeout 设太短容易误报超时,设太长会让客户端等太久。30秒是个常见的中间值。如果超时,根源在别处。

进阶技巧:

  • 暴露指标: 确保你的 Spring Boot Actuator 开启了 metrics 端点,并且可以访问到 /actuator/metrics/hikaricp.*。持续监控这些指标。
  • 调整测试: 修改配置后,部署并观察一段时间,特别是业务高峰期。看看 TTFB 是否改善,HikariCP pending 连接数是否下降,RDS 指标是否稳定。需要反复试几次才能找到最佳值。

方案二:检查和调整 RDS 参数及性能

如果连接池调整后问题依旧,或者 CloudWatch 显示 RDS 有压力迹象,那就得深入看看 RDS 了。

原理: 确保 RDS 实例有足够的资源处理当前负载,并且数据库内部参数设置合理。

操作步骤:

  1. 密切关注 CloudWatch RDS 指标:

    • CPUCreditBalance (针对 T3 实例): 这是重中之重!如果这个值很低或者经常为零,说明你受限于 T3 的基准性能了。这是性能不稳定的常见元凶。
    • CPUUtilization: 持续高位(如 > 80%)也表明 CPU 瓶颈。
    • DatabaseConnections: 和 HikariCP 的 maximum-pool-size * 应用实例数 对比,看看是否接近。
    • IOPS/Throughput 指标: 看看是否打满了磁盘 I/O。
    • FreeableMemory: 如果持续很低,可能有内存压力。
  2. 启用并使用 RDS Performance Insights:

    • 必须启用! 对于性能问题诊断价值极大。
    • 在 Performance Insights 控制面板,选择时间范围(包含问题发生的时间段)。
    • 查看 "Database Load" 图表,识别主要瓶颈是 CPU、I/O 还是等待事件。
    • 查看 "Top SQL",找出消耗资源最多的查询语句。可能需要优化这些 SQL(加索引、改写逻辑等)。
    • 查看 "Top Waits",了解数据库内部在等什么。是等 CPU?等 I/O?等锁?
  3. 检查 RDS 参数组 (Parameter Group):

    • 确认 max_connections 设置。虽然默认值通常够用,但确认一下没坏处。注意,不是越大越好,要匹配实例的处理能力。
    • innodb_buffer_pool_size: 通常 RDS 会根据实例内存自动设置一个合理值(约占总内存 75%),一般不需要动。但如果内存压力大,可以检查下。
    • wait_timeout: MySQL 服务器关闭空闲连接的时间。应确保 HikariCP 的 max-lifetime 比它短。

解释:

  • CPUCreditBalance 低是 T 系列实例性能不稳的“特色”。持续低迷意味着你需要升级到 M 或 R 系列(固定性能实例),或者降低负载。
  • Performance Insights 能直接告诉你数据库“疼”在哪。是某个烂 SQL 拖慢了大家?还是 I/O 跟不上?或是锁竞争太激烈?

安全建议:

  • 使用 IAM 数据库身份验证,而不是密码,更安全。
  • 确保 RDS 实例放在私有子网。
  • 开启 RDS 数据传输加密 (SSL/TLS)。
  • 定期备份。

进阶技巧:

  • 考虑只读副本 (Read Replicas): 如果你的应用读多写少,可以创建一个或多个只读副本,将读流量导向副本,分担主库压力。Spring Boot 可以通过配置多个数据源或使用特定库(如 Spring Cloud AWS JDBC)来做读写分离。
  • 优化查询和索引: Performance Insights 找到的慢 SQL,必须优化。加索引是最常见的手段。检查 EXPLAIN 计划,看是否走了合适的索引,有没有全表扫描。

方案三:排查网络链路

网络问题虽然概率相对低,但一旦发生就很隐蔽。

原理: 找到并排除 ECS 和 RDS 之间的网络配置错误或潜在瓶颈。

操作步骤:

  1. 仔细核对安全组 (Security Group):

    • RDS 安全组: 入站规则必须明确允许来自 ECS 任务安全组(或者 ECS 任务所在子网的 IP 段)的 TCP 端口 3306 流量。出站规则通常默认允许所有,但也检查下。
    • ECS 任务安全组: 出站规则必须允许到 RDS 安全组(或 RDS 实例私有 IP)的 TCP 端口 3306 流量。入站规则取决于应用是否需要接收外部流量。
    • 规则要精简: 避免使用 0.0.0.0/0,尽量精确指定源/目标。
  2. 检查网络 ACL (NACL):

    • 确认 ECS 任务子网和 RDS 子网关联的 NACL。
    • 入站规则要允许从对方 IP/端口来的流量。
    • 出站规则要允许去往对方 IP/端口的流量。
    • 注意 NACL 是无状态的,入和出都要配对。通常保持默认允许所有比较省事,除非有特定安全需求。
  3. 检查子网和路由表:

    • 确认 ECS 任务和 RDS 在同一个 VPC 内。
    • 确认它们所在的子网(可能跨 AZ)之间的路由是通的(通过 VPC 的本地路由)。
    • 检查是否有意外的路由指向了 NAT Gateway 或其他可能产生瓶颈的地方。
  4. 分析 VPC Flow Logs:

    • 如果怀疑网络层有问题,开启 Flow Logs 是个好办法。
    • 重点筛查 ECS ENI IP 和 RDS IP 之间的流量记录。
    • 查找 REJECT 记录,分析是哪个规则(安全组还是 NACL)拒绝了流量。
    • 观察 ACCEPT 记录的模式,虽然不能直接看延迟,但如果发现大量短时间内重复的连接尝试或奇怪的数据包大小模式,也可能暗示问题。

解释:

  • 安全组和 NACL 是最常见的网络配置问题来源。一个配置疏忽就可能导致连接超时或延迟。
  • VPC Flow Logs 是诊断这类问题的有力证据。

安全建议:

  • 坚持最小权限原则配置安全组和 NACL。
  • 将 RDS 放在私有子网,不暴露公网 IP。

进阶技巧:

  • 考虑 VPC Endpoints: 为 RDS 创建一个 VPC Endpoint (接口类型)。这样,ECS 任务访问 RDS 的流量会通过 AWS 的私有网络,不经过公网或 NAT Gateway,可能更稳定、延迟更低。

方案四:升级 RDS 实例规格

如果以上优化都做了,但监控(特别是 CPUCreditBalance 持续低、CPU 长期高负荷、或 Performance Insights 显示资源瓶颈)仍然表明 RDS 就是扛不住,那只能考虑加钱升级了。

原理: 提供更多的 CPU、内存、I/O 或网络带宽来匹配应用负载。

何时考虑:

  • db.t3.medium 的 CPU 积分 (CPUCreditBalance) 经常耗尽,导致性能下降。
  • CPU 使用率持续(比如超过 80%)处于高位,即使优化了查询。
  • Performance Insights 明确显示 CPU、内存或 I/O 是持续瓶颈。
  • 应用需要的并发连接数确实超出了 db.t3.medium 配合优化后能舒适处理的范围。

操作步骤:

  1. 在 RDS 控制台,选择要修改的实例。
  2. 点击“修改”。
  3. 在“实例配置”下,选择一个更强的实例规格。建议从 T 系列迁移到 M(通用型)或 R(内存优化型)系列,比如 db.m5.largedb.r5.large。它们提供固定的 CPU 性能,不受积分限制。
  4. 根据需要调整存储等其他设置。
  5. 选择是立即应用更改还是在下一个维护时段应用。注意,修改实例规格可能会导致短暂的服务中断(即使是 Multi-AZ 配置,也会有 DNS 切换的延迟)。

解释:

  • 升级是解决资源不足的直接办法,但成本也更高。
  • 务必先排除应用代码、连接池配置、查询效率等问题,再考虑升级。否则只是用更高的成本掩盖了底层问题。

安全建议:

  • 在业务低峰期进行升级操作。
  • 确保有可靠的备份和回滚计划。

诊断思路总结

碰到这种间歇性慢的问题,排查思路建议如下:

  1. 先看应用层: 从离应用最近的地方开始,检查和优化 HikariCP 连接池配置。这是最快能看到效果也最容易调整的地方。同时,确保 Spring Boot Actuator 的 HikariCP 指标可见,并监控起来。
  2. 再深入数据库: 如果连接池优化后改善不大,或者有证据(CloudWatch 指标、CPUCreditBalance)指向 RDS,那就启用 Performance Insights。分析数据库负载、等待事件、慢 SQL。看看是不是 RDS 资源不足(特别是 T3 积分)或者有待优化的查询。
  3. 然后排查网络: 如果应用层和数据库层都没明显问题,或者有明确的网络错误(如连接超时日志),再开始检查安全组、NACL、路由和 VPC Flow Logs。
  4. 最后考虑硬件: 只有当前三步都充分优化,且证据确凿表明 RDS 实例本身资源不足以支撑负载时,才考虑升级 RDS 实例规格。

整个过程中,持续监控是关键。CloudWatch 指标、RDS Performance Insights、应用日志、HikariCP 指标,都是你定位和解决问题的眼睛。祝你好运!