转载

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

发现问题

最近dubbo发布了2.7.1版本,尝试将2.7.0版本升级至2.7.1,原以为版本号调整就可以轻松完成。版本调整完成后发布至测试环境,客户端尝试调用却报错了。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

查看异常堆栈发现客户端居然调用的url居然是一个本地(127.0.0.1)地址,检查注册中心等配置都没发现异常。继续追查服务提供方(provider),在服务端的启动日志中发现了端倪。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

服务端发布的地址居然是127.0.0.1,查看zookeeper中注册的信息确认了服务发布的url确实存在问题。

将dubbo版本回退至2.7.0,重启服务,发现服务发布的url又变回了内网地址(192.168.158.3)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

同时客户端又恢复正常

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

难道这次版本升级就要结束了么,心里还是有那么点不服气,决定跟踪下源码看看问题出在哪里。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

问题追踪

先从 2.7.0版本 开始入手,由主入口开始追踪,主要寻找dubbo发布服务时获取ip的关键地方,发现dubbo是借助ServiceBean( 实现了ApplicationListener接口 )来监听事件ContextRefreshedEvent,Spring容器启动完成后会发布 ContextRefreshedEvent事件,dubbo通过监听该事件来进行服务发布。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

监听 ContextRefreshedEvent事件,如符合条件则调用export方法。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

export方法内调用了父类方法,如不需延迟发布则直接调用doExport方法。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

在方法doExportUrlsFor1Protocol中部可以找到获取host的方法findConfigedHosts

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

尝试在protocol配置中获取,如有配置绑定ip将直接使用;如protocol未配置ip则参数在provider配置中获取,如果 provider配置中也未配置绑定的ip,则使用InetAddress.getLocalHost().getHostAddress()获取本地ip。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

此时InetAddress.getLocalHost().getHostAddress()获取到的ip为127.0.0.1,方法isInvalidLocalHost认为ip不合法,继续查找。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

尝试通过Socket连接注册中心,建立成功后获取本地IP,如果仍获取不到再借 助NetUtils. L ocalHost()方法获取。现在 已经获取到了ip是192.168.158.3。此时,顺便看下如果无法通过连接registryURL时是如何通过 NetUtils. get L ocalHost ()方法获取ip的。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

如果address不是回送地址(127.xxx.xxx.xxx)并且不为0.0.0.0、127.0.0.1,满足ip规范则视为有效IP。 以上是dubbo2.7.0版本发布服务获取ip的过程。

追踪比较

下面我们再看看 dubbo2.7.1版本中存在哪些不同。我们同样追踪至doExportUrlsFor1Protocol中的findConfigedHosts方法。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

此时我们发现2.7.1版本中的ip获取操作,省去了尝试使用socket连接注册中心的方式获取,应该是为了减少不必要的开销,直接使用了 NetUtils.get L ocalHost()方法获取ip,我们再继续看看这个方法的实现是否有调整,直到getLocalAddress0我们可以看到新版本的实现抽取了一些重复代码,但主流程没有太大的改变,还是先尝试InetAddress.getLocalHost()验证ip是否合法,否则再遍历网卡获取ip。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

我们再看看toValidAddress方法,可以发现增加了一个isValidPublicAddress校验。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

这时候我们可以看到isValidPublicAddress方法内多一些不一样的东西,与2.7.0中校验的实现相比,增加了address.isSiteLocalAddress()校验。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

address.isSiteLocalAddress()主要是判断是否是 私有网络地址。(出于什么考虑增加该判断呢?)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

查看 Inet4Address中的实现判断的是是否是10/8、172.16/12、192.168/16前缀的ip地址, 到这里可以发现之前局域网IP(192.168.158.3)刚好在此范围内,因此被视为无效地址。最终因为在InetAddress.getLocalHost()和网卡地址中均找不到合法的ip地址,最后选择了最先通过InetAddress.getLocalHost()获取到的127.0.0.1,导致服务发布出现了发布地址是本地地址的情况。

对比

重新比较下dubbo2.7.0和2.7.1的 NetUtils.get L ocalHost()实现。借助以下demo:

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

最终确认是因为 NetUtils.get L ocalHost()实现增加了 私有网络地址的判断,导致局域网ip被视为无效,从而导致新版本dubbo在发布时采用了本地地址(127.0.0.1)。

尝试解决

因为新版本实现最终选择使用 InetAddress.getLocalHost()获取到的ip作为发布地址,那么为什么 InetAddress.getLocalHost()获取到的是127.0.0.1?而不是192.168.158.3呢?

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

查看 InetAddress.getLocalHost()的实现发现,其中读取了服务器的hostname,并通过hostname在hosts文件中查找对应的ip。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

查看服务器/etc/sysconfig/network文件发现,hostname确实是localhost.localdomain,再查看/etc/hosts文件确认其对应为127.0.0.1。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1) 尝试思路

调整hostname及hosts文件名称关联来让dubbo获取到局域网ip

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

调整hostname为lizp-test,并在hosts文件中建立192.168.158.3 lizp-test,重启使配置生效。

发现重新执行NetUtils.getLocalAddress()可以获取到ip192.168.158.3,重启demo也可以发布服务了。

记一次 Dubbo 版本升级问题(2.7.0 -> 2.7.1)

思考

在这次排查追踪经历,有以下几点总结与思考:

  1. 在进行通用组件升级时,必须充分了解其中版本差异及有充分测试,否则将导致出现不可预料的问题。

  2. 阅读源码是了解框架及排查问题最有效的方法。

  3. 另值得思考的是, NetUtils.get L ocalHost()新版的实现中加入了 isSiteLocalAddress的考量是什么?(暂时还未领悟)

  4. 除了通过借助hostname、hosts文件的方式来解决,是否还有其他方案呢?(待研究)

原文  https://mp.weixin.qq.com/s/3aY1IkYqZ-Db4iOuj3M16A
正文到此结束
Loading...