异常信息:java.io.IOException: listener timeout after waiting for [60000] ms

异常堆栈:

java.io.IOException: listener timeout after waiting for [60000] ms
	at org.elasticsearch.client.RestClient$SyncResponseListener.get(RestClient.java:660)
	at org.elasticsearch.client.RestClient.performRequest(RestClient.java:219)
	at org.elasticsearch.client.RestClient.performRequest(RestClient.java:191)
	at com.suning.maoning.core.es.EsClientHolder.performRequest(EsClientHolder.java:113)
	at com.suning.maoning.core.es.EsClientHolder.performRequest(EsClientHolder.java:127)
	at com.suning.maoning.core.es.EsClient.searchAfter(EsClient.java:343)
	at com.suning.maoning.mnbi.service.impl.commissionexport.XXXXXXXXXXXService.queryData(BaseCommissionEsExcelExportService.java:76)
	at com.suning.maoning.excel.common.export.service.impl.EsScrollRunnable.async(EsScrollRunnable.java:39)
	at com.suning.maoning.excel.common.export.service.impl.EsScrollRunnable$$FastClassBySpringCGLIB$$1736c315.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at com.suning.maoning.core.cache.interceptor.RedisInterceptor.invoke(RedisInterceptor.java:119)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

根据异常堆栈指示的信息,找到对应发生异常的业务类,定位到相关具体语句,是一个ES查询调用的地方,调用方法为

esClient.searchAfter

继续跟踪对应方法

继续往下跟踪

最后再这里

到这里,分成了两个部分,

第一部分创建了一个SyncResponseListener,用于监听对es请求的返回

另一部分performRequestAsync则对es发起HttpRequest,进行查询。

需要注意的是SyncResponseListener创建的时候会同时创建一个CountDownLatch

private final CountDownLatch latch = new CountDownLatch(1);

而在performRequestAsync请求es的逻辑中,不论请求成功得到结果,还是失败或者发送异常等情况下,都会调用latch.countDown()

同时,最重要的,在上面的代码中,最后调用listener.get()会进行等待

这里也就是最终异常抛出的地方。

下面我们简单整理描述下这部分代码到底在干啥

首先这边分成了两个线程,一个线程为response结果监听线程,用于监听请求结果是否得到了,另一个则是去向es请求的线程,监听线程在这里会监听等待timeout的时长,如果没有得到结果,则直接抛出IOException。

那么到这里,我们已经明白了为什么会产生这样的异常了,简单来说就是去es请求,但是超时了。

对于处理办法,首先想到的是修改参数增加timeout超时时间,这样监听线程会等待更久,以不至于会超时。

不过这显然不够优雅,扒完了源码,我们重新回到业务中,我看到了es查询请求的分页参数设置的size = 10000,即每页查询10000条数据,根据开发这块业务功能的同学以及测试同学一起描述的,“测试的时候没啥问题啊!”,嗯,确实,测试的时候测试环境费劲巴拉最多也就造了1000多条数据,o(╥﹏╥)o,基本不可能暴露出来问题的。

所以,在这边,我们重新配置修改了分页大小参数,先对半砍改为5000试了下,执行,确认通过,没有问题。

这边还有个比较有意思的,关于es查询的深分页,这边使用的是es的search after方法,另外es还提供了一个scroll的深分页查询方法,回头可以展开再讨论下