在本设计内,我们主要涉及的内容为Dubbo的指标体系,并且需要设计的内容总共涉及三块,指标收集、本地聚合、指标推送。
指标收集:我们将Dubbo内部需要监控的指标推送至统一的Collector中进行存储。
本地聚合:指标收集获取的均为基础指标,而一些分位数指标则需通过本地聚合计算得出。
指标推送:收集和聚合后的指标通过一定的方式推送至第三方服务器,本文档只涉及Prometheus。
我们将原来的MetricsConfig配置重构后供可观测性使用,并且只有在柔性服务开启或者Metrics,Config配置存在的情况下才会开启指标收集的功能。
Java 提供的静态变量(static field)能力可以将持有对象引用的行为绑定到类上面来,这给开发者提供了巨大的便利。注入单例模式、工厂模式等设计模式的实现方案都依赖了静态变量的功能。通过使用静态变量,开发者可以在任何时间、任何地点简单地获取到所需要的对象信息。
public class Test { public static Object obj; } Test.obj = xxx;
在一直以来的 Dubbo 框架开发中,静态变量受到了广泛地应用,诸如使用一个全局共享的 ConfigManager 来存储全局配置信息、ServiceRepository 来存储服务信息,不论从中心化管理配置或者是参数获取的便利性的角度来说,这种设计都是最佳的。在 Dubbo 2.7 以前的所有版本,Dubbo 所需要的运行时配置信息都通过全局静态变量获取,通过 RPC 服务三元组(interface + version + group)的方式进行唯一定位。
但是随着 Dubbo 用户基数的不断扩大以及在阿里集团内由 Dubbo 作为内核的 HSF3 框架都对原来的这种设计模式提出了挑战。
对于开源用户,社区收到的诉求主要包括以下几点:
对于阿里集团内大规模落地来说,我们遇到的问题主要有:
基于众多的这些原因,在八月初的时候我们决定对 Dubbo 的生命周期进行重构,经过一个月的紧张开发,目前社区版本已经完整支持了多实例化的功能,Dubbo 的生命周期也变得更加清晰。
整个 Dubbo 多实例的设计我们按照了三层模型来配置,分别是 Framework 框架层、Application 应用层、Module 模块层。
基于三层机制,我们可以将 Dubbo 按照一定规则进行隔离:
为了实现 Dubbo 多实例化,Dubbo 框架内做的最多的变化是修改掉大部分的从静态变量中获取的参数的逻辑,最明显的逻辑是 Dubbo 内部用于参数传递的 URL 对象带上了 ScopeModel 状态,这个 ScopeModel 对应的就是上面提到的三层模型的具体数据承载对象。
多实例重构版本之后的 Dubbo 对于大多数用户的使用来说是无感知的,改造后的 DubboBootstrap 已经变成一个独立的启动器,用户可以通过 DubboBootstrap 定制多实例的使用。
下面是使用多实例的一个简单的例子。
ServiceConfig<DemoService> service = new ServiceConfig<>(); service.setInterface(DemoService.class); service.setRef(new DemoServiceImpl()); ReferenceConfig<DemoService> reference1 = new ReferenceConfig<>(); reference1.setInterface(DemoService.class); ReferenceConfig<DemoService> reference2 = new ReferenceConfig<>(); reference2.setInterface(DemoService.class); // 创建一个启动器(自动创建新 ApplicationModel) DubboBootstrap bootstrap1 = DubboBootstrap.newInstance(); // 指定应用名 bootstrap1.application(new ApplicationConfig("dubbo-demo-app-1")) .registry(new RegistryConfig("nacos://localhost:8848")) // 创建一个模块 .newModule() // 在模块内发布服务 .service(service) .endModule() // 创建一个模块 .newModule() // 在模块内订阅服务 .reference(reference1) .endModule() .start(); // 创建一个启动器(自动创建新 ApplicationModel) DubboBootstrap bootstrap2 = DubboBootstrap.newInstance(); // 指定应用名 bootstrap2.application(new ApplicationConfig("dubbo-demo-app-2")) .registry(new RegistryConfig("nacos://localhost:8848")) // 创建一个模块 .newModule() // 在模块内订阅服务 .reference(reference2) .endModule() .start(); // stub1 与 stub2 是两个独立的订阅,互相隔离 // 订阅的 stub DemoService stub1 = reference1.get(); System.out.println(stub1.sayHello("Hello World!")); // 订阅的 stub DemoService stub2 = reference2.get(); System.out.println(stub2.sayHello("Hello World!")); bootstrap1.stop(); bootstrap2.stop();
这个例子对外发布了一个 DemoService 的服务,由 dubbo-demo-app-1 这个应用提供。同时我们创建了两个订阅,分别是在 dubbo-demo-app-1 应用和 dubbo-demo-app-2 应用中,然后我们去对两个订阅进行调用,得到预期的结果。
这里需要注意的是虽然两个订阅的服务信息是完全一致的,在多实例化改造后,这两个订阅对于消费端来说是完全隔离的,也就是在最新版本的 Dubbo 中是支持三元组一样的情况下通过变更参数来创建多个订阅的行为的了。