1.背景
开发任务:
本地mysql存储了100w+ 的酒店行记录数据, 现在遍历每个酒店id,从elong开发者平台拉取elong的百万酒店的图片到本地,存储到ES当中
方案设计:
- 采取生产消费的代码模型,一个线程不断地循环读取mysql 的酒店id add 进阻塞队列,然后在启动spring容器的同时,初始化三个线程去消费阻塞队列
- 阻塞队列是线程安全的,设计没有问题
- 为什么是三个线程去消费,是因为受限于elong开发者平台 获取酒店图片的接口QPS的限制
- 每个消费的线程http去访问elong开发者平台获取酒店图片的接口,采用的是kevinsawicki框架
- 每个线程在访问elong开发者平台获取酒店图片的接口失败的时候,会打印日志并且重新加入阻塞队列进行重试
- spring bean的初始化消费线程刚开始获取不到阻塞队列的元素就睡一会,保护机器线程
- 这个接口写了是去拉取数据的,待拉取数据完成后,需要调小blockingQueue的大小,然后注释掉@PostConstruct, 避免在jvm进程中有一些冗余的time_waited 线程,接口是一次性的。拉完数据就释放资源
2.代码demo演示
bean中的成员变量blockingQueue
主线程中的生产者
spring 的bean的三个消费者
3. 遇到的问题
消费线程莫名不工作了,不清楚是否阻塞队列还有待消费的元素,反正es的数据保持不变,没有增加,说明消费线程不工作了,或者是机器重启了
4 解决步骤
-
怀疑跑任务机器被重启,去devops平台确认没人重启
-
去看日志,少打了,补充日志,会打日志真的很重要,会打日志真的很重要,会打日志真的很重要,重要的事情说三遍,等会说一下我补充了哪些日志
-
补充了日志进一步上生产继续跑数据,5个小时以后发生了同样的问题,怀疑机器oom了,去查看机器的oom日志和gc日志、cpu、内存等信息
不巧的很,没配置heap oom 日志 -XX:HeapDumpPath=null
cpu, 内存正常
GC 日志正常
- 排查到这,常规手段都用上了,都没啥问题,现在我的疑惑就是我的消费线程干嘛去了,现在在干嘛,我要去追踪我的线程,那只能上jstack了
接下来分析stack文件,一下子就看到了
我在http访问第三方接口的时候被阻塞了,一直在那running,很明显我没设置降级措施,没设置超时时间
总结
外部接口的不稳定最终会导致这样的问题,所以一定要http接口降级,设置超时时间
我补充了线程名称和阻塞队列的任务数目的日志,后来我去jstack才能准确的捕获到出问题的线程,大家也看到了,并不是只有block的线程有危害,running的线程也是有可能有问题的
可以借助chatgpt分析jstack文件