当前位置: 首页 > 后端技术 > Java

记得使用低版本的ESJavaClient偶尔查询超时问题解决过程

时间:2023-04-01 20:12:53 Java

先说明下项目中使用的ES版本是2.4版本,ESJavaClient是2.4.4版本。服务器配置为16G,8核。现象:在我们的一个区域项目中,有一个查询ES的接口在翻页的时候莫名其妙的超时了。可能翻第一页的时候出现,也可能翻第三页的时候出现。排查思路由于项目没有任何监控系统,所以排查的唯一方法就是定位超时执行的位置。在可能出现超时的代码中,打log,临时释放。最后发现当查询ES调用Stuck(`searchRequestBuilder.execute().actionGet();`)时,没有异常日志,代码执行的过程就和平结束了。
到目前为止,我还没有任何线索。我怀疑可以查看ActionRequestBuilder#execute的源码,看到使用了线程池,所以怀疑是机器配置不够,线程不够用?publicListenableActionFutureexecute(){PlainListenableActionFuturefuture=newPlainListenableActionFuture<>(threadPool);执行(未来);returnfuture;}于是进入容器使用top1命令查看CPU使用率,发现每个CPU使用率都很低,排除机器配置问题。是不是内存不够?JVM一直在执行GC、STW?使用jstat-gcutil-tprocessjd10001000查看GC信息,发现GC也正常,Eden、S1、S0、Old区域的占用都在正常范围内,FULLGC只有3次,而且YongGC和OldGC不是很频繁,所以排除是内存不足导致的。再次怀疑是线程问题。使用jstack-l命令输出线程信息。检查后发现有大量的es线程,线程状态为WAITING(parking)。具体线程信息如下,发现线程阻塞在BaseFuture#get位置。并且在调用AdapterActionFuture#actionGet时发生,于是追查源码发现BaseFuture内部Sync实现了AQS。AQS相关的文章可以查看我之前写的几篇博客。源码追踪BaseFuture#get实际上调用的是Sync#get,源码如下:BaseFuture.Sync#getVget()throwsCancellationException,ExecutionException,InterruptedException{//获取允许中断的共享锁。//AQS模板方法,尝试获取共享锁acquireSharedInterruptibly(-1);返回获取值();}AQS#acquireSharedInterruptiblypublicfinalvoidacquireSharedInterruptibly(intarg)throwsInterruptedException{if(Thread.interrupted())thrownewInterruptedException();如果(tryAcquireShared(arg)<0)doAcquireSharedInterruptibly(lyarg);使用tryAcquireShared(arg)判断是否获取到共享锁,tryAcquireShared(arg)是在Sync中实现的,代码如下/**如果future为don则获取成功e,否则失败。*/@OverrideprotectedinttryAcquireShared(intignored){if(isDone()){return1;}return-1;}booleanisDone(){return(getState()&(COMPLETED|CANCELLED))!=0;}getState是AQS的方法,状态默认为0,0和任意数都是0,所以只有state不为0的时候才会返回true,什么时候state会发生变化,看到这里估计可以猜的很好了。数据必须从ES获取,否则异常后状态会被修改。查看Sync,发现tryReleaseShared方法被重写了@OverrideprotectedbooleantryReleaseShared(intfinalState){setState(finalState);returntrue;}最初,这是修改状态的地方。检查调用方法的位置。最后,它从Sync#complete调用。根据方法名,我们也可以知道是完成时调用的。privatebooleancomplete(@NullableVv,@NullableThrowablet,intfinalState){booleandoCompletion=compareAndSetState(RUNNING,COMPLETING);if(doCompletion){//如果此线程成功转换为COMPLETING,则设置值//和异常,然后释放到最终状态。这个.value=v;这个.exception=t;释放共享(最终状态);}elseif(getState()==COMPLETING){//如果其他线程当前正在完成future,则阻塞直到//它们完成,这样我们才能保证完成。获取共享(-1);}returndoCompletion;}问题就定位到这里,基本确定了问题所在。当我们调用ESClient进行查询时,实际上是启用了一个查询线程和一个等待线程。由于查询线程还没有回调结果,导致等待线程一直阻塞,导致查询失败。解决方案由于用户着急使用,采用了临时解决方案,观察到的现象是零星的,所以尝试添加失败时的重试机制,发现#actionGet方法可以设置超时时间,于是更换可以设置超时时间的API,捕获ElasticsearchTimeoutException,在有异常时捕获Retry,问题解决。publicTactionGet(longtimeout,TimeUnitunit){try{returnget(timeout,unit);}catch(TimeoutExceptione){thrownewElasticsearchTimeoutException(e.getMessage());}catch(InterruptedExceptione){thrownewIllegalStateException("Futuregotinterrupted",e);}catch(ExecutionExceptione){抛出rethrowExecutionException(e);}}至于查询线程为什么长时间不回调,目前还没有具体定论,还在调查中。本文由博客多发平台OpenWrite发布!

最新推荐
猜你喜欢