Java Spring Boot应用自动扩缩容时的性能问题排查求助
问题背景
Java Spring Boot应用集成开源调度器,核心流程:
- 收到请求后创建延迟任务,任务详情以Blob存入PostgreSQL的
task_table - 应用启动后启动带独立线程池的轮询器,由master agent检查数据表并分配到期任务给从线程执行,单任务最长耗时15秒
- 单容器部署时耗时正常,扩至2-4个容器后,任务耗时随容器数增加持续上升(2容器耗时20秒,3容器耗时45秒)
- 已排查数据库连接池、IOPS延迟,更换EC2实例类型、调整RDS存储/IOPS等,均未解决问题
可能的原因及排查点
任务拾取的锁竞争与重复抢占
多容器轮询器同时访问task_table时,若没有原子性的任务抢占机制,会出现多个实例同时尝试锁定同一任务的情况,导致数据库行锁等待。即使最终仅一个实例执行任务,其他实例的等待时间会叠加到任务总耗时中。
排查:检查任务拾取SQL是否使用PostgreSQL支持的SELECT ... FOR UPDATE SKIP LOCKED来实现无等待的任务抢占,还是采用先查询再更新状态的非原子逻辑——后者在多实例下会引发大量锁冲突与重试。轮询频率叠加导致数据库隐性负载上升
每个容器独立运行轮询器,容器数增加后,轮询查询的总频率成倍数增长。虽然单看IOPS和连接池无异常,但高频查询会导致数据库CPU使用率上升、事务上下文切换增多,间接拖慢任务执行时的数据库操作速度。
排查:通过PostgreSQL的pg_stat_activity视图统计各容器的轮询查询次数,观察数据库CPU、事务处理耗时的变化趋势。跨容器的任务资源竞争
若任务执行依赖共享资源(如全局缓存、数据库中同一类业务资源),多容器同时执行任务时会引发跨实例的资源竞争。此外,若每个容器的master agent未做任务限流,大量任务同时涌入会导致线程池上下文切换频繁,拉长任务执行时间。
排查:查看任务执行过程中访问的共享资源是否存在并发瓶颈,检查线程池的队列长度、活跃线程数及上下文切换指标。任务状态更新的冲突
任务执行完成或状态变更时,多容器可能同时尝试更新同一任务的状态(如标记为已完成),或其他轮询实例持续读取该任务状态,引发不必要的锁等待或重复操作。
排查:检查任务状态更新的SQL逻辑,是否使用乐观锁(如version字段)或合适的行锁机制避免冲突。EC2实例的资源争用
多容器部署在同一EC2实例上时,会共享CPU、内存、网络带宽。容器数增加后,CPU上下文切换、内存分页、网络带宽竞争都会导致任务执行耗时上升。
排查:查看EC2 CloudWatch指标(CPU使用率、内存使用率、网络流量)及容器资源使用情况(docker stats),确认是否存在实例级资源瓶颈。
内容的提问来源于stack exchange,提问作者S.Thulasi Raman

