AWS ECS Spring Boot 访问 RDS MySQL 间歇性慢?排查指南
2025-03-26 20:22:06
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 多个活跃连接。
根据这些线索,重点怀疑对象有这么几个:
- 数据库连接池出问题了? Spring Boot 默认用的 HikariCP,是不是池子里的连接不够用,或者获取连接太慢了?
- RDS 处理并发连接吃力? 虽然总连接数没超标,但
db.t3.medium
这个实例规格是不是在特定情况下处理 30 多个并发请求有点勉强? - ECS 和 RDS 之间的网络传输抽风了? AWS 内部网络虽然通常很稳,但也架不住有特殊情况,比如配置问题或者偶发性网络抖动。
分析问题根源
要解决问题,得先定位到根子上。咱们挨个分析上面提到的疑点。
连接池是瓶颈吗?
Spring Boot 应用访问数据库,一般都会用连接池(默认是 HikariCP)。连接池就像个“连接储备库”,应用需要访问数据库时,不是每次都重新建立连接(这很耗时),而是从池子里借一个现成的连接,用完再还回去。
为什么会出问题?
如果某一瞬间,需要数据库连接的请求突然增多,超过了连接池里可用连接的数量,那后来的请求就得排队等着,直到有连接被释放回来。这就直接导致了请求处理延迟,反映到浏览器上就是漫长的 TTFB。
怎么查?
-
看 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
) 提供的实时数据。
- 如果你的 Spring Boot 应用集成了 Actuator,可以暴露 HikariCP 的监控端点 (
-
看应用日志:
- 在 HikariCP 配置里,可以适当调高日志级别(比如调到 DEBUG),观察连接获取和释放的日志。如果看到大量关于等待连接超时的警告或错误,那问题就比较明显了。
- 注意:生产环境长时间开 DEBUG 日志可能会影响性能,用完记得调回去。
RDS 扛不住并发?
db.t3.medium
是个不错的入门级实例,但它属于 T 系列(Burstable performance instances),性能依赖于“CPU 积分”。
为什么会出问题?
- CPU 积分耗尽: T3 实例平时会积累 CPU 积分,当需要高性能时(比如并发请求增多),它会消耗积分来提升 CPU 性能。如果积分耗尽了,CPU 性能会被限制在一个较低的基准水平,导致数据库处理变慢,即使 CPU 使用率看起来可能不高(比如被限制在 20%)。
- 连接数上限: 虽然
SHOW PROCESSLIST
看到的连接数没超过 MySQL 的max_connections
参数值,但不代表数据库内部处理这些连接就很轻松。max_connections
是个硬限制,但在此之前,过多的活跃连接同样会消耗数据库的 CPU、内存和 I/O 资源。db.t3.medium
的默认max_connections
大概在几百个,但实际能高效处理的并发查询可能远低于这个数。 - 内存压力: 4GB 内存对于需要处理一定并发量和复杂查询的 MySQL 来说,不算特别宽裕。如果
innodb_buffer_pool_size
设置不当,或者查询导致大量临时表、排序操作,内存可能成为瓶颈。 - IOPS 限制: EBS 卷(RDS 存储)也有 IOPS 限制。虽然
db.t3.medium
关联的 GP2/GP3 卷性能不错,但在高并发读写下,也可能触及 IOPS 或吞吐量上限。
怎么查?
-
AWS CloudWatch RDS 指标:
CPUUtilization
: 查看 CPU 使用率。CPUCreditUsage
&CPUCreditBalance
(针对 T 系列实例): 这两个非常关键!CPUCreditUsage
如果持续高于积分生成速率,CPUCreditBalance
就会下降。如果CPUCreditBalance
趋近于零或为零,说明 CPU 性能被限制了。DatabaseConnections
: 再次确认连接数趋势。ReadIOPS
,WriteIOPS
,ReadThroughput
,WriteThroughput
: 查看磁盘 I/O 是否达到瓶颈。NetworkReceiveThroughput
,NetworkTransmitThroughput
: 查看网络流量。FreeableMemory
: 观察可用内存情况。
-
RDS Performance Insights:
- 这是一个非常强大的工具,必须用起来!它可以帮你看到数据库负载的来源,比如哪些 SQL 语句、哪些等待事件(Wait Events)消耗了最多的时间。
- 重点关注 "Database load" 图表。如果 "CPU" 部分很高,说明 CPU 繁忙;如果 "IO" (如
wait/io/file
) 部分很高,说明 I/O 是瓶颈;如果看到大量wait/synch/mutex
或lock
相关的等待,可能是锁竞争激烈。如果wait/io/socket/sql/client_connection
等待时间长,可能跟网络或客户端处理慢有关。
ECS 和 RDS 之间的网络有问题?
ECS Fargate 任务和 RDS 实例通常在同一个 VPC (Virtual Private Cloud) 内,但也可能因为配置不当或偶发问题导致网络延迟。
为什么会出问题?
- 安全组 (Security Groups) 配置: 配置错误或过于复杂的安全组规则可能导致数据包处理延迟,甚至丢包。比如,RDS 的安全组入站规则没有精确允许来自 ECS 任务安全组的流量,或者规则过多需要逐条匹配。
- 网络 ACL (Network Access Control Lists): NACL 是子网级别的防火墙,如果规则配置不当(特别是出站/入站规则不匹配),也可能导致连接问题。
- 子网路由: ECS 任务所在的子网和 RDS 实例所在的子网之间的路由是否正确?如果跨可用区 (AZ),是否存在额外的延迟?
- NAT Gateway 瓶颈 (如果使用): 如果 ECS 任务放在私有子网,需要通过 NAT Gateway 访问公网资源(比如第三方 API),并且数据库连接也走了这条路径(虽然通常直连更好),NAT Gateway 本身可能成为瓶颈(并发连接数限制、带宽限制)。但对于 VPC 内的 RDS 连接,一般不经过 NAT Gateway。
- DNS 解析延迟: 虽然少见,但 VPC 内的 DNS 解析偶尔也可能出现短暂延迟。
怎么查?
- 检查安全组和 NACL 配置:
- 确保 RDS 实例的安全组入站规则允许来自 ECS Fargate 任务所使用的安全组在 MySQL 端口(默认 3306)上的 TCP 流量。规则越精确越好(只允许必要的源)。
- 确保 ECS 任务的安全组出站规则允许到 RDS 端口的 TCP 流量。
- 检查相关子网的 NACL 规则,确保入站和出站都允许 ECS 和 RDS 之间的通信。NACL 是无状态的,出入规则都要配对。
- 检查路由表: 确认 ECS 任务子网和 RDS 子网之间的路由是正确的,通常应该通过 VPC 的本地路由。
- VPC Flow Logs:
- 给你的 VPC、子网或特定网络接口(ENI)启用 VPC Flow Logs,并将日志发送到 CloudWatch Logs 或 S3。
- 分析 Flow Logs,查找从 ECS 任务 ENI 到 RDS 实例 ENI 的记录。关注
REJECT
状态的记录,这表示流量被安全组或 NACL 阻止了。关注ACCEPT
记录的传输时间(虽然 Flow Logs 不直接提供延迟信息,但可以通过分析大量记录的模式判断是否有连接建立慢或丢包迹象)。
- 测试连接 (间接): 由于无法直接登录 Fargate 容器执行
ping
或traceroute
,可以尝试从同一子网或配置相似的 EC2 实例(如果方便创建的话)测试到 RDS Endpoint 的网络连通性和延迟。
动手解决:一步步优化
基于上面的分析,我们来制定一套解决策略。建议按顺序尝试。
方案一:优化 HikariCP 连接池配置
这是最容易调整也通常见效较快的方案。
原理: 合理配置连接池大小和超时时间,使其既能满足应用高峰期需求,又不会给数据库带来过大压力。
操作: 修改 application.properties
或 application.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 实例有足够的资源处理当前负载,并且数据库内部参数设置合理。
操作步骤:
-
密切关注 CloudWatch RDS 指标:
CPUCreditBalance
(针对 T3 实例): 这是重中之重!如果这个值很低或者经常为零,说明你受限于 T3 的基准性能了。这是性能不稳定的常见元凶。CPUUtilization
: 持续高位(如 > 80%)也表明 CPU 瓶颈。DatabaseConnections
: 和 HikariCP 的maximum-pool-size
* 应用实例数 对比,看看是否接近。- IOPS/Throughput 指标: 看看是否打满了磁盘 I/O。
FreeableMemory
: 如果持续很低,可能有内存压力。
-
启用并使用 RDS Performance Insights:
- 必须启用! 对于性能问题诊断价值极大。
- 在 Performance Insights 控制面板,选择时间范围(包含问题发生的时间段)。
- 查看 "Database Load" 图表,识别主要瓶颈是 CPU、I/O 还是等待事件。
- 查看 "Top SQL",找出消耗资源最多的查询语句。可能需要优化这些 SQL(加索引、改写逻辑等)。
- 查看 "Top Waits",了解数据库内部在等什么。是等 CPU?等 I/O?等锁?
-
检查 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 之间的网络配置错误或潜在瓶颈。
操作步骤:
-
仔细核对安全组 (Security Group):
- RDS 安全组: 入站规则必须明确允许来自 ECS 任务安全组(或者 ECS 任务所在子网的 IP 段)的 TCP 端口 3306 流量。出站规则通常默认允许所有,但也检查下。
- ECS 任务安全组: 出站规则必须允许到 RDS 安全组(或 RDS 实例私有 IP)的 TCP 端口 3306 流量。入站规则取决于应用是否需要接收外部流量。
- 规则要精简: 避免使用
0.0.0.0/0
,尽量精确指定源/目标。
-
检查网络 ACL (NACL):
- 确认 ECS 任务子网和 RDS 子网关联的 NACL。
- 入站规则要允许从对方 IP/端口来的流量。
- 出站规则要允许去往对方 IP/端口的流量。
- 注意 NACL 是无状态的,入和出都要配对。通常保持默认允许所有比较省事,除非有特定安全需求。
-
检查子网和路由表:
- 确认 ECS 任务和 RDS 在同一个 VPC 内。
- 确认它们所在的子网(可能跨 AZ)之间的路由是通的(通过 VPC 的本地路由)。
- 检查是否有意外的路由指向了 NAT Gateway 或其他可能产生瓶颈的地方。
-
分析 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
配合优化后能舒适处理的范围。
操作步骤:
- 在 RDS 控制台,选择要修改的实例。
- 点击“修改”。
- 在“实例配置”下,选择一个更强的实例规格。建议从 T 系列迁移到 M(通用型)或 R(内存优化型)系列,比如
db.m5.large
或db.r5.large
。它们提供固定的 CPU 性能,不受积分限制。 - 根据需要调整存储等其他设置。
- 选择是立即应用更改还是在下一个维护时段应用。注意,修改实例规格可能会导致短暂的服务中断(即使是 Multi-AZ 配置,也会有 DNS 切换的延迟)。
解释:
- 升级是解决资源不足的直接办法,但成本也更高。
- 务必先排除应用代码、连接池配置、查询效率等问题,再考虑升级。否则只是用更高的成本掩盖了底层问题。
安全建议:
- 在业务低峰期进行升级操作。
- 确保有可靠的备份和回滚计划。
诊断思路总结
碰到这种间歇性慢的问题,排查思路建议如下:
- 先看应用层: 从离应用最近的地方开始,检查和优化 HikariCP 连接池配置。这是最快能看到效果也最容易调整的地方。同时,确保 Spring Boot Actuator 的 HikariCP 指标可见,并监控起来。
- 再深入数据库: 如果连接池优化后改善不大,或者有证据(CloudWatch 指标、
CPUCreditBalance
)指向 RDS,那就启用 Performance Insights。分析数据库负载、等待事件、慢 SQL。看看是不是 RDS 资源不足(特别是 T3 积分)或者有待优化的查询。 - 然后排查网络: 如果应用层和数据库层都没明显问题,或者有明确的网络错误(如连接超时日志),再开始检查安全组、NACL、路由和 VPC Flow Logs。
- 最后考虑硬件: 只有当前三步都充分优化,且证据确凿表明 RDS 实例本身资源不足以支撑负载时,才考虑升级 RDS 实例规格。
整个过程中,持续监控是关键。CloudWatch 指标、RDS Performance Insights、应用日志、HikariCP 指标,都是你定位和解决问题的眼睛。祝你好运!