一个隐藏bug:dubbo应用在DNS异常时导致OOM

注:dubbo 已被阿里重启,这个issue已经在 最新版本 2.6.1 之后解决了

这个上半年我们在线上发现的一个bug,现在想起来,还是记录一下。希望官方也能修复
这是一个Dubbo的bug,
版本是2.8.4 (这个版本号是当当网基于2.5.3增加了几个特性后的分支版本,中央仓库应该没有)

bug描述:

如果应用配置了 monitor=registry,当DNS异常时有可能导致应用OOM,并且无法恢复。

这个情况比较极端,一般很难出现,但是 。。。 我们碰到了。。。

故障模拟

模拟过程:
在测试环境部署一个 dubbo应用,配置 monitor=registry
dubbo的注册中心配置,配的是域名,并且域名是通过DNS解析(非/etc/hosts解析)
启动一个测试脚本,循环调用该应用提供的几个服务。
修改 /etc/resolv.conf 将DNS的IP 修改为错误IP
脚本继续运行,引发dubbo应用的内存泄漏问题

故障原因分析:

dubbo 封装了一个工具方法:
com.alibaba.dubbo.common.utils.NetUtils.getIpByHost(host)

public static String getIpByHost(String hostName) {
        try{
            return InetAddress.getByName(hostName).getHostAddress();
        }catch (UnknownHostException e) {
            return hostName;
        }
    }

当DNS发生故障时,通过域名获取IP这个工具方法
返回的不是IP地址而是域名。

在配置了dubbo的 monitor=registry的情况下,dubbo完成业务后将调用
com.alibaba.dubbo.monitor.support.MonitorFilter 的 collect()方法
【请自行看源码】
该方法将执行
Monitor monitor = monitorFactory.getMonitor(url);
获取monitor对象,dubbo设计为可支持多个monitor,因此内部使用了Map保存多个monitor,每次调用getMonitor()时将从url计算出key,代码片段如下:
代码位于:
com.alibaba.dubbo.monitor.support.AbstractMonitorFactory
这里写图片描述
其中的第49行
url.toServiceString()
里面使用了之前提到过的
NetUtils.getIpByHost(host) 方法
因此,
当DNS正常时,计算出来的key是包含IP的形如 172.32.42.34/*
当DNS异常时,计算出来的key是包含域名的形如 zookeeper.service.zbj.com/*
so
在DNS出问题之前,MONITORS这个map中只有一个k-v,key是”172.32.42.34/*”,代码走入第54行,直接返回已有的monitor。
而当DNS发生异常时,计算出来的key不同了,代码进入第56行。
继续进入第56行代码的具体实现:
….略….
将进入
AbstractRegistryFactory 的 getRegistry()方法
代码片段如下:
这里写图片描述

类似的,key在DNS正常时,异常时返回的值不同。
在异常情况下,将进入第94行。
createRegistry是抽象方法,在我们的配置情况下,将执行如下代码:
这里写图片描述

由于ZookeeperRegistry extends FailbackRegistry
先看 ZookeeperRegistry的构造方法:
这里写图片描述

先调了父类FailbackRegistry的构造方法:
这里写图片描述

再仔细研读FailbackRegistry代码,FailbackRegistry 将是一个 Deamon类,创建后不会被销毁。
继续。。。
回到ZookeeperRegistry的构造方法,代码走到第69行

zkClient = zookeeperTransporter.connect(url);
由于DNS异常,该方法将抛出异常。

再回到
AbstractRegistryFactory 的 getRegistry()方法
这里是一个关键点:
这里写图片描述

异常抛出后,直接进入finnaly块,REGISTERS这个map里面依然只有一个k-v。
同理
com.alibaba.dubbo.monitor.support.AbstractMonitorFactory 的 getMonitor() 方法
也是一样的情况,异常抛出后 map里只有一个k-v。

当下一次请求进来时,从两个地方的map里面依然无法找到,然后就会按照刚刚分析的代码一样的执行,就又会进入getRegistry() 第94行,继续抛出异常,map继续只有一个k-v。

从上面的分析,可以得到如下结论:
如果DNS有问题、且配置了dubbo monitor=registry,每次请求都会new出一个ZookeeperRegistry,ZookeeperRegistry是一个Demon类,创建之后无法销毁。

继续往下分析:

当yongGC时,ZookeeperRegistry会直接进入老年代,dubbo应用 JVM的老年代是有限的,我们用了CMSGC配置为达到80%以上触发FullGC,然而 FullGC也不会将 ZookeeperRegistry清理掉。
于是,在DNS无法联通的这段时间里,非常多的 ZookeeperRegistry实例被创建(每一个请求创建一个该对象的实例),当达到 老年代的80%后, CMSGC将循环无限被执行,这个会使JVM 进入非常多的 stop-the-world 状态,该JVM接受到的请求的响应时间会变得很长(试CMSGC的stw时间而定,有的很长有的有点长)。

而由于内存根本无法被回收,即使 DNS恢复了,JVM也无法恢复。

(注:若DNS异常的时间更短一些,新创建的ZookeeperRegistry的数量还不多-没达到老年代的80%,情况将会好很多)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值