1、排查线上问题有什么难点?
- 虽然我们学了很多排查线上问题的理论知识,但一直没有施展的机会?
- 线上偶尔出现一次问题,但还没来得及保存现场,而后面又很难复现?
- 能够稳定复现,发现分析起来还是太困难了?没有经验,理论知识不能灵活应用,很难下手?
2、这篇文章有什么优势
- 直接基于开源框架
dubbo-admin
进行分析,不存在复杂业务,人人都可以查看源码 - 都够稳定复现问题(有关于服务量我们我完全可以手动模拟)
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、怎么排查?
- top
- top -p pid -H
- jstatck pid |grep 0xxx
- 看GC
- 内存DUmp,
- MAT分析:查看大对象,发现
URL_IDS_MAPPER
中元素有100万 - 分析代码
频繁YGC
背景
内存溢出问题是解决了,但却遇到一个新的问题:YGC太频繁。YGC太频繁主要体现在两个方面:
- 应用启动时:发生 300 ~ 500 次YGC
- 应用运行时:无规律频繁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中可能没有你想要的信息
- 查看gc情况,full平稳,ygc频繁
- 查看对情况,老年代使用很少,新生代很容易满,接着触发YGC
- 尝试将新生代容量调大一些,YGC次数少了一些,但还是很频繁,说明问题不在这里
- dump了几次内存,但dump出来的东西很少,被回收了
- 借助jvisualvm工具,gc插件和 性能分析,可以查看某个线程占用的内存情况
- 发现DubboSaveRegistryCache-thread-1这个线程有时候突然就会产生大量的对象,根据线程名找到对应的代码在AbstractRegistry类中,查看代码逻辑,找到原因
1、刚开始时的GC情况(新生代1200M),频繁GC
2、优化后的GC情况(新生代1200M),新生代缓慢增长,新生代容量较大,GC频率少,但每次GC耗时更长
3、优化后的GC情况(新生代512M),新生代更小了,GC频率稍微快一点,但是每次的GC耗时比较低
4、优化后的GC情况(新生代512M),自己手动模拟请求,发现GC频率加快,符合预期
5、优化后的GC情况(新生代512M),这个点应该是大家都去吃饭了,没有服务信息变更
解决方案: 这个缓存机制完全是为了容错考虑,dubbo-amind根本不需要用到这个特性,把这个功能关闭就好了