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

相关推荐
程序员敲代码吗5 分钟前
如何通过命令行启动COMSOL的参数化、批处理和集群扫描
java·c#·bash
MX_935910 分钟前
Spring的bean工厂后处理器和Bean后处理器
java·后端·spring
市场部需要一个软件开发岗位27 分钟前
JAVA开发常见安全问题:纵向越权
java·数据库·安全
历程里程碑39 分钟前
普通数组----合并区间
java·数据结构·python·算法·leetcode·职场和发展·tornado
程序员泠零澪回家种桔子1 小时前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构
CodeCaptain1 小时前
nacos-2.3.2-OEM与nacos3.1.x的差异分析
java·经验分享·nacos·springcloud
Anastasiozzzz2 小时前
Java Lambda 揭秘:从匿名内部类到底层原理的深度解析
java·开发语言
骇客野人2 小时前
通过脚本推送Docker镜像
java·docker·容器
铁蛋AI编程实战2 小时前
通义千问 3.5 Turbo GGUF 量化版本地部署教程:4G 显存即可运行,数据永不泄露
java·人工智能·python
晚霞的不甘2 小时前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频