第二章 基于Dubbo2.7.0 分析Dubbo服务注册与发现的机制

 

阅读文章之前最好掌握dubbo的基本用法,并了解dubbo的基本设计思想。

如果希望基于Springboot搭建简单的dubbo生产者消费者,可以参考本文下面要介绍的研究时环境或之前的一篇博客:

《SpringBoot2.1.1 整合Dubbo2.6.5 实现生产者消费者最简单的案例》

一、构建学习环境

构建一套基本的dubbo生产消费环境,重点是要debug了解dubbo服务注册消费的机制和请求应答的基本数据流转过程。

基于接口编程思想,我们暴露接口服务,用实现依赖接口的方式构建我们的基本目录结构:

  • customer:消费者
  • provider:生产者
  • procider-api:生产者接口(服务暴露接口,生产者和消费者都去依赖它)

放一张dubbo官网的图说明一下:生产者消费者都是图中的两个端点,这里invoke我们实际调用的就是接口,双方都是直接调用接口而不是实现。registry就是我们的服务注册中心,也是我们重点要了解的地方。推荐大家多去看官方文档,一下是链接:

http://dubbo.apache.org/zh-cn/docs/user/preface/architecture.html

用于测试学习的代码很简单:

先配置启动你们的zookeeper。有疑问可参考   《centos单机安装zookeeper》

主pom我定义在父模块(Springboot-dubbo-base) pom中,包含各种外部jar的依赖,大家直接复制粘贴即可

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.springboot</groupId>
    <artifactId>base</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>provider</module>
        <module>customer</module>
        <module>provider-api</module>
    </modules>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.1.1.RELEASE</spring-boot.version>
        <dubbo.version>2.6.5</dubbo.version>
        <zkclient.version>0.2</zkclient.version>
        <zookeeper.version>3.4.9</zookeeper.version>
        <curator-framework.version>2.12.0</curator-framework.version>
        <netty-all.version>4.1.31.Final</netty-all.version>


        <!-- Maven plugins -->
        <maven-jar-plugin.version>3.0.2</maven-jar-plugin.version>
        <maven-compiler-plugin.version>3.6.0</maven-compiler-plugin.version>
        <maven-source-plugin.version>3.0.1</maven-source-plugin.version>
        <maven-jacoco-plugin.version>0.8.1</maven-jacoco-plugin.version>
        <maven-gpg-plugin.version>1.5</maven-gpg-plugin.version>
        <apache-rat-plugin.version>0.12</apache-rat-plugin.version>
        <maven-release-plugin.version>2.5.3</maven-release-plugin.version>
        <maven-surefire-plugin.version>2.19.1</maven-surefire-plugin.version>
        <alibaba-spring-context-support.version>1.0.2</alibaba-spring-context-support.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>${curator-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty-all.version}</version>
        </dependency>

        <!-- Dubbo -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>${dubbo.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>



        <!-- Alibaba Spring Context extension -->
        <dependency>
            <groupId>com.alibaba.spring</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${alibaba-spring-context-support.version}</version>
        </dependency>

        <!-- ZK -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>${zookeeper.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>${zkclient.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>log4j</artifactId>
                    <groupId>log4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>slf4j-log4j12</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <classifier>exec</classifier>
            </configuration>
        </plugin>
    </plugins>
    </build>
</project>

provider模块

1、pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>base</artifactId>
        <groupId>com.springboot</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>provider</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.springboot</groupId>
            <artifactId>provider-api</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

2、 结构与代码

缓存模块

package com.dubbo.provider.app.service;

import java.util.HashMap;
import java.util.Map;

/**
 * @program: com.dubbo.provider.app.service
 * @description: 缓存模块
 * @author: liujinghui
 * @create: 2019-02-24 13:09
 **/
public class Cache {

    public static final Map cacheMap = new HashMap(100);

    public Cache(){
        System.out.println("init Construct");
    }

    static{
        System.out.println("init static area");
        cacheMap.put("name","ljh");
        cacheMap.put("age","18");
    }
}

 真正的服务模块

package com.dubbo.provider.app.service;

import com.alibaba.dubbo.config.annotation.Service;
import provider.app.service.IDubboProviderService;

/**
 * @program: com.dubbo.provider.app.service
 * @description:生产者服务提供方实现类
 * @author: liujinghui
 * @create: 2019-02-24 13:04
 **/
@Service
public class DubboProviderServiceImpl implements IDubboProviderService {
    @Override
    public void doDubboService() {
        System.out.println("do Sth");
    }

    @Override
    public String getDubboService(String key) {
        Object obj =  Cache.cacheMap.get(key);
        return obj.toString();
    }
}

 启动引导类

package com.dubbo.provider;

import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableDubbo
@SpringBootApplication
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class,args);
    }
}

 配置文件

# Spring boot application
spring.application.name = dubbo-provider
server.port = 8080

# Base packages to scan Dubbo Components (e.g., @Service, @Reference)
dubbo.scan.basePackages  = com.dubbo.provider

# Dubbo Config properties
## ApplicationConfig Bean
dubbo.application.name = provider

## ProtocolConfig Bean
dubbo.protocol.name = dubbo

## RegistryConfig Bean
dubbo.registry.address = zookeeper://192.168.3.104:2181

provider-api模块

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>base</artifactId>
        <groupId>com.springboot</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>provider-api</artifactId>
</project>

 接口模块

package provider.app.service;

/**
 * 暴露的接口服务
 */
public interface IDubboProviderService {
    /**
     * 测试不带参数不带返回值的服务
     */
    public void doDubboService();

    /**
     * 测试带参数的带返回值的服务
     * @param key
     * @return
     */
    public String getDubboService(String key);
}

consumer模块

模块概览

Controller及Service实现和接口代码 All in one

package com.dubbo.customer.app.controller;

import com.dubbo.customer.app.service.IDubboCustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @program: com.dubbo.customer.app.controller
 * @description:
 * @author: liujinghui
 * @create: 2019-02-24 13:21
 **/
@Controller
public class DubboController {

    @Autowired
    private IDubboCustomerService iDubboCustomerService;


    @RequestMapping("/method1")
    public  void  method1(){
        iDubboCustomerService.callDubboServiceDoSth();
    }

    @RequestMapping("/method2")
    @ResponseBody
    public  void  method2(String key){
        System.out.println(iDubboCustomerService.callDubboServiceGetValue(key));
    }
}

=======================================================
package com.dubbo.customer.app.service;

import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
import provider.app.service.IDubboProviderService;

/**
 * @program: com.dubbo.customer.app.service
 * @description:
 * @author: liujinghui
 * @create: 2019-02-24 13:17
 **/
@Service
public class DubboCustomerServiceImpl implements IDubboCustomerService {
    @Reference
    private IDubboProviderService iDubboProviderService;

    @Override
    public void callDubboServiceDoSth() {
        iDubboProviderService.doDubboService();
    }

    @Override
    public String callDubboServiceGetValue(String key) {
        return iDubboProviderService.getDubboService(key);
    }
}

======================================================
package com.dubbo.customer.app.service;

/**
 * @program: com.dubbo.customer.app.service
 * @description:
 * @author: liujinghui
 * @create: 2019-02-24 13:17
 **/
public interface IDubboCustomerService {
    public void callDubboServiceDoSth() ;
    public String callDubboServiceGetValue(String key);
}

 启动引导类:

package com.dubbo.customer;

import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@EnableDubbo
@SpringBootApplication
public class CustomerApplication {
    public static void main(String[] args) {
        SpringApplication.run(CustomerApplication.class,args);
    }
}

pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>base</artifactId>
        <groupId>com.springboot</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>customer</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.springboot</groupId>
            <artifactId>provider-api</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

 配置文件:

spring.application.name = dubbo-customer
server.port = 8081

dubbo.scan.basePackages  = com.dubbo.customer

dubbo.application.name = consumer
dubbo.application.qosEnable = false

dubbo.protocol.name = dubbo

dubbo.registry.address = zookeeper://192.168.3.104:2181

dubbo.provider.retries = 0

dubbo.consumer.check = false

二、dubbo是如何启动加载的

先探索dubbo是如何启动的,这一点也是我比较困惑的,为什么增加@EnableDubbo注解就可以启动dubbo呢?

我们先来看看@EnableDubbo注解到底enable了什么

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
    @AliasFor(
        annotation = DubboComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = DubboComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(
        annotation = EnableDubboConfig.class,
        attribute = "multiple"
    )
    boolean multipleConfig() default false;
}

 这里可以看到除了基本元标注之外,还有@EnableDubboConfig和@DubboComponentScan

  • @EnableDubboConfig:@Import({DubboConfigConfigurationSelector.class})
  • @DubboComponentScan:@Import({DubboComponentScanRegistrar.class})

 所以下一步我们关注一下这两个class:

1、DubboConfigConfigurationSelector

它主要包括四个方法

Public

DubboConfigConfigurationSelector()

这是一个构造方法

public String[] 

selectImports(AnnotationMetadata importingClassMetadata)

这个类实现了ImportSelector

他返回的是com.alibaba.dubbo.config.spring.context.annotation.DubboConfigConfiguration$Single

个人理解就是加载DubboConfigConfiguration的bean定义和元数据。并根据配置选择单播还是组播,默认单播方式

private static <T> T[]of(T... values) return values 返回String类型数组;被selectImports调用,用于返回dubbo的配置bean(DubboConfigConfiguration)的全限定名
private int

getOrder() 数值越小,类的优先级越高

这个类实现了Ordered

return -2147483648;

2、DubboComponentScanRegistrar

它主要包括五个方法

Public

DubboComponentScanRegistrar()

这是一个构造方法

public void

registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)

主要作用是获取Set集合,集合中是本次启动要扫描的Dubbo路径。

此方法在spring启动时被执行,是因为它实现了ImportBeanDefinitionRegistrar接口,所以此类也被当成bean注册到容器中。之后他分别执行了下面的三个方法

private void

registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry)

生成ServiceAnnotationBeanPostProcessor.class的bean定义,并初始化部分属性(Role、scanpath)

注册ServiceAnnotationBeanPostProcessor.class 这个bean定义到容器

private void

registerReferenceAnnotationBeanPostProcessor(BeanDefinitionRegistry registry)

生成ReferenceAnnotationBeanPostProcessor.class的bean定义,并初始化部分属性(Role)

注册ReferenceAnnotationBeanPostProcessor.class的这个bean定义到容器

private Set<String>

getPackagesToScan(AnnotationMetadata metadata)

1、根据DubboComponentScan.class.getName()获取注解(DubboComponentScan)中的属性的AnnotationAttributes对象,可以理解为注解属性的键值对。

2、从属性中获取basePackages、basePackageClasses、value。如果没有配置这些属性,那么使用main启动类的路径代替。这也就是说跟springboot的扫描机制一致。dubbo默认扫描@EnableDubbo注解所在包下的所有类。

当执行完第一个方法后,开始加载一些bean的process方法,

这些步骤在DubboConfigBindingsRegistrar类的registerBeanDefinitions中体现。

我们可以看到有这么多的类都等着被处理,按照名字可以大概感觉得出来这些内容跟我们的Springboot中application.properties配置的dubbo属性有关,接着debug

可以看出配置文件中的dubbo的协议配置被读取加载。因此可以大概猜出这里是初始化配置信息到容器的。

顺便查看日志:通过日志可以发现有几个类被执行了加载:ApplicationConfig、RegistryConfig、ProtocolConfig

The dubbo config bean definition [name : com.alibaba.dubbo.config.ApplicationConfig#0, class : com.alibaba.dubbo.config.ApplicationConfig] has been registered.
The BeanPostProcessor bean definition [com.alibaba.dubbo.config.spring.beans.factory.annotation.DubboConfigBindingBeanPostProcessor] for dubbo config bean [name : com.alibaba.dubbo.config.ApplicationConfig#0] has been registered.
The dubbo config bean definition [name : com.alibaba.dubbo.config.RegistryConfig#0, class : com.alibaba.dubbo.config.RegistryConfig] has been registered.
The BeanPostProcessor bean definition [com.alibaba.dubbo.config.spring.beans.factory.annotation.DubboConfigBindingBeanPostProcessor] for dubbo config bean [name : com.alibaba.dubbo.config.RegistryConfig#0] has been registered.
The dubbo config bean definition [name : com.alibaba.dubbo.config.ProtocolConfig#0, class : com.alibaba.dubbo.config.ProtocolConfig] has been registered.
The BeanPostProcessor bean definition [com.alibaba.dubbo.config.spring.beans.factory.annotation.DubboConfigBindingBeanPostProcessor] for dubbo config bean [name : com.alibaba.dubbo.config.ProtocolConfig#0] has been registered.

 他们是通过循环方式从下面代码中执行的加载过程:

private void registerDubboConfigBeans(String prefix,
                                          Class<? extends AbstractConfig> configClass,
                                          boolean multiple,
                                          BeanDefinitionRegistry registry) {

        Map<String, Object> properties = getSubProperties(environment.getPropertySources(), prefix);

        if (CollectionUtils.isEmpty(properties)) {
            if (log.isDebugEnabled()) {
                log.debug("There is no property for binding to dubbo config class [" + configClass.getName()
                        + "] within prefix [" + prefix + "]");
            }
            return;
        }
        Set<String> beanNames = multiple ? resolveMultipleBeanNames(properties) :
                Collections.singleton(resolveSingleBeanName(properties, configClass, registry));
        for (String beanName : beanNames) {
            registerDubboConfigBean(beanName, configClass, registry);
            registerDubboConfigBindingBeanPostProcessor(prefix, beanName, multiple, registry);

        }
    }

个人感觉 不同类型的配置会对应到上述的其中配置bean中,被分别注册到容器中。

三、dubbo的服务注册

首先我们关闭所有springboot启动类,然后清空zookeeper下的dubbo节点,不能用delete因为zk有子节点的时候不能用delete删除父节点,只能用rmr删除。

[zk: localhost:2181(CONNECTED) 14] rmr /dubbo

ok下一步,我们启动生产者模块,看看服务注册的时机与zk中节点的样子。

首先获取获取服务接口对应的ServiceConfig类,它里面配置这提供服务的Service接口

其次通过proxyFactory获得接口的invoker

接下来将invoker包装成DelegateProviderMetaDataInvoker

获得register对象:


 

DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

 接下来打开连接Zookeeper的netty连接

执行FailbackRegistry中的doRegister,调用zktemplate方法,注册节点

此时我们发现节点已经成功注册到zk中

所以借助dubbo官网的图,更清晰的说明这个流程:更多细节可以参阅官网

引用资料:http://dubbo.apache.org/zh-cn/docs/dev/implementation.html

下面我们查看zk节点

这就是我们的服务名称,存储在zknode中,我们查看他的信息:

于此同时缓存我们的服务,并监听用户的请求。

 

四、dubbo的服务发现

消费者端处理流程与上述大致相同,我也没有太深入去看

ProtocolListenerWrapper

@Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
                Collections.unmodifiableList(
                        ExtensionLoader.getExtensionLoader(InvokerListener.class)
                                .getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
    }

然后注册自己的调用方方法:

 @Override
    @SuppressWarnings("unchecked")
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
            return proxyFactory.getInvoker((T) registry, type, url);
        }

        // group="a,b" or group="*"
        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
        String group = qs.get(Constants.GROUP_KEY);
        if (group != null && group.length() > 0) {
            if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
                    || "*".equals(group)) {
                return doRefer(getMergeableCluster(), registry, type, url);
            }
        }
        return doRefer(cluster, registry, type, url);
    }

第一步:调用getRegistry方法,获取ZookeeperRegistry,创建于zk的连接

第二步 创建注册目录RegistryDirectory,也就是将zk的注册连接对象和服务的调用信息结合在一起,另外增加了注册协议

第三步 执行RegistryProtocol类中的注册方法

五、小结

总的来说第一次跟源码,还有很多地方是云里雾里,这只是服务注册和引用注册,下面还应该深入了解一下服务调用的逻辑

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值