01
背景
Aliware
Dubbo 3.0 在阿里巴巴各条业务线包括淘宝、考拉、饿了么、钉钉、达摩院等已全量或开始分批上线,社区企业如小米、工商银行、平安健康等也纷纷引入了 3.0 版本的新特性。支持如此大规模的体量的业务平稳运行,对 Dubbo 3.0 的稳定性有很高的要求,尤其是考虑到社区极高的活跃度,这个挑战就更大了。目前社区有超过 600 位 contributor,近一年以来每天有大约 60 个 commits,陆续支持了 Triple 协议、多实例等功能,这些功能都会涉及到大量的代码变更。每一次提交代码的质量对 Dubbo 3.0 版本的质量、稳定性、可靠性等方面有着至关重要的影响。
02
现状
Aliware
Dubbo 保证提交代码的质量主要体现在以下几个方面:
dubbo 代码仓库中的单测
dubbo-samples 中的集成测试
dubbo-benchmark 中的性能测试
code review 机制
其中,dubbo 的单测主要针对 Dubbo 代码单元的独立测试,侧重点在功能、分支覆盖等方面,是最基本的测试。dubbo-samples 主要是对 Dubbo 中常用的功能组合和场景进行测试,同时也会对齐 2.7.x 和 3.0 版本之间的功能。dubbo-benchmark 主要是在每次有重大版本升级变更的时候对性能进行压测。code review 机制是在每一次提交的代码都通过所有的单测和集成测试以后由社区的 contributor 和 committer 对提交的 pr 进行 review,当大家的意见不一致会在社区内进行讨论最终达成一致。
03
问题
Aliware
集成测试和 benchmark 测试都是独立的代码仓库,进过 Dubbo 社区多年的演进,相对来说变更较少已经比较稳定了。code review 机制主要是通过人的主观判断来对代码的质量进行评估,Dubbo 社区一直在不断吸纳更多优秀的 contributor 加入,理论上这方面的质量是在不断提高的。相对而言,dubbo 的单测集中在 100 多个 module 中,是变更最多、最频繁的,也是最容易出问题的地方。
目前 dubbo 的单测存在以下几个问题:
01
耗时长
dubbo 的单测会在 Ubuntu 和 Windows 上分别对 JDK8 和 11 进行测试,其中 Ubuntu 和 Windows 上运行单测的超时时间分别是 40 分钟和 50 分钟,但是我们发现经常会出现超时的情况。
02
多注册中心场景无法覆盖
dubbo 支持多注册中心,但是在单测中很难覆盖到与多注册中心相关的逻辑。dubbo 支持的注册中心包含 zookeeper、nacos、apollo 等,多种不同注册中心的混合场景更加难以覆盖。
04
分析
Aliware
通过对单测运行日志进行抽查和分析我们发现,dubbo 单测的耗时主要集中在与 zookeeper 相关的 module 中。
Testcase |
Time elapsed(s) |
org.apache.dubbo.rpc.protocol.dubbo.ArgumentCallbackTest |
76.013 |
org.apache.dubbo.config.spring.ConfigTest |
61.33 |
org.apache.dubbo.config.spring.schema.DubboNamespaceHandlerTest |
50.437 |
org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessorTest |
44.678 |
org.apache.dubbo.rpc.cluster.support.AbstractClusterInvokerTest |
29.954 |
org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperClientTest |
25.847 |
org.apache.dubbo.config.spring.beans.factory.annotation.MethodConfigCallbackTest |
25.635 |
org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListenerTest |
24.866 |
org.apache.dubbo.remoting.zookeeper.curator5.Curator5ZookeeperClientTest |
19.664 |
导致这一问题的最主要原因是因为我们在写单测的时候通常会使用 zookeeper 作为注册中心和元数据中心,而在运行单测的过程中要对 zookeeper 进行频繁的 start 和 stop 操作。每一个单测希望彼此之间互不干扰,都采用了独立的 zookeeper 作为注册中心和元数据中心。当将单测并行运行时又会出现 zookeeper 端口冲突的问题。
现在需要解决的问题是:如何处理 dubbo 单测与注册中心和元数据中心的依赖问题。
05
解决方案
Aliware
这个问题看似比较简单,但是在实践过程中却发现比想象的要复杂的多,分别对各种解决方案进行了尝试:
01
TestingServer
TestingServer 是 curator-test 包中提供的专门用来 mock zookeeper 的工具类,目前 dubbo 单测中大量使用了 TestingServer。对与多注册中心的场景是通过同时启动两个 TestingServer 对外暴露不同的端口的方式来实现的。代码如下:
class ZookeeperRegistryCenter extends AbstractRegistryCenter {
/**
* Initialize the default registry center.
*/
public ZookeeperRegistryCenter(int... ports) {
this.ports = ports;
this.instanceSpecs = new ArrayList<>(this.ports.length);
this.zookeeperServers = new ArrayList<>(this.ports.length);
}
private static final Logger logger = LoggerFactory.getLogger(ZookeeperRegistryCenter.class);
/**
* The type of the registry center.
*/
private static final String DEFAULT_REGISTRY_CENTER_TYPE = "zookeeper";
private int[] ports;
private List<InstanceSpec> instanceSpecs;
private List<TestingServer> zookeeperServers;
private AtomicBoolean started = new AtomicBoolean(false);
/**
* start zookeeper instances.
*/
@Override
public void startup() throws RpcException {
try {
if (started.compareAndSet(false, true)) {
logger.info("The ZookeeperRegistryCenter is starting...");
for (int port : this.ports) {
InstanceSpec instanceSpec = this.createInstanceSpec(port);
this.instanceSpecs.add(instanceSpec);
this.zookeeperServers.add(new TestingServer(instanceSpec, true));
}
logger.info("The ZookeeperRegistryCenter is started successfully");
}
} catch (Exception exception) {
started.set(false);
throw new RpcException("Failed to initialize ZookeeperRegistryCenter instance", exception);
}
}
/**
* destroy the zookeeper instances.
*/