Elasticsearch-BulkProcessor-死锁分析

Elasticsearch-BulkProcessor-死锁分析

摘要

本文基于ES 6.4.3。主要分析由于在使用BulkProcessor向ES集群提交数据而集群响应异常时,导致的BulkProcessor死锁,而无法继续向BulkProcessor提交新数据,导致整个处理流程卡死的问题。

1 问题概述

我们程序流程是:

  1. 从Kafka消费数据
  2. 程序解析数据
  3. 处理好后,将数据组装为IndexRequest,使用bulkProcessor.add(indexRequest),异步批量提交数据到ES

最近Kafka消费延迟监控报警,发现某个topic消费延迟持续增长,消费速率降为0,所以查看了日志,发现最开是出问题的时候报错了:

...
2020-09-05 00:36:21.196 ERROR ElastServiceImpl$2,478 - ElastServiceImpl saveData  failed: null
2020-09-05 00:36:21.196 ERROR ElastServiceImpl$2,479 - bulk failed null msgs
2020-09-05 00:36:26.798 ERROR ElastServiceImpl$2,478 - ElastServiceImpl saveData  failed: null
2020-09-05 00:36:26.798 ERROR ElastServiceImpl$2,479 - bulk failed null msgs
2020-09-05 00:36:28.526 ERROR ElastServiceImpl$2,478 - ElastServiceImpl saveData  failed: null
2020-09-05 00:36:28.526 ERROR ElastServiceImpl$2,479 - bulk failed null msgs
2020-09-05 00:36:28.927 ERROR ElastServiceImpl$2,478 - ElastServiceImpl saveData  failed: null
2020-09-05 00:36:28.927 ERROR ElastServiceImpl$2,479 - bulk failed null msgs

这段日志来源于BulkProcessor

@Override
public void afterBulk(long executionId,
                       BulkRequest request,
                       Throwable failure) {
     logger.error("ElastServiceImpl saveData  failed: " + failure.getMessage());
     logger.error("bulk failed " + failure.getMessage() + " msgs");
 }

而我们的程序消费Kafka后提交offset日志也停在了同一时段:

...
2020-09-05 00:36:19.865 INFO  AbstractKafkaConsumeTask,172 - EsKafkaConsumeTask successfully to commit offset to zookeeper, topic=xxx, consumeId is:1
2020-09-05 00:36:21.192 INFO  AbstractKafkaConsumeTask,172 - EsKafkaConsumeTask successfully to commit offset to zookeeper, topic=xxx, consumeId is:2
2020-09-05 00:36:21.249 INFO  AbstractKafkaConsumeTask,172 - EsKafkaConsumeTask successfully to commit offset to zookeeper, topic=xxx, consumeId is:3

2 问题排查

2.1 日志

  1. 消费线程挂了?
    否,没有出现相关日志
  2. 消费出错?
    否,没有出现相关日志
  3. 写入ES出错?
    否,没有出现相关日志

2.2 top

我先用以下命令看了下有没有占cpu很高的:

top -Hp pid

发现线程占得资源都很平均。

2.3 jstack

也就是说程序只是卡住了,具体卡在哪里,只能用jstack分析。

首先把jstack输出到文件

jstack 29192 > slave.jstack

然后分析该文件.

less slave.jstack

直接搜索BulkProcessor关键字,发现大量相关线程处于BLOCKED阻塞状态,也就是说代码阻塞在synchronized等待对象锁:
在这里插入图片描述
上图中- waiting to lock <0x0000000606b340c8> (a org.elasticsearch.action.bulk.BulkProcessor)字样就是指当前线程在等待对象锁,可以看看该处BulkProcessor#internalAdd代码:

private synchronized void internalAdd(DocWriteRequest request, @Nullable Object payload) {
    ensureOpen();
    bulkRequest.add(request, payload);
    executeIfNeeded();
}

可以看到,该方法是需要获取对象级别的同步锁的。

我们以某个等待的锁编号0x0000000606b340c8来查询,结果如下:
在这里插入图片描述
可以看到,这个elasticsearch[scheduler][T#1]线程已经获取了该对象锁,但现在处于WAITING状态,等待申请 Semaphore,相关代码如下:
在这里插入图片描述
而我们知道,BulkProcessor的Flush线程会定时调用这个execute方法,并在响应后释放semaphore。但现在没有被正确释放,说明这个之前的定时调度没有调用回调方法:

@Override
public void onResponse(BulkResponse response) {
    try {
        listener.afterBulk(executionId, bulkRequest, response);
    } finally {
        semaphore.release();
        latch.countDown();
    }
}

@Override
public void onFailure(Exception e) {
    try {
        listener.afterBulk(executionId, bulkRequest, e);
    } finally {
        semaphore.release();
        latch.countDown();
    }
}

而我们Flush线程定时调度相关代码:

scheduler.scheduleWithFixedDelay(flushRunnable, flushInterval, ThreadPool.Names.GENERIC);

那也就造成了无法继续调度,全部在这hang住了。

3 问题总结

  1. 因为Flush定时调度线程有两次调度semaphore没有正确被释放,导致后续调度执行到semaphore.acquire()的时候无法获得许可,所以调用了底层的AQS使用的LockSupport.park阻塞,变为WAITING状态。
  2. Flush线程每次run的时候会占用BulkProcessor对象级别的同步锁
@Override
public void run() {
    synchronized (BulkProcessor.this) {
        if (closed) {
            return;
        }
        if (bulkRequest.numberOfActions() == 0) {
            return;
        }
        execute();
    }
}
  1. 所以我们的程序消费线程往BulkProcessor插入数据的时候,就无法获取该BulkProcessor对象级同步锁了
    private synchronized void internalAdd(DocWriteRequest request, @Nullable Object payload) {
       ensureOpen();
        bulkRequest.add(request, payload);
        executeIfNeeded();
    }
    
  2. 最终,造成全部卡死!
  3. 重试线程也无法进行,因为重试线程的调度是和Flush线程使用同一个只有一个核心线程的线程池进行的:
    在这里插入图片描述

该issue已经有人提出,相关连接:

4 根本原因

根本原因,没有再去深究,这个是ES内核的问题,也不知道到底是哪一步卡住了。

有兴趣的读者可以继续探究,相关源码已经跟到RestClient#performRequestAsync

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值