本文转载自微信公众号《捉虫大师》,作者捉虫大师。转载本文请联系捕虫大师公众号。背景最近一天深夜,刚洗完澡就接到业务方的电话,说他们的dubbo服务出问题了,让我帮忙排查。在电话里,我问他们现在几点了。是在线丢失失败吗?-是的,止损了吗?-损失已经停止。有现场预约吗?-没有,所以我打开电脑并连接到互联网来检查问题。为了便于理解,架构简化如下,只需要关注A、B、C三个服务,它们之间的调用都是dubbo调用。当发生故障时,B服务中的几台机器完全死机,无法处理请求,而其余正常机器的请求量急剧增加,耗时增加,如下图(请求数在图1中,耗时在图2中)。可以先看监控和日志监控除了上面的监控,我查看了服务BCPU和内存的基础监控,发现有几台故障机的内存涨得比较多,达到了80%的水平,并且CPU消耗也增加了。当时比较怀疑是内存问题,于是查看了JVM的fullGC监控,结果发现fullGC时间增加了很多。基本可以断定是内存泄漏导致服务不可用。但是为什么会出现内存泄漏还不清楚。在日志中申请机器权限,查看日志,发现一条很奇怪的WARN日志[dubbo-future-timeout-thread-1]WARNorg.apache.dubbo.common.timer.HashedWheelTimer$HashedWheelTimeout(HashedWheelTimer.java:651)-[DUBBO]TimerTask.抛出异常,dubb版本:2.7.12,当前主机:xxx.xxx.xxx.xxxjava.util.concurrent.RejectedExecutionException:Taskorg.apache.dubbo.remoting.exchange.support.DefaultFuture$TimeoutCheckTask$$Lambda$674/10670777932d@util.concurrent.ThreadPoolExecutor@7a9f0e84[Terminated,poolsize=0,activethreads=0,queuedtasks=0,completedtasks=21]可以看出业务端使用的是2.7.12版本的dubbo。把这个日志拿到dubbo的github仓库里去搜索了一下,发现了如下问题:https://github.com/apache/dubbo/issues/6820不过很快就排除了问题,因为代码在2.7版本已经修复了。12.继续找这两个issue:https://github.com/apache/dubbo/issues/8172https://github.com/apache/dubbo/pull/8188从报错和版本来看,是完全一致,但是没有提到内存问题,我们忽略内存问题,看看能不能按照#8188issue重现。issue中也说清楚了如何复现,所以我设置了这三个服务来复现。一开始,它还没有被复制。.通过修复代码推导删除代码部分是有问题的,但我们很难复现。我们怎样才能进入呢?这里一个feature代表一个request,只有在request没有完成的时候才会进入,好办,让provider不返回,肯定是可以的,所以加上Thread.sleep(Integer.MAX_VALUE)提供商端的测试代码;经过测试,确实重现了,正如问题所说,当kill-9删除第一个提供者时,消费者的全局ExecutorService关闭,而当kill-9第二个提供者时,SHARED_EXECUTOR也关闭。那么这个线程池是用来做什么的呢?在HashedWheelTimer中用于检测消费者请求是否超时。HashedWheelTimer是dubbo实现的一种检测时轮检测请求是否超时的算法。此处不再详述。改天可以写篇详细介绍dubbo中time-wheel算法的文章。请求发出后,如果能正常返回就好了,但是如果过了设置的超时时间还没有返回,就需要这个线程池的任务去检测并中断超时的任务。下面的代码是提交一个任务。当线程池关闭时,提交任务时会抛出异常,无法检测到超时。publicvoidexpire(){if(!compareAndSetState(ST_INIT,ST_EXPIRED)){return;}try{task.run(this);}catch(Throwablet){if(logger.isWarnEnabled()){logger.warn("Anexceptionwastrownby"+TimerTask.class.getSimpleName()+'.',t);}}}到这里突然恍然大悟:如果请求一直发送没有超时,是不是有可能爆内存?于是又模拟了一下,开了3个线程一直在请求provider,重现了内存爆的场景,但是在没有触发这个问题的时候,内存一直稳定在低位。这里我使用arthas查看内存变化。本地复现后很方便下结论,所以跟业务方核实了一下,这个问题的复现还是比较苛刻的。一是必须异步调用,二是需要异常下载provider。行,最后需要阻塞提供者,即请求永不返回。异步调用业务方确认,provider异常下线。这是比较常见的。当物理机故障导致容器漂移时,就会出现这种情况。最后,提供商被封杀,这也得到了业务方的确认。确实有一个C服务。机器在那一点附近冻结,无法处理请求,但进程仍然存在。所以这个问题是dubbo2.7.12的一个bug导致的。翻了一下这个bug,2.7.10引入,2.7.13修复。定位重放花了将近一天的时间,还算顺利,运气不错,走的弯路不多,但还是有一些需要注意的地方。最好在止损的同时保住场地。比如这次在重启前转储内存或者去流量保留机器现场,可能有助于加快问题的定位。比如配置OOM时自动转储内存等其他手段。这也是这次事故的不足之处。服务的可观察性很重要,不管是日志、监控还是其他,都要完整。基本的像日志记录、出口、导入请求监控、机器指标(内存、CPU、网络等)、JVM监控(线程池、GC等)。你可以做得很好。基本上,应该有开源产品可用。您可以从关键日志中搜索Internet。你遇到的问题大概率是你遇到的。这也是这次的幸运点,少走了很多弯路【责任编辑:吴小燕TEL:(010)68476606】
