共计 3048 个字符,预计需要花费 8 分钟才能阅读完成。
首先阐明我的项目中 ES 应用版本为 2.4 版本,ES JavaClient 为 2.4.4 版本。服务器配置为 16G、8 核。
景象
咱们一个地区我的项目中有一个查问 ES 的接口莫名其妙的翻页会呈现超时,可能翻第一页会呈现,可能翻第三页会呈现。
排查思路
因为我的项目没有做任何监控零碎,因而惟一的排查思路就是定位到底是哪里执行超时的,在代码中可能产生超时的地位都打了日志并进行长期公布,最初发现是在调用查问 ES 的时候卡住的(`searchRequestBuilder.execute().actionGet();`),并且没有任何的异样日志,代码执行的过程中很安详的就完结了。<br /> 到此为止,还没有任何的脉络,
狐疑点
- 查问
ActionRequestBuilder#execute
源码看到应用到了线程池,因而狐疑是机器配置不够,线程不够用了?
public ListenableActionFuture<Response> execute() {PlainListenableActionFuture<Response> future = new PlainListenableActionFuture<>(threadPool);
execute(future);
return future;
}
因而进入容器内应用 top 1 命令
查看 cpu 占用,发现每个 cpu 占用都很低,因而排除机器配置问题。
- 难道是内存不够用了?JVM 始终进行 GC,STW 了?
应用 jstat -gcutil -t 过程 jd 1000 1000
查看 GC 信息,发现 GC 也失常,Eden、S1、S0、Old 区占用都 处于失常范畴,FULL GC 才 3 次,Yong GC 和 Old GC 也不是很频繁,因而排除是内存不够导致。
- 再次狐疑是线程问题
应用jstack -l 命令
将线程信息输入进去,查看后,发现有大量的 es 线程,并且线程状态为WAITING (parking)
,具体线程信息如下
发现线程是阻塞在 BaseFuture#get
地位,并且是在调用 AdapterActionFuture#actionGet
时产生的,因而跟踪源码,发现BaseFuture 外部 Sync 实现了 AQS
,AQS 相干文章可查看我之前写的几篇博客,
源码跟踪
BaseFuture#get
实际上是调用的Sync#get
,源码如下:
BaseFuture.Sync#get
V get() throws CancellationException, ExecutionException,
InterruptedException {
// Acquire the shared lock allowing interruption.
//AQS 的模板办法,尝试获取共享锁
acquireSharedInterruptibly(-1);
return getValue();}
AQS#acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
看到 AQS#acquireSharedInterruptibly
中调用了 tryAcquireShared(arg)
判断是否拿到了共享锁,tryAcquireShared(arg)
在 Sync
中实现了,代码如下
/*
* Acquisition succeeds if the future is done, otherwise it fails.
*/
@Override
protected int tryAcquireShared(int ignored) {if (isDone()) {return 1;}
return -1;
}
boolean isDone() {return (getState() & (COMPLETED | CANCELLED)) != 0;
}
getState
是 AQS 的办法,state 默认为 0,0 与任何数都是 0,因而只有当 state 不为 0 的时候,才会返回 true,那什么时候 state 才会更改呢,看到这里预计也能猜个八九不离十了,必定是从 ES 拿到数据,或者异样后才会批改 state,查看Sync 看到重写了 tryReleaseShared 办法
。
@Override
protected boolean tryReleaseShared(int finalState) {setState(finalState);
return true;
}
原来就是在这里批改 state 的,查看该办法被哪里调用了,最终是从 Sync#complete
调用过来的,依据办法名称咱们也能够晓得就是实现的时候调用。
private boolean complete(@Nullable V v, @Nullable Throwable t,
int finalState) {boolean doCompletion = compareAndSetState(RUNNING, COMPLETING);
if (doCompletion) {
// If this thread successfully transitioned to COMPLETING, set the value
// and exception and then release to the final state.
this.value = v;
this.exception = t;
releaseShared(finalState);
} else if (getState() == COMPLETING) {
// If some other thread is currently completing the future, block until
// they are done so we can guarantee completion.
acquireShared(-1);
}
return doCompletion;
}
问题定位
到这里,咱们根本就确定了问题所在,咱们在调用 ES Client 去查问的时候,实际上是开启了一个查问线程和一个期待线程,因为查问线程始终没有回调后果,期待线程始终阻塞,导致查问失败。
解决方案
因为用户焦急应用,因而采纳长期计划,察看到景象为偶发,因而尝试在失败时减少重试机制,发现 #actionGet 办法
能够设置超时工夫,因而更换可设置超时 API,并捕捉ElasticsearchTimeoutException
,在捕捉到异样时进行重试,问题解决。
public T actionGet(long timeout, TimeUnit unit) {
try {return get(timeout, unit);
} catch (TimeoutException e) {throw new ElasticsearchTimeoutException(e.getMessage());
} catch (InterruptedException e) {throw new IllegalStateException("Future got interrupted", e);
} catch (ExecutionException e) {throw rethrowExecutionException(e);
}
}
至于为何查问线程迟迟不回调,还没有具体的定论,还在持续排查。
本文由博客一文多发平台 OpenWrite 公布!