thread堆栈分析报告

一 线程dump报告

1.1 背景介绍

现网发现rabbitmq消费者程序一直没有消费消息,最终导致rabbitmq磁盘溢出。

1.2 报告内容

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| "RabbitMQ-Consumer-46" #46 prio=5 os_prio=0 tid=0x00007fd2cf551800 nid=0x127 runnable 0x00007fce6a1aa000 java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:754) at com.lingxi.lingxipaas.rabbitmq.RabbitMQConsumer_Fixed.removeSensitiveWordWithTimeout(RabbitMQConsumer_Fixed.java:688) at com.lingxi.lingxipaas.rabbitmq.RabbitMQConsumer_Fixed.removeSensitiveWord(RabbitMQConsumer_Fixed.java:651) at |

1.4 线程状态

线程状态

|---------------|--------------------------------------------------------------|
| 状态 | 含义 |
| NEW | 线程刚创建,未启动 |
| RUNNABLE | 线程正在 JVM 中执行,或者在等待操作系统资源(如 CPU、I/O) |
| BLOCKED | 线程在 等待进入 synchronized 同步块/方法(即等待获取 monitor 锁) |
| WAITING | 调用了 Object.wait(), Thread.join(), LockSupport.park() 等,无限期等待 |
| TIMED_WAITING | 有超时的等待,如 Thread.sleep(1000), wait(1000) |
| TERMINATED | 线程已结束 |

1.5 报告分析

统计结果如下

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| at com.lingxi.lingxipaas.rabbitmq.RabbitMQConsumer_Fixed.enrichDataflowWithApiInfo(RabbitMQConsumer_Fixed.java:428) 出现11次 at com.lingxi.lingxipaas.rabbitmq.RabbitMQConsumer_Fixed.removeSensitiveWordWithTimeout(RabbitMQConsumer_Fixed.java:688) 出现4次 |

尽管没有加锁带来的阻塞,结合代码逻辑是触发第三方调用,会导致线程阻塞在IO上,实际资源监控发现,CPU消耗不高,因为线程没有实际工作,所以导致消息堆积严重

1.6 监控

消息队列资源消耗

消费者程序资源消耗

注: 即使生产/消费速度严重不对等,盲目扩大消费者程序不一定能正向提高消费能力,要考虑综合考虑上游服务消息队列和下游服务即第三方服务的压力

二 测试复现

2.1 测试用例

Mock程序: 准备一个故意睡眠10分钟的接口,供消费者程序调用

测试场景:持续1分钟,调用生产消息接口,快速生产3w+消息

测试配置: prefetch为250,concurrency为1 restTemplate超时时间是3秒

2.2 程序测试结果

测试程序

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Slf4j @Service public class TestService { @Resource private RestTemplate restTemplate; public String fallback(Throwable t){ return "系统故障,无法访问"; } public String handlerMsg() { String result = ""; try{ ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:8001/sleep", String.class); result = response.getBody(); }catch (Exception e){ log.error("系统异常"); //不能抛异常,否则消息无法确认 //throw new RuntimeException("系统异常"); } return result; } } |

发现至少要3秒才能消费一条

2.3 程序熔断后测试结果

测试程序

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Slf4j @Service public class TestService { @Resource private RestTemplate restTemplate; public String fallback(Throwable t){ return "系统故障,无法访问"; } @CircuitBreaker(name = "test", fallbackMethod = "fallback") public String handlerMsg() { String result = ""; try{ ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:8001/sleep", String.class); result = response.getBody(); }catch (Exception e){ log.error("系统异常"); //需要抛异常,否则无法触发熔断 throw new RuntimeException("系统异常"); } return result; } } |

熔断配置

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| resilience4j.circuitbreaker.instances.test.sliding-window-type=COUNT_BASED resilience4j.circuitbreaker.instances.test.sliding-window-size=5 #失败率 resilience4j.circuitbreaker.instances.test.failure-rate-threshold=10 #最小请求次数 resilience4j.circuitbreaker.instances.test.minimum-number-of-calls=5 #试探性恢复 resilience4j.circuitbreaker.instances.test.permitted-number-of-calls-in-half-open-state=3 #休眠时间 resilience4j.circuitbreaker.instances.test.wait-duration-in-open-state=120s #哪些异常记录为失败 #resilience4j.circuitbreaker.instances.test.recordExceptions=java.lang.reflect.InvocationTargetException,java.lang.RuntimeException,org.springframework.web.client.HttpServerErrorException,org.springframework.web.client.ResourceAccessException |

可以快速完成消费

注: 具体方法独立成单独Service,相关依赖一定要引入resilience4j和aop

三 参考pom

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot2</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> |

四 参考链接

https://resilience4j.readme.io/docs/getting-started-3

https://github.com/resilience4j/resilience4j-spring-boot2-demo

相关推荐
驭渊的小故事3 小时前
MyBatis XML 开发实战:从配置到 CRUD 全解析
xml·java·mybatis
哦虎!8 小时前
【JavaEE】网络初识
java
for_ever_love__8 小时前
MySQL ORDER BY 排序优化:从 Using filesort 到索引有序扫描(含双路 / 单路排序原理)
java·mysql·优化·存储·by·order·filesort
我是人✓9 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
欣栀♡9 小时前
JVM 频繁 Full GC 先看日志括号里的 cause,jstat 的 FGC 在 JDK 8 上会骗人
java·jvm
IT研究室9 小时前
最新计算机毕业设计选题推荐-基于spring boot的葡萄酒文化宣传网站-网站-文档指导-Java-springboot
java·spring boot·课程设计
慧都小项9 小时前
MyEclipse 中创建和测试 JAX-RS REST 接口:从项目配置到 REST Explorer
java·restful·myeclipse·jax-rs
Wang's Blog9 小时前
Java 项目实战: 交友项目 - 入门案例:编写 Dubbo 服务提供者
java·dubbo·交友
唠点键盘之外的9 小时前
27 存量系统接大模型:旁挂一个 AI 微服务,老架构一行不改
人工智能·微服务·架构
秋饼10 小时前
Spring AI 2.0 结构化输出企业级实战:从 BeanOutputConverter 源码到生产级解析容错
java·ai·技术分享·后端开发