0
点赞
收藏
分享

微信扫一扫

浅谈Dubbo服务导出到注册中心源码


Dubbo服务导出到注册中心源码

  • ​​一、检查更新配置(checkAndUpdateSubConfigs)​​
  • ​​1、completeCompoundConfigs​​
  • ​​2、startConfigCenter​​
  • ​​3、refresh​​
  • ​​二、执行服务导出(doExport)​​
  • ​​1、doExportUrls​​
  • ​​2、loadRegistries​​
  • ​​3、doExportUrlsFor1Protocol​​
  • ​​4、PROXY_FACTORY.getInvoker​​
  • ​​5、export​​
  • ​​6、overrideSubscribeListener、overrideUrlWithConfig、ProviderConfigurationListener(初始化监听器)​​
  • ​​7、doLocalExport(初始化服务注册的连通方式)​​
  • ​​8、register(将服务注册到注册中心)​​
  • ​​三、总结​​


由于ServiceBean实现了ApplicationListener接口。所以当Spring容器启动完成之后,会触发对应的事件,继而调用到onApplicationEvent方法,最终调用到当前类的export方法。

export方法核心分成两大部分(在ServiceBean的父类ServiceConfig中实现):第一部分为处理所有配置的参数,第二部分为将服务真正进行导出



浅谈Dubbo服务导出到注册中心源码_ide



一、检查更新配置(checkAndUpdateSubConfigs)

1、completeCompoundConfigs

如果ServiceConfig中的某些属性如果是空的,那么就从ProviderConfig、ModuleConfig、ApplicationConfig中获取。即补全ServiceConfig中部分的属性



浅谈Dubbo服务导出到注册中心源码_rpc_02


2、startConfigCenter

获取dubbo-admin远程客户端上配置项,然后将对应的配置项覆盖到前面已经设置过值得对象上(完善ConfigManager对象),这里可以看出远程配置平台得配置优先级是高于本地得dubbo的配置文优先级

1、获取远程配置中心的参数值:



浅谈Dubbo服务导出到注册中心源码_ide_03


2、覆盖对应的参数值

从配置中心取到配置数据后,刷新所有的XxConfig中的属性,除开ServiceConfig。即将配置中心配置的数据对除了ServiceConfig对象以外的对象进行赋值



浅谈Dubbo服务导出到注册中心源码_rpc_04


3、refresh

前面覆盖了很多参数,但是没有设置ServiceConfig的配置,该方法用来配置对ServiceConfig对象的参数设置

1)调用getConfiguration方法,获取配置数据



浅谈Dubbo服务导出到注册中心源码_rpc_05


2)设置远程配置中心优先级和@Service注解上的优先级顺序

CompositeConfiguration对象内部维护了一个有序的LinkList对象,用来顺序存放所有的配置信息,后期执行set方法的时候就按照这个顺序进行依次set值。初始顺序如上图。

configCenterFirst是一个配置参数,默认为true。即默认情况下, 当前ServiceConfig配置项(ServiceBean的父类,这里可以直接理解为@Service上的配置)放在第四个位置。由于未来是顺序执行,即远程配置中心的配置优先级高于@Service注解上的配置信息。

同理,如果配置为false,则表示@Service注解上的配置项优先级高于远程配置中心的配置。



浅谈Dubbo服务导出到注册中心源码_dubbo_06


3)依次调用set方法完成参数设置

这里会对遍历ServiceBean对象的set方法,如果对应的参数没有进行赋值(value=null),就获取配置项(上一步构建的linkList顺序里的配置项)里面的value,进行赋值操作



浅谈Dubbo服务导出到注册中心源码_java_07


二、执行服务导出(doExport)

服务导出里面有一个重要的概念:

URL:我们的协议(http、dubbo)可以是一个URL,我们的注册中心(zookeeper、redis)也会是一个URL。在源码中对应着registryUrl(注册中心构建的url)和providerUrl(服务提供者和通信协议构建的url)

1、doExportUrls

在每一个选择暴露的ServiceBean服务实体类中,都会进行服务导出操作。在前面提到的更新配置基础上,紧接着开始获取到所有注册中心的列表,然后将服务按照不同的协议都注册到对应的注册中心。比如,我们的服务配置了redis和zookeeper两个不同的注册中心,那么这里的registyURLs的size就等于2,如果我们的服务配置了dubbo和http两种通信协议,这里的protocols的size也会等于2。然后将对应的注册中心list和单个协议传入指定的方法,完成后续操作(为每个协议导出一次)



浅谈Dubbo服务导出到注册中心源码_ide_08


2、loadRegistries

该方法为获取注册中心列表的逻辑

protected List<URL> loadRegistries(boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<URL>();
if (CollectionUtils.isNotEmpty(registries)) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
// 如果注册中心没有配地址,则地址为0.0.0.0
if (StringUtils.isEmpty(address)) {
address = ANYHOST_VALUE;
}
// 如果注册中心的地址不是"N/A"
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
// 把application中的参数放入map中,注意,map中的key是没有prefix的
appendParameters(map, application);
// 把config中的参数放入map中,注意,map中的key是没有prefix的
// config是RegistryConfig,表示注册中心
appendParameters(map, config);
// 此处path值固定为RegistryService.class.getName(),因为现在是在加载注册中心
map.put(PATH_KEY, RegistryService.class.getName());
// 把dubbo的版本信息和pid放入map中
appendRuntimeParameters(map);

// 如果map中如果没有protocol,那么默认为dubbo
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}

// 构造注册中心url,地址+参数
List<URL> urls = UrlUtils.parseURLs(address, map);

for (URL url : urls) {
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(REGISTRY_PROTOCOL)
.build();
// 到此为止,url的内容大概为:
// registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&pid=269936®istry=zookeeper×tamp=1584886077813
// 该url表示:使用registry协议调用org.apache.dubbo.registry.RegistryService服务
// 参数为application=dubbo-demo-annotation-provider&dubbo=2.0.2&pid=269936®istry=zookeeper×tamp=1584886077813

// 这里是服务提供者和服务消费者区别的逻辑
// 如果是服务提供者,获取register的值,如果为false,表示该服务不注册到注册中心
// 如果是服务消费者,获取subscribe的值,如果为false,表示该引入的服务不订阅注册中心中的数据
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}


3、doExportUrlsFor1Protocol

这里的appendParameters方法,大致逻辑为找到第二个对象里面的所有getXxx方法,然后得到对应的value。然后以xxx为key,对应的get方法获取到的值为value,放进第一个参数的map中。

由于这是一个map,所以如果key相同则对应的value是会覆盖的



浅谈Dubbo服务导出到注册中心源码_java_09


最终将map中的数据,和已有的信息拼接为一个url,然后存放到注册中心里面



浅谈Dubbo服务导出到注册中心源码_ide_10


4、PROXY_FACTORY.getInvoker

该方法主要作为可以简单理解为将ref代码的对象(真正被代理的对象)封装为一个Invoker对象,未来想要调用被代理对象的业业务逻辑,直接使用Invoker对象进行调用即可

这个方法同时还完成将注册中心的url(registryURL为传入该方法的registryURLs的其中一个值)与前面构建出来的new URL()创建出来的url(ServiceBean属性构建的url字符串)对象进行一个拼接。即将单个注册中心和指定协议下的服务拼接在了一起

得到Invoker对象后,再封装为一个DelegateProviderMetaDataInvoker对象,然后调用export方法,完成正真的服务到导出功能



浅谈Dubbo服务导出到注册中心源码_分布式_11


5、export

使用特定的协议来对服务进行导出,这里的协议为RegistryProtocol,导出成功后得到一个Exporter

  1. 先使用RegistryProtocol进行服务注册
  2. 注册完了之后,使用DubboProtocol进行导出

@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 导出服务
// registry:// ---> RegistryProtocol
// zookeeper:// ---> ZookeeperRegistry
// dubbo:// ---> DubboProtocol

// 得到注册中心url,即registry://xxx?xx=xx®istry=zookeeper ---> zookeeper://xxx?xx=xx
URL registryUrl = getRegistryUrl(originInvoker);
// 得到服务提供者url,表示服务提供者
URL providerUrl = getProviderUrl(originInvoker);


// 在服务提供者url的基础上,生成一个overrideSubscribeUrl,协议为provider://,增加参数category=configurators&check=false
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);

// 一个overrideSubscribeUrl对应一个OverrideListener,用来监听变化事件,监听到overrideSubscribeUrl的变化后,
// OverrideListener就会根据变化进行相应处理,具体处理逻辑看OverrideListener的实现
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);


// 在这个方法里会利用providerConfigurationListener和serviceConfigurationListener去重写providerUrl
// providerConfigurationListener表示应用级别的动态配置监听器,providerConfigurationListener是RegistyProtocol的一个属性
// serviceConfigurationListener表示服务级别的动态配置监听器,serviceConfigurationListener是在每暴露一个服务时就会生成一个
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);

// export invoker
// 根据动态配置重写了providerUrl之后,就会调用DubboProtocol或HttpProtocol去进行导出服务了
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

// url to registry
// 得到注册中心-ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);

// 对存入到注册中心的providerUrl的参数进行简化
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);

// 将当前服务提供者Invoker,以及该服务对应的注册中心地址,以及简化后的服务url存入ProviderConsumerRegTable
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);


//to judge if we need to delay publish
//是否需要注册到注册中心
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 注册服务,把简化后的服务提供者url注册到registryUrl中去
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}

registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}



6、overrideSubscribeListener、overrideUrlWithConfig、ProviderConfigurationListener(初始化监听器)

这里涉及两种类型的监听器:

都可以用来监听dubbo-admin客户端的配置修改。未来发生了修改,overrideSubscribeListener这个监听器会调用到notify方法,而overrideUrlWithConfig和ProviderConfigurationListener会调用到notifyOverrides方法

1)overrideUrlWithConfig监听器调用的方法



浅谈Dubbo服务导出到注册中心源码_rpc_12


2)overrideUrlWithConfig监听器调用的方法



浅谈Dubbo服务导出到注册中心源码_rpc_13


7、doLocalExport(初始化服务注册的连通方式)

如果使用dubbo通讯协议,底层会开启netty

1)调用到DubboProtocol的export方法

与前面调用export方式类似。这里传进来的URL是providerUrl,即服务提供者对应的export方法。我们这里使用dubbo协议进行传输,所以这里的export方法会调用到DubboProtocol的export方法

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
String key = getCacheKey(originInvoker);

return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);

return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
});
}



2)在该方法种会开启对应的服务连接

openServer底层再调用createServer方法。由于该调用链路比较长,后面直接放一张流程图



浅谈Dubbo服务导出到注册中心源码_分布式_14


浅谈Dubbo服务导出到注册中心源码_java_15


3)new ServerBootstrap

真正初始化netty服务端的代码(doOpen方法中)。初始化netty服务端用来处理底层的通讯



浅谈Dubbo服务导出到注册中心源码_rpc_16


8、register(将服务注册到注册中心)

在执行上一步的export方法的过程中,会调用register方法,即register(registryUrl, registeredProviderUrl);方法。传入的参数为注册中心url,以及简化版的服务提供者url,这两个url就可以理解为一个是注册中心的地址,一个是暴露服务的参数信息

public void register(URL registryUrl, URL registeredProviderUrl) {
Registry registry = registryFactory.getRegistry(registryUrl);
// 调用ZookeeperRegistry的register方法
registry.register(registeredProviderUrl);
}

1)addFailedRegistered

如果调用doRegister方法发生异常,则会有对应的重试机制



浅谈Dubbo服务导出到注册中心源码_rpc_17


2)使用具体注册中心,完成注册功能



浅谈Dubbo服务导出到注册中心源码_分布式_18


三、总结

用自己的话,总结一下Dubbo服务导出代码的主体流程,主要分为三部分。

1-3:完成基本的参数配置;

4-6:构建注册时需要的参数;

7-9:完成正真的注册逻辑。

  1. 首先会去检查ServiceBean部分属性是否有值(ServiceConfig是ServiceBean的父类,源码检查的是其父类),每值就进行赋值
  2. 获取全程配置中心dubbo-admin上的配置参数,然后对相关对象进行赋值(除开对ServiceConfig对象进行赋值)
  3. 配置ServiceConfig对象。设置该对象涉及到很多配置,如JVM环境变量、操作系统环境变量、配置中心APP配置、配置中心Global配置还有dubbo.properties配置文件还有@Service注解上的配置。这几个配置按照优先级顺序完成对ServiceConfig对象参数的设置
  4. 获取部分对象的属性值,以key-value的形式放进一个map。部分对象包括运行时参数、监控中心参数、应用相关参数、模块相关参数、提供者相关参数、协议相关参数、服务本身相关参数、服务中某些方法参数
  5. 将registryURL(注册中心URL对象)和protocolConfig(协议和服务都成的URL对象)封装为一个invoker对象。未来在使用这连个URL对象的时候,直接使用invoker对象就都能获取到
  6. invoker对象已经能够完成服务和注册中心一对一的关系。接下来就是完成具体的注册逻辑
  7. 初始化监听器,用来监听未来dubbo-admin客户端发生的动态配置命令
  8. 初始化对应的通讯通道(dubbo就是初始化NettyServer对象,http就是初始化Tomcat对象,对应TomcatHttpServer类)
  9. 创建注册中心对象,完成将服务注册到注册中心上。如果注册失败,还有对应的重试机制


举报

相关推荐

0 条评论