Dubbo管控台线上问题排查

895

1、排查线上问题有什么难点?

  1. 虽然我们学了很多排查线上问题的理论知识,但一直没有施展的机会?
  2. 线上偶尔出现一次问题,但还没来得及保存现场,而后面又很难复现?
  3. 能够稳定复现,发现分析起来还是太困难了?没有经验,理论知识不能灵活应用,很难下手?

2、这篇文章有什么优势

  1. 直接基于开源框架dubbo-admin进行分析,不存在复杂业务,人人都可以查看源码
  2. 都够稳定复现问题(有关于服务量我们我完全可以手动模拟)
  3. dubbo-admin源码简单,并且很多公司都在用,学习之后可以帮你解决实际问题

内存泄漏问题

新版的dubbo-admin 在支持dubbo2.7新特性的同时,还兼容dubbo2.6。基于dubbo2.7的元数据中心,我们可以做一些事情,比如服务测试,在目前版本的dubbo-admin中,其实已经支持这个功能。

结论

先说结论,导致内存泄漏的代码在 org.apache.dubbo.admin.service.RegistryServerSync#notify 中,核心代码就是这一段

if (URL_IDS_MAPPER.containsKey(url.toFullString())) {
    ids.put(URL_IDS_MAPPER.get(url.toFullString()), url);
} else {
    String md5 = CoderUtil.MD5_16bit(url.toFullString());
    ids.put(md5, url);
    URL_IDS_MAPPER.putIfAbsent(url.toFullString(), md5);
}

简单来说就是 URL_IDS_MAPPER一直在增长,导致它占用的内存越来越越大,最后导致不停的fullGC

分析

1、什么情况下会执行这个方法? 当/dubbo下的节点发生变更的时候

2、URL_IDS_MAPPER的本意只是想维护一个 md5 与 fullUrl 的关系,但因为控制不当,导致它的容量不断增长,感觉这个URL_IDS_MAPPER完全没有必要

3、控制不当指的是什么? 比如每次提供者或者消费者 上线 -> 下线 -> 上线,虽然该服务一直都只有一个实例,但却产生了多个MD5,如果频繁的进行这个操作,就会导致URL_IDS_MAPPER容量越来越大

4、与URL_IDS_MAPPER对应的其实还有一个 'registryCache',但为什么 'registryCache'没有内存泄漏问题? 因为在该方法中有针对'registryCache'的清除操作

5、怎么发现的这个问题? 如果服务量比较小,服务变更不频繁,可能感知不到这个问题。但如果服务量大又经常上下线,这个问题就很明显了。会发现应用占用的内存越来大,到后面服务器就一直在fullGC了

6、怎么排查?

  1. top
  2. top -p pid -H
  3. jstatck pid |grep 0xxx
  4. 看GC
  5. 内存DUmp,
  6. MAT分析:查看大对象,发现URL_IDS_MAPPER中元素有100万
  7. 分析代码

频繁YGC

背景

内存溢出问题是解决了,但却遇到一个新的问题:YGC太频繁。YGC太频繁主要体现在两个方面:

  1. 应用启动时:发生 300 ~ 500 次YGC
  2. 应用运行时:无规律频繁YGC。稳定一段时间没有GC然后突然一段时间频繁GC

结论

1、dubbo-admin的特点 本质上就是对注册中心上的信息的查询与修改! 在dubbo-admin,为了能及时的感知到注册中心信息的变化,它通过ZK的监听机制来实现。但它没有用原生的API,而是借助dubbo提供的ZookeeperRegistry和NotifyListener来实现,简单来说就是监听/dubbo下的节点。

2、dubbo里面有一个服务信息缓存机制,目的在注册中心挂了的情况下,已缓存的服务实例还可以调用,新的服务实例无法被调用。就是一种容错机制吧。该缓存文件的更新时机是在服务节点发生变更的时候,即 AbstractRegistry#notify 方法中,即:当服务节点发生变更的时候,需要更新本地缓存文件。频繁YGC就是因为这个造成的。

3、为什么正常的dubbo应用没有这个问题而dubbo-admin有这个问题? 正常应用监听的时候 /dubbo/接口名 下面的节点;dubbo-admin 监听的是 /dubbo 节点,相当于是所有的服务

分析过程

YGC太频繁问题不太好排查,因为太频繁了,内存dump中可能没有你想要的信息

  1. 查看gc情况,full平稳,ygc频繁
  2. 查看对情况,老年代使用很少,新生代很容易满,接着触发YGC
  3. 尝试将新生代容量调大一些,YGC次数少了一些,但还是很频繁,说明问题不在这里
  4. dump了几次内存,但dump出来的东西很少,被回收了
  5. 借助jvisualvm工具,gc插件和 性能分析,可以查看某个线程占用的内存情况
  6. 发现DubboSaveRegistryCache-thread-1这个线程有时候突然就会产生大量的对象,根据线程名找到对应的代码在AbstractRegistry类中,查看代码逻辑,找到原因

1、刚开始时的GC情况(新生代1200M),频繁GC image.png

2、优化后的GC情况(新生代1200M),新生代缓慢增长,新生代容量较大,GC频率少,但每次GC耗时更长 image.png

3、优化后的GC情况(新生代512M),新生代更小了,GC频率稍微快一点,但是每次的GC耗时比较低 image.png

4、优化后的GC情况(新生代512M),自己手动模拟请求,发现GC频率加快,符合预期 image.png

5、优化后的GC情况(新生代512M),这个点应该是大家都去吃饭了,没有服务信息变更 image.png

解决方案: 这个缓存机制完全是为了容错考虑,dubbo-amind根本不需要用到这个特性,把这个功能关闭就好了