一次神奇的Dubbo FutureContext内存泄露 惨痛教训

238 阅读4分钟

问题描述

突然有一天运维说 你这个内存不正常经常full gc 给了我个截图

发现这个gc次数有点多啊,为什么gc次数会这么多呢?我就去查看应用的gc日志

发现full gc是 因为大对象 导致的fullgc ,然后我这个时候保护现场一下,jdump 将内存导出(慎用jdump 会导致卡死 我是重新构建一个新的应用 将流量切换到这个应用,然后流量将不能到打到老的应用)

问题分析

分析 hprof 文件

发现一个对象 引用的内存大概有4M 我的天 怎么会这么大?而且为啥不会被释放呢?

来我们继续跟踪 点进一个对象查看详情 我草发现了这个对象是个大对象里面的有一个属性,竟然占用3M的内存空间,这个开发是真的讨打啊,写的什么鬼东西,再细看里面,这个configInfo是一个大json,里面放了一堆配置,果然没有最烂只有更烂,所以 数据库当中最好**别定义存储json的字段****,不然你后面维护都不知道这个json里面到底有啥,深深的给自己买了一个大坑,就算你自己很小心使用,其他对自己开发要求不一样的人,就可能会很随便,导致给团队造成了不可估量的损失。**

继续深究

我们最终发现了那个大对象是哪一个,但是问题的根本原因不是这个,大对象只是诱因。我发现这个是大对象是一个rpc请求返回的响应结果,所以应该是朝生夕死啊,按道理就算是大对象,上一次full gc应该就把这种对象的内存给释放了啊,为什么没有释放呢?神奇神奇神神奇,来我们继续分析

从截图来看我们发现了一个重要信息

1.org.apache.dubbo.rpc.FutureContext

2.org.apache.dubbo.common.threadlocal.InternalThreadLocalMap

FutureContext 这个对象是 dubbo 用来处理异步请求时,获取异步请求结果的上下文的类

SerivceConsumer.sayHello("hello")
 //可以拿到异步结果的对象
CompletableFuture<?> result=FutureContext.getContext().getCompletableFuture()
//我们可以对结果进行监听回调
result.whenComplete((v,e))->{}

可以看到dubbo的注释 这个类 用法类似 consumer异步调用 做链路跟踪时,需要对异步结果进行监听

那啥时候往这个FutureContext的ThreadLocal当中写入值的呢?

可以看到每次rpc都会往里面塞入值。org.apache.dubbo.rpc.protocol.AbstractInvoker#invoke

那什么时候回收呢?找找看

我草居然没找到

好吧原因找到了,原来是这个没有被回收,一直处于泄露状态,那dubbo为啥不回收呢?

1.在dubbo的角度 这个是给了一个thread local 变量 给到用户 可以拿到异步结果,那在什么时候回收呢?怎么回收呢 这个是需要考虑的?

dubbo 有个 ContextFilter 专门来清理 RpcContext, 如果dubbo 在ContextFilter当中调用了 清理FutureContext ,那可能有问题, 如果还有filter想拿到这个结果呢?

2.为啥dubbo不清理呢?

dubbo定位 应用之间的rpc 请求框架,期望的rpc数据报文是比较小的,所以不去清理了?而且这个都能计算的,是有限的空间。

如果本身是DubboServer,在作为Provider的时候又调用这个rpc ,我们分析源码可以知道线程池的大小,org.apache.dubbo.common.threadpool.support.fixed.FixedThreadPool 这个来构建线程池的,而且默认核心线程数为200个,而且如果不是作为Provider 而是tomcat直接访问rpc,那么就是计算tomcat的线程池的大小也差不多是200个,所以如果你的对象比较小,那么占用的总内存还是比较有限的。而且每次新请求都会覆盖上一个请求的响应。

那怎么解决这个问题呢?

1. 如果你的应用没有使用异步调用,而且也不需要使用FutureContext.getContext(),那么可以自定义实现Filter,来清理FutureContext。 2. 升级dubbo dubbo新版本提供了一些参数

通过设置这个参数future.sync.set=false(dubbo最近的代码质量也比较差了,这个默认值应该为false,但是官方是true) future.clear.once=true 但是我认为这个没啥用 如果你前面设置了false 他都直接不调用,除非你真的是异步调用才有用,我最近要提个[Bug] future.sync.set default value error · Issue #14727 · apache/dubbo (github.com)把这个默认值给改了,这样就不用配置了。