1. 引言
Hi,你好,我是有清
在金融系统中,业务人员可以通过页面点击来修改当前产品的保费利率,然后触发一系列的业务逻辑
近几天,业务人员反馈偶尔修改产品的保费利率页面会卡死,但是刷新页面后重试,往往都能成功....
红线要紧,下文中出现的代码,均为伪代码
2. 排查过程
2.1. 接口问题排查
在业务人员反馈问题发生的时间点前后,查看该接口的日志,的确存在大量超时的情况
最后的日志都停在了 countDownLatch 的 await 前
这边结合上方伪代码,顺便科普一下 counrDownLatch 的用法
- 初始化 countDownLatch,传入初始化参数,比如传入 3
- 在 Task 任务中,使用 countDownLatch.countDown() 对 3 进行 -- 操作
- 当 3 被子线程减到 0 的情况下,countDownLatch.await() 会自动被唤醒往下走,如果没有加超时时间线程会被持续夯住,如果加了超时时间,在时间到达之后则会进行异常抛出
那这个情况下,很显然 main 线程被阻塞住了,无法继续往下走,而且有意思的是 Task 中的日志均没有打印
那么即子线程中的任务都没有执行,没有进行 countDownLatch.countDown() 操作,导致可怜的 main 线程一直苦苦等待
2.2. 子线程任务未执行
那么为何子线程中的任务没有执行呢?
开始经典老八股了,线程池的工作流程是什么样的呢?请默写并背诵
- 新任务进来,开启线程执行,直到核心线程数满载
- 任务继续进来,核心线程数满载,将任务放到队列中,直到任务队列满载
- 任务继续进来,任务队列满载,开启最大线程数执行,直到最大线程数满载
- 任务继续进来,任务根据具体的拒绝策略,进行对应的处理
根据这个套路,我们的子任务未执行,可能的情况只有两个
- 任务还在队列里,还没轮到他执行
- 任务被抛弃了,根本没有执行到
但其实我们重写了拒绝策略,一旦任务被抛弃,我们会主动抛出出我们的业务异常,但是对日志进行关键字搜索,并没有发现该异常
那么真相只有一个,任务还在队列里,还没轮到他执行
但是很可惜,我们没有直接对线程池的可视化界面,但是我们可以借助 Arthas 的 watch 去佐证我们的判断,再对比正确机器和错误机器的线程队列大小之后,我们的推测是正确
那么,为什么子线程的任务不执行呢?我们继续看代码
2.3. 子线程代码排查
先看一波代码
在子线程中,竟然又开了新的子线程去执行新任务
Main 线程开启 2 个 task 子线程,这 2 个子线程又开启 2 个 subTask 子线程
一图胜千言
当主线程进来的时候,任务 1 开始执行,什么时候执行完成呢?必须等等 任务1-1 和 任务 1-2执行完成后,才能继续执行,但是这些任务队列还没满,有没有新的线程去处理这些任务,就导致线程池中任务一直无法被执行
概括一下就是:主线程等待 任务1,任务1 等待任务 1-1,1-2,但是1-1、1-2 无人执行,任务1 卡住,任务1卡住,任务 2 卡住,任务 1、2 卡住,主线程卡住
完整伪代码如下,感兴趣的同学可以看下
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 3, 2, TimeUnit.MINUTES, new LinkedBlockingQueue(7));
List futures = Lists.newArrayList();
int taskCount = 2;
CountDownLatch countDownLatch = new CountDownLatch(taskCount);
int i = 1;
while (i < taskCount) {
Task task = new Task(countDownLatch, threadPoolExecutor, String.valueOf(i));
Future res = threadPoolExecutor.submit(task);
futures.add(res);
i += 1;
}
try {
countDownLatch.await();
} catch (Exception e) {
}
}
static class Task implements Callable {
private CountDownLatch countDownLatch;
private ThreadPoolExecutor gocExecutors;
private String taskId;
public Task(CountDownLatch countDownLatch, ThreadPoolExecutor threadPoolExecutor, String taskId) {
this.countDownLatch = countDownLatch;
this.gocExecutors = threadPoolExecutor;
this.taskId = taskId;
}
@Override
public String call() throws Exception {
Thread.sleep(300);
int taskCounts = 2;
CountDownLatch cl = new CountDownLatch(taskCounts);
List futures = Lists.newArrayList();
for (int i = 1; i