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

相关推荐
百***78752 小时前
gpt-image-1.5极速接入指南:3步上手+图像核心能力解析+避坑手册
android·java·gpt
阿蒙Amon2 小时前
C#每日面试题-值类型与引用类型区别
java·面试·c#
nnsix2 小时前
Unity SenseGlove力反馈手套 基础配置
java·unity·游戏引擎
用户9446814013502 小时前
JUC 小试牛刀:从源码分析「ArrayBlockingQueue」,Java自带的线程安全的、有界的阻塞队列
java·后端
百***24372 小时前
GPT-Image 1.5 vs Nano Banana Pro 深度对比:国内业务落地的场景适配与避坑指南
java·数据库·gpt
代码栈上的思考2 小时前
MyBatis——动态SQL讲解
java·开发语言·数据库
@淡 定2 小时前
JVM调优参数配置详解
java·jvm·算法
撩得Android一次心动2 小时前
Android 四大组件——Service(服务)【基础篇2】
android·java·服务·四大组件·android 四大组件
爱宇阳2 小时前
在 Docker 环境中为 GitLab 实例配置邮件服务器
java·docker·gitlab