从源码解析dubbo的服务注册
一、引言
对于 Java
开发者而言,关于 dubbo
,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。
但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。
本期 dubbo
源码解析系列文章,将带你领略 dubbo
源码的奥秘
本期源码文章吸收了之前 Spring
、Kakfa
、JUC
源码文章的教训,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。
虽然现在是互联网寒冬,但乾坤未定,你我皆是黑马!
废话不多说,发车!
二、环境配置
本篇文章适合对 dubbo
有兴趣 & 日常工作中有使用的人
环境配置:
- dubbo版本:3.1.8
- maven版本:3.5.4
- JDK版本:JDK8
- Zookeeper版本:3.4.9
因为服务数据是注册在 Zookeeper
上的,所以需要一个 Zookeeper
的可视化界面:ZooInspector
当然,就算你上述环境配置不全,也不影响你本篇文章的阅读体验。
三、dubbo历史及架构
在讲述服务注册之前,我们先聊一聊 dubbo
的历史和架构
1、历史
Dubbo
是一个高性能的、基于 Java
的 RPC
框架。它最初是由阿里巴巴集团开发的,用于支持其大规模的分布式服务架构。
以下是Dubbo
的历史:
- 2011年:
Dubbo
项目由阿里巴巴集团内部团队开始开发 - 2012年:
Dubbo
在GitHub
上开源发布 - 2013年:
Dubbo
进入Apache
孵化器,成为Apache
的一个孵化项目 - 2014年:
Dubbo
正式成为Apache顶级项目 - 2016年:
Dubbo
被阿里巴巴集团捐赠给了Apache基金会 - 2018年:
Dubbo
发布了2.7.x
版本,增加了很多新特性和改进 - 2021年:
Dubbo
发布了2.7.8
版本 - 2023年:``Dubbo
发布了
3.2.0` 版本
目前,Dubbo
仍然是业界广泛使用的 RPC
框架之一,得到了众多企业和开发者的支持和认可。
2、架构
dubbo
最经典的还是下面这张架构图,简单描述了 dubbo
整个的运行逻辑
运行流程:
1、服务端启动将服务以 ip:port
的形式注册至注册中心
2、消费端启动时去注册中心一次性订阅所有的注册接口
3、当服务端的接口发生变化时,通知注册中心,由注册中心通知我们的消费端更改接口信息
4、消费端通过负载均衡调用注册接口
5、服务端和消费端调用完成之后,将请求的数据次数通过监控中心上报
四、服务注册
大家初次听到这个名词,可能有点好奇,服务注册是什么鬼?
简单来说:服务端启动将服务信息注册到Zookeeper的过程
假设我们是一个重来没有接触过 dubbo
源码的人,如果你是 dubbo
的创作者,你会怎么去实现?
我提一下我的想法:
- 第一:
dubbo
背靠Spring
实现的,肯定要和Spring
有关 - 第二:
dubbo
的引入有两种:注解 和 xml,所以第一步肯定是解析注入 - 第三:通过
Spring
的机制,将我们前面注入的BeanDefinition
进行实例化 - 第四:获取
Zookeeper
的地址,将服务信息注册上去
我们来看看 dubbo
如何实现的?
4.1 服务配置
我们简单配置一个服务:
启动类:
@EnableDubbo @SpringBootApplication public class DubboProviderApplication { public static void main(String[] args) { SpringApplication.run(DubboProviderApplication.class); } }
暴露接口和实现类:
public interface IUserService { User getUserById(Long id); } @DubboService(timeout = 100, version = "1.0.0.dev") public class UserServiceImpl implements IUserService { @Override public User getUserById(Long userId) { User user = User(2); log.info("服务获取用户信息:{}",user); return user; } }
4.2 注解扫描
在我们启动的时候,EnableDubbo
会自动进行扫描,其功能主要注解包括:@EnableDubboConfig 和 @DubboComponentScan
4.2.1 EnableDubboConfig
@Import(DubboConfigConfigurationRegistrar.class) public @interface EnableDubboConfig {}
这个注册引入了 DubboConfigConfigurationRegistrar.class
,我们直接看这个类的实现
public class DubboConfigConfigurationRegistrar implements ImportBeanDefinitionRegistrar { // 删减了入参 public void registerBeanDefinitions() { DubboSpringInitializer.initialize(registry); } } public static void initialize(BeanDefinitionRegistry registry) { // 删减了部分不重要代码 // 初始化我们的dubbo initContext(context, registry, beanFactory); } private static void initContext(DubboSpringInitContext context, BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory) { // 注册DubboDeployApplicationListener DubboBeanUtils.registerCommonBeans(registry); }
下面就是我们 EnableDubboConfig
注解注册的一些类信息:
- ReferenceAnnotationBeanPostProcessor:解析和处理
@Reference
注解
- 注意:这个@Reference注解是消费端使用的!!!服务端用不到!!!
- DubboDeployApplicationListener:注册监听器,初始化
dubbo
服务 - DubboConfigApplicationListener:加载
dubbo
的配置
我们后面也会聊到
static void registerCommonBeans(BeanDefinitionRegistry registry) { // 解析和处理@Service和@Reference注解 registerInfrastructureBean(registry, ReferenceAnnotationBeanPostProcessor.BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class); // 注册监听器 registerInfrastructureBean(registry, DubboDeployApplicationListener.class.getName(), DubboDeployApplicationListener.class); //加载配置 registerInfrastructureBean(registry, DubboConfigApplicationListener.class.getName(), DubboConfigApplicationListener.class); }
4.2.2 DubboComponentScan
主要注册了 ServiceAnnotationPostProcessor
该类:
- ServiceAnnotationPostProcessor:扫描对应
@DubobService
形成实例
@Import(DubboComponentScanRegistrar.class) public @interface DubboComponentScan {} public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 初始化bean DubboSpringInitializer.initialize(registry); // 获取@EnableDubbo和@DubboComponentScan注解的包路径 Set<String> packagesToScan = getPackagesToScan(importingClassMetadata); // 注册一个后置处理器,这个ServiceAnnotationPostProcessor // 用于扫描对应@DubobService形成实例 registerServiceAnnotationPostProcessor(packagesToScan, registry); }
通过上述的注解,我们知道了以下逻辑关系:
通过我们之前 Spring
的学习,我们应该有一个他们几个类执行的一个大概的逻辑:
ServiceAnnotationPostProcessor
:Spring
后置处理器的扩展,最开始执行的DubboDeployApplicationListener
:Spring
容器初始化完成后,发送事件,开始执行DubboConfigApplicationListener
:Spring
容器初始化完成后,发送事件,开始执行
那下面我们一起看看这几个类源码如何实现
4.3 服务实例化
通过 ServiceAnnotationPostProcessor
将服务进行实例化,主要由以下几方面:
- 扫描我们的注解(@DubboService、@Service)配置
- 先扫描实现类,将实现类封装成
BeanDefinition
注册至BeanDefinitionMap
中 - 通过我们的实现类找到接口类,将接口类也封装成
BeanDefinition
注册至BeanDefinitionMap
中
整体流程图如下:
源码解析如下:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry){ this.registry = registry; scanServiceBeans(resolvedPackagesToScan, registry); } private void scanServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) { scanned = true; // 生成扫描器 DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader); BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry); scanner.setBeanNameGenerator(beanNameGenerator); // 将下面三个注解放到扫描器里面 // @since 2.7.7 DubboService.class, // @since 2.7.0 Service.class, // @since 2.7.3 com.alibaba.dubbo.config.annotation.Service.class for (Class<? extends Annotation> annotationType : serviceAnnotationTypes) { scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType)); } ScanExcludeFilter scanExcludeFilter = new ScanExcludeFilter(); scanner.addExcludeFilter(scanExcludeFilter); // 遍历所有的路径进行扫描 for (String packageToScan : packagesToScan) { // 1、扫描路径下的注解(DubboService、Service) // 2、将注解的配置生成BeanDefinition(相比正常的BeanDefinition,这里添加了一些注解自定义配置) // 3、将BeanDefinition注册至BeanDefinitionMap中 // 注意:这里只能扫描到实现类(UserServiceImpl),没办法扫描接口(IUserService) scanner.scan(packageToScan); // 这里去扫描接口(IUserService) Set<BeanDefinitionHolder> beanDefinitionHolders = findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator); // 遍历所有的实现类(UserServiceImpl、...) for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders){ // 1、根据实现类找到其接口(IUserService) // 2、拿到实现类@DubboService的配置(version->1.0.0.dev、timeout->100) // 3、获取bean的名称:ServiceBean:com.msb.common.service.IUserService:1.0.0.dev:group // 4、初始化IUserService的BeanDefinition(interface/protocol/registry/ref)参数等 // 5、注册至BeanDefinitionMap中 // 注意:这里有一个ref参数,指向我们实现类 processScannedBeanDefinition(beanDefinitionHolder); servicePackagesHolder.addScannedClass(beanDefinitionHolder.getBeanDefinition().getBeanClassName()); } servicePackagesHolder.addScannedPackage(packageToScan); } }
我们看到,在我们的接口类里面有一个 ref
方法指向我们的实现类:
简单来理解,当我们消费端去调用接口时,由服务端接口类通过 ref
寻找指定的 实现类
4.4 服务配置初始化
整体流程如下:
通过 DubboConfigApplicationListener
进行服务的配置加载
public void onApplicationEvent(DubboConfigInitEvent event) { if (nullSafeEquals(applicationContext, event.getSource())) { // CAS只允许一个线程来初始化配置 if (initialized.compareAndSet(false, true)) { initDubboConfigBeans(); } } } private void initDubboConfigBeans() { applicationContext.getBean(DubboConfigBeanInitializer.BEAN_NAME, DubboConfigBeanInitializer.class); // 配置初始化 moduleModel.getDeployer().prepare(); } public void prepare() { applicationDeployer.initialize(); this.initialize(); }
这里可以看到分成了两个 initialize
,我们分别看一下其实现
4.4.1 applicationDeployer.initialize
- registerShutdownHook:注册
Dubbo
的JVM
关闭钩子,当JVM
关闭时会自动调用Dubbo
的销毁方法 - startConfigCenter:初始化
Dubbo
的配置中心(Zookeeper),从Dubbo
的配置文件中读取配置中心地址,根据配置初始化Dubbo
的配置中心 - loadApplicationConfigs:加载
Dubbo
的配置信息,用于初始化Dubbo
的各个组件(监控、协议、元数据、指标等) - initModuleDeployers:初始化
Dubbo
模块部署器,该方法会根据Dubbo
配置信息初始化相应的模块部署器,注册上下文 - startMetadataCenter:方法用于启动
Dubbo
的元数据中心
public void initialize() { if (initialized) { return; } // 锁一下 synchronized (startLock) { if (initialized) { return; } // 注册Dubbo应用程序的JVM关闭钩子 registerShutdownHook(); // 初始化Dubbo的配置中心 startConfigCenter(); // 加载Dubbo应用程序的配置信息 loadApplicationConfigs(); // 初始化Dubbo应用程序的模块部署器, initModuleDeployers(); // 启动Dubbo的元数据中心 startMetadataCenter(); initialized = true; } }
4.4.2 this.initialize
- 初始化
Dubbo
模块,并加载Dubbo
模块的配置信息和元数据信息 - 读取
Dubbo
模块的ModuleConfig
配置,并根据配置设置Dubbo
模块的参数,例如是否异步导出服务、是否异步引用服务、是否在后台线程中启动服务
public void initialize() throws IllegalStateException { if (initialized) { return; } synchronized (this) { if (initialized) { return; } loadConfigs(); ModuleConfig moduleConfig = moduleModel.getConfigManager().getModule().orElseThrow(() -> new IllegalStateException("Default module config is not initialized")); exportAsync = Boolean.TRUE.equals(moduleConfig.getExportAsync()); referAsync = Boolean.TRUE.equals(moduleConfig.getReferAsync()); background = moduleConfig.getBackground(); if (background == null) { background = isExportBackground() || isReferBackground(); } initialized = true; if (logger.isInfoEnabled()) { logger.info(getIdentifier() + " has been initialized!"); } } }
4.4 服务注册
我们本篇简单的介绍下 服务注册 的过程,具体细节下一篇文章我们细聊
五、总结
鲁迅先生曾说:独行难,众行易,和志同道合的人一起进步。彼此毫无保留的分享经验,才是对抗互联网寒冬的最佳选择。
其实很多时候,并不是我们不够努力,很可能就是自己努力的方向不对,如果有一个人能稍微指点你一下,你真的可能会少走几年弯路。