写点什么

从源码全面解析 dubbo 服务注册的来龙去脉

  • 2023-05-29
    湖南
  • 本文字数:5923 字

    阅读完需:约 19 分钟

一、引言

对于 Java 开发者而言,关于 dubbo ,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。


但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。


本期 dubbo 源码解析系列文章,将带你领略 dubbo 源码的奥秘。


本期源码文章吸收了之前 SpringKakfaJUC源码文章的教训,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。


虽然现在是互联网寒冬,但乾坤未定,你我皆是黑马!


废话不多说,发车!

二、环境配置

本篇文章适合对 dubbo 有兴趣 & 日常工作中有使用的人


环境配置:

  • dubbo 版本:3.1.8

  • maven 版本:3.5.4

  • JDK 版本:JDK8

  • Zookeeper 版本:3.4.9


因为服务数据是注册在 Zookeeper 上的,所以需要一个 Zookeeper 的可视化界面:ZooInspector

当然,就算你上述环境配置不全,也不影响你本篇文章的阅读体验。

三、dubbo 历史及架构

在讲述服务注册之前,我们先聊一聊 dubbo 的历史和架构

3.1 历史

Dubbo 是一个高性能的、基于 JavaRPC框架。它最初是由阿里巴巴集团开发的,用于支持其大规模的分布式服务架构


以下是Dubbo 的历史:

  • 2011 年:Dubbo 项目由阿里巴巴集团内部团队开始开发

  • 2012 年:DubboGitHub 上开源发布

  • 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 框架之一,得到了众多企业和开发者的支持和认可。

3.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@SpringBootApplicationpublic 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 的学习,我们应该有一个他们几个类执行的一个大概的逻辑:

  • ServiceAnnotationPostProcessorSpring 后置处理器的扩展,最开始执行的

  • DubboDeployApplicationListenerSpring 容器初始化完成后,发送事件,开始执行

  • DubboConfigApplicationListenerSpring 容器初始化完成后,发送事件,开始执行


那下面我们一起看看这几个类源码如何实现

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:注册 DubboJVM 关闭钩子,当 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 服务注册

我们本篇简单的介绍下 服务注册 的过程,具体细节下一篇文章我们细聊

五、总结

鲁迅先生曾说:独行难,众行易,和志同道合的人一起进步。彼此毫无保留的分享经验,才是对抗互联网寒冬的最佳选择。


其实很多时候,并不是我们不够努力,很可能就是自己努力的方向不对,如果有一个人能稍微指点你一下,你真的可能会少走几年弯路。


作者:爱敲代码的小黄

链接:https://juejin.cn/post/7238274120881602616

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
从源码全面解析 dubbo 服务注册的来龙去脉_Java_做梦都在改BUG_InfoQ写作社区