瓜子二手车在 Dubbo 版本升级、多机房方案方面的思考和实践
The following article is from 阿里巴巴中间件 Author 李锦涛
点击上方"IT牧场",选择"设为星标"
技术干货每日送达!
Photo @ Jude Beck
前言
一、Ephermal 节点未及时删除导致 provider 不能恢复注册的问题修复
事故背景
排查过程
public void register(URL url) {
super.register(url);
failedRegistered.remove(url);
failedUnregistered.remove(url);
try {
// Sending a registration request to the server side
doRegister(url);
} catch (Exception e) {
Throwable t = e;
// If the startup detection is opened, the Exception is thrown directly.
boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
&& url.getParameter(Constants.CHECK_KEY, true)
&& !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
boolean skipFailback = t instanceof SkipFailbackWrapperException;
if (check || skipFailback) {
if (skipFailback) {
t = t.getCause();
}
throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
} else {
logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
// Record a failed registration request to a failed list, retry regularly
failedRegistered.add(url);
}
}
在继续排查问题前,我们先普及下这些概念:Dubbo 默认使用 curator 作为ZooKeeper 的客户端, curator 与 ZooKeeper 是通过 session 维持连接的。当 curator 重连 ZooKeeper 时,若 session 未过期,则继续使用原 session 进行连接;若 session 已过期,则创建新 session 重新连接。而 Ephemeral 节点与 session 是绑定的关系,在 session 过期后,会删除此 session 下的 Ephemeral 节点。
public void createEphemeral(String path) {
try {
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (NodeExistsException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
但是实际上还有一种极端场景,ZooKeeper 的 Session 过期与删除 Ephemeral 节点不是原子性的,也就是说客户端在得到 Session 过期的消息时, Session 对应的 Ephemeral 节点可能还未被 ZooKeeper删除。此时 Dubbo 去创建 Ephemeral 节点,发现原节点仍存在,故不重新创建。待 Ephemeral 节点被 ZooKeeper 删除后,便会出现 Dubbo 认为重新注册成功,但实际未成功的情况,也就是我们在生产环境遇到的问题。
此时,问题的根源已被定位。定位问题之后,经我们与 Dubbo 社区交流,发现考拉的同学也遇到过同样的问题,更确定了这个原因。
问题的复现与修复
定位到问题之后,我们便开始尝试本地复现。由于 ZooKeeper 的 Session 过期但Ephemeral节点未被删除的场景直接模拟比较困难,我们通过修改 zookeeper 源码,在 Session 过期与删除 Ephemeral 节点的逻辑中增加了一段休眠时间,间接模拟出这种极端场景,并在本地复现了此问题。
在排查问题的过程中,我们发现 kafka 的旧版本在使用 ZooKeeper 时也遇到过类似的问题,并参考 Kafka 关于此问题的修复方案,确定了 Dubbo 的修复方案。在创建 Ephemeral 节点捕获到 NodeExistsException 时进行判断,若 Ephemeral 节点的 SessionId 与当前客户端的 SessionId 不同,则删除并重建 Ephemeral 节点。在内部修复并验证通过后,我们向社区提交了 issues 及 pr 。
Kafka 类似问题 issues :
https://issues.apache.org/jira/browse/KAFKA-1387
Dubbo 注册恢复问题 issues:
https://github.com/apache/dubbo/issues/5125
二、瓜子的dubbo升级历程
上文中的问题修复方案已经确定,但我们显然不可能在每一个 Dubbo 版本上都进行修复。在咨询了社区 Dubbo 的推荐版本后,我们决定在 Dubbo2.7.3 版本的基础上,开发内部版本修复来这个问题。并借这个机会,开始推动公司 Dubbo 版本的统一升级工作。
为什么要统一 Dubbo版本
1、统一 Dubbo 版本后,我们可以在此版本上内部紧急修复一些 Dubbo 问题(如上文的 Dubbo 注册故障恢复失效问题)。
2、瓜子目前正在进行第二机房的建设,部分 Dubbo 服务也在逐渐往第二机房迁移。统一 Dubbo 版本,也是为 Dubbo 的多机房做铺垫。
3、有利于我们后续对 Dubbo 服务的统一管控。
4、Dubbo 社区目前的发展方向与我们公司现阶段对Dubbo 的一些诉求相吻合,如支持 gRPC 、云原生等。
为什么选择 Dubbo2.7.3
1、我们了解到,在我们之前携程已经与 Dubbo 社区合作进行了深度合作,携程内部已全量升级为 2.7.3 的社区版本,并在协助社区修复了 2.7.3 版本的一些兼容性问题。感谢携程的同学帮我们踩坑~
2、Dubbo2.7.3 版本在当时虽然是最新的版本,但已经发布了 2 个月的时间,从社区issues反馈来看,Dubbo2.7.3 相对 Dubbo2.7之前的几个版本,在兼容性方面要好很多。
3、我们也咨询了 Dubbo 社区的同学,推荐升级版本为 2.7.3 。
内部版本定位
基于社区 Dubbo2.7.3 版本开发的 Dubbo 内部版本属于过渡性质的版本,目的是为了修复线上 provider 不能恢复注册的问题,以及一些社区 Dubbo2.7.3 的兼容性问题。瓜子的 Dubbo 最终还是要跟随社区的版本,而不是开发自已的内部功能。因此我们在 Dubbo 内部版本中修复的所有问题均与社区保持了同步,以保证后续可以兼容升级到社区 Dubbo 的更高版本。
兼容性验证与升级过程
我们在向 Dubbo 社区的同学咨询了版本升级方面的相关经验后,于 9 月下旬开始了 Dubbo 版本的升级工作。
1、初步兼容性验证
首先,我们梳理了一些需要验证的兼容性 case ,针对公司内部使用较多的dubbo版本,与 Dubbo2.7.3 一一进行了兼容性验证。经验证,除 DubboX 外, Dubbo2.7.3 与其他 Dubbo 版本均兼容。DubboX 由于对 Dubbo 协议进行了更改,与 Dubbo2.7.3 不兼容。
2、生产环境兼容性验证
在初步验证兼容性通过后,我们与业务线合作,挑选了一些重要程度较低的项目,在生产环境对 Dubbo2.7.3 与其他版本的兼容性进行了进一步验证。并在内部版本修复了一些兼容性问题。
3、推动公司 Dubbo 版本升级
在 10 月初,完成了 Dubbo 兼容性验证后,我们开始在各个业务线推动 Dubbo 的升级工作。截止到 12 月初,已经有 30% 的 Dubbo 服务的完成了版本升级。按照排期,预计于 2020 年 3 月底前完成公司 Dubbo 版本的统一升级。
兼容性问题汇总
在推动升级 Dubbo2.7.3 版本的过程整体上比较顺利,当然也遇到了一些兼容性问题:
1、创建 ZooKeeper 节点时提示没有权限
Dubbo 配置文件中已经配置了 ZooKeeper 的用户名密码,但在创建 ZooKeeper 节点时却抛出 KeeperErrorCode = NoAuth 的异常,这种情况分别对应两个兼容性问题:
a. issues:
https://github.com/apache/dubbo/issues/5076
dubbo在未配置配置中心时,默认使用注册中心作为配置中心。通过注册中心的配置信息初始化配置中心配置时,由于遗漏了用户名密码,导致此问题。
b. issues:
https://github.com/apache/dubbo/issues/4991
Dubbo 在建立与 ZooKeeper 的连接时会根据 ZooKeeper 的 address 复用之前已建立的连接。当多个注册中心使用同一个 address ,但权限不同时,就会出现 NoAuth 的问题。参考社区的 PR ,我们在内部版本进行了修复。
2、curator 版本兼容性问题
a. Dubbo2.7.3 与低版本的 curator 不兼容,因此我们默认将 curator 版本升级至4.2.0
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
b. 分布式调度框架 elastic-job-lite 强依赖低版本的 curator ,与 Dubbo2.7.3 使用的 curator 版本不兼容,这给 Dubbo 版本升级工作带来了一定阻塞。考虑到 elastic-job-lite 已经很久没有人进行维护,目前一些业务线计划将 elastic-job-lite 替换为其他的调度框架。
3、 OpenFeign 与 Dubbo 兼容性问题
issues:
https://github.com/apache/dubbo/issues/3990
Dubbo 的 ServiceBean 监听 Spring 的 ContextRefreshedEvent ,进行服务暴露。OpenFeign提前触发了 ContextRefreshedEvent ,此时 ServiceBean 还未完成初始化,于是就导致了应用启动异常。
参考社区的pr,我们在内部版本修复了此问题。
4、RpcException兼容性问题
Dubbo低版本consumer不能识别dubbo2.7版本provider抛出的org.apache.dubbo.rpc.RpcException。因此,在consumer全部升级到2.7之前,不建议将provider的com.alibaba.dubbo.rpc.RpcException改为org.apache.dubbo.rpc.RpcException
5、QoS 端口占用
Dubbo2.7.3 默认开启 QoS 功能,导致一些混部在物理机的 Dubbo 服务升级时出现 qos 端口占用问题。关闭 QoS 功能后恢复。
6、自定义扩展兼容性问题
业务线对于 Dubbo 的自定义扩展比较少,因此在自定义扩展的兼容性方面暂时还没有遇到比较难处理的问题,基本上都是变更 package 导致的问题,由业务线自行修复。
7、Skywalking agent 兼容性问题
我们项目中一般使 Skywalking进行链路追踪,由于Skywalking agent6.0 的 plugin 不支持 Dubbo2.7 ,因此统一升级 Skywalking agent 到 6.1 。
三、Dubbo 多机房方案
瓜子目前正在进行第二机房的建设工作,Dubbo 多机房是第二机房建设中比较重要的一个话题。在 Dubbo 版本统一的前提下,我们就能够更顺利的开展 Dubbo 多机房相关的调研与开发工作。
初步方案
我们咨询了 Dubbo 社区的建议,并结合瓜子云平台的现状,初步确定了 Dubbo 多机房的方案。
1、在每个机房内,部署一套独立的 ZooKeeper 集群。集群间信息不同步。这样就没有了 ZooKeeper 集群跨机房延迟与数据不同步的问题。
2、Dubbo 服务注册时,仅注册到本机房的 ZooKeeper 集群;订阅时,同时订阅两个机房的 ZooKeeper 集群。
3、实现同机房优先调用的路由逻辑。以减少跨机房调用导致的不必要网络延迟。
同机房优先调用
Dubbo 同机房优先调用的实现比较简单,相关逻辑如下:
1、瓜子云平台默认将机房的标志信息注入容器的环境变量中。
2、 provider 暴露服务时,读取环境变量中的机房标志信息,追加到待暴露服务的url中。
3、 consumer 调用 provider 时,读取环境变量中的机房标志信息,根据路由策略优先调用具有相同标志信息的 provider 。
针对以上逻辑,我们简单实现了 Dubbo 通过环境变量进行路由的功能,并向社区提交了 PR 。
Dubbo 通过环境变量路由 PR :
https://github.com/apache/dubbo/pull/5348
干货分享
最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!
•001:《Java并发与高并发解决方案》学习笔记;•002:《深入JVM内核——原理、诊断与优化》学习笔记;•003:《Java面试宝典》•004:《Docker开源书》•005:《Kubernetes开源书》•006:《DDD速成(领域驱动设计速成)》•007:全部•008:加技术讨论群
近期热文
•如何提高服务器并发处理能力?•太神奇的 SQL 查询经历,group by 慢查询优化!•SpringBoot+RabbitMQ ,保证消息100%投递成功并被消费(附源码)•Java 并发异步编程,原来十个接口的活现在只需要一个接口就搞定!•初探性能优化--2个月到4小时的性能提升!•关于数据库分库分表的一切都在这里了。
想知道更多?长按/扫码关注我吧↓↓↓