浅谈Dubbo服务导出到注册中心源码
Posted 默辨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈Dubbo服务导出到注册中心源码相关的知识,希望对你有一定的参考价值。
Dubbo服务导出到注册中心源码
由于ServiceBean实现了ApplicationListener接口。所以当Spring容器启动完成之后,会触发对应的事件,继而调用到onApplicationEvent方法,最终调用到当前类的export方法。
export方法核心分成两大部分(在ServiceBean的父类ServiceConfig中实现):第一部分为处理所有配置的参数,第二部分为将服务真正进行导出
一、检查更新配置(checkAndUpdateSubConfigs)
1、completeCompoundConfigs
如果ServiceConfig中的某些属性如果是空的,那么就从ProviderConfig、ModuleConfig、ApplicationConfig中获取。即补全ServiceConfig中部分的属性
2、startConfigCenter
获取dubbo-admin远程客户端上配置项,然后将对应的配置项覆盖到前面已经设置过值得对象上(完善ConfigManager对象),这里可以看出远程配置平台得配置优先级是高于本地得dubbo的配置文优先级
1、获取远程配置中心的参数值:
2、覆盖对应的参数值
从配置中心取到配置数据后,刷新所有的XxConfig中的属性,除开ServiceConfig。即将配置中心配置的数据对除了ServiceConfig对象以外的对象进行赋值
3、refresh
前面覆盖了很多参数,但是没有设置ServiceConfig的配置,该方法用来配置对ServiceConfig对象的参数设置
1)调用getConfiguration方法,获取配置数据
2)设置远程配置中心优先级和@Service注解上的优先级顺序
CompositeConfiguration对象内部维护了一个有序的LinkList对象,用来顺序存放所有的配置信息,后期执行set方法的时候就按照这个顺序进行依次set值。初始顺序如上图。
configCenterFirst是一个配置参数,默认为true。即默认情况下, 当前ServiceConfig配置项(ServiceBean的父类,这里可以直接理解为@Service上的配置)放在第四个位置。由于未来是顺序执行,即远程配置中心的配置优先级高于@Service注解上的配置信息。
同理,如果配置为false,则表示@Service注解上的配置项优先级高于远程配置中心的配置。
3)依次调用set方法完成参数设置
这里会对遍历ServiceBean对象的set方法,如果对应的参数没有进行赋值(value=null),就获取配置项(上一步构建的linkList顺序里的配置项)里面的value,进行赋值操作
二、执行服务导出(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和单个协议传入指定的方法,完成后续操作(为每个协议导出一次)
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是会覆盖的
最终将map中的数据,和已有的信息拼接为一个url,然后存放到注册中心里面
4、PROXY_FACTORY.getInvoker
该方法主要作为可以简单理解为将ref代码的对象(真正被代理的对象)封装为一个Invoker对象,未来想要调用被代理对象的业业务逻辑,直接使用Invoker对象进行调用即可
这个方法同时还完成将注册中心的url(registryURL为传入该方法的registryURLs的其中一个值)与前面构建出来的new URL()创建出来的url(ServiceBean属性构建的url字符串)对象进行一个拼接。即将单个注册中心和指定协议下的服务拼接在了一起
得到Invoker对象后,再封装为一个DelegateProviderMetaDataInvoker对象,然后调用export方法,完成正真的服务到导出功能
5、export
使用特定的协议来对服务进行导出,这里的协议为RegistryProtocol,导出成功后得到一个Exporter
- 先使用RegistryProtocol进行服务注册
- 注册完了之后,使用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监听器调用的方法
2)overrideUrlWithConfig监听器调用的方法
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方法。由于该调用链路比较长,后面直接放一张流程图
3)new ServerBootstrap
真正初始化netty服务端的代码(doOpen方法中)。初始化netty服务端用来处理底层的通讯
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方法发生异常,则会有对应的重试机制
2)使用具体注册中心,完成注册功能
三、总结
用自己的话,总结一下Dubbo服务导出代码的主体流程,主要分为三部分。
1-3:完成基本的参数配置;
4-6:构建注册时需要的参数;
7-9:完成正真的注册逻辑。
- 首先会去检查ServiceBean部分属性是否有值(ServiceConfig是ServiceBean的父类,源码检查的是其父类),每值就进行赋值
- 获取全程配置中心dubbo-admin上的配置参数,然后对相关对象进行赋值(除开对ServiceConfig对象进行赋值)
- 配置ServiceConfig对象。设置该对象涉及到很多配置,如JVM环境变量、操作系统环境变量、配置中心APP配置、配置中心Global配置还有dubbo.properties配置文件还有@Service注解上的配置。这几个配置按照优先级顺序完成对ServiceConfig对象参数的设置
- 获取部分对象的属性值,以key-value的形式放进一个map。部分对象包括运行时参数、监控中心参数、应用相关参数、模块相关参数、提供者相关参数、协议相关参数、服务本身相关参数、服务中某些方法参数
- 将registryURL(注册中心URL对象)和protocolConfig(协议和服务都成的URL对象)封装为一个invoker对象。未来在使用这连个URL对象的时候,直接使用invoker对象就都能获取到
- invoker对象已经能够完成服务和注册中心一对一的关系。接下来就是完成具体的注册逻辑
- 初始化监听器,用来监听未来dubbo-admin客户端发生的动态配置命令
- 初始化对应的通讯通道(dubbo就是初始化NettyServer对象,http就是初始化Tomcat对象,对应TomcatHttpServer类)
- 创建注册中心对象,完成将服务注册到注册中心上。如果注册失败,还有对应的重试机制
以上是关于浅谈Dubbo服务导出到注册中心源码的主要内容,如果未能解决你的问题,请参考以下文章