第二章 基于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类中的注册方法
五、小结
总的来说第一次跟源码,还有很多地方是云里雾里,这只是服务注册和引用注册,下面还应该深入了解一下服务调用的逻辑