关键词:服务注册、服务发现、注册中心
Dubbo源码版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.9</version>
</dependency>
(读Dubbo源码,关键是要搞清URL中属性protocol的值是什么,以及@Adaptive注解与getAdaptiveExtension方法的原理以及获取到的具体的对象。)
角色:提供者(服务端)、消费者(客户端、服务调用者)、注册中心
消费者当需要调用服务提供者时,消费者要知道服务提供者的位置,即IP、端口以及接口等信息。在早期的一个消费者对一个提供者,消费者可以将提供者的地址写死在代码中。但是随着分布式、集群的大规模应用,更多的是多个消费者对多个提供者。因为在实际场景中,服务会因为某些原因出现不可用的状态,此时为了提高整个系统的可用性,需要消费者能够动态感知可用服务者的列表。这个感知的能力要么服务提供者将自己可用的信息一一告知每一个服务消费者,如果消费者的数量很多,这样显然是效率非常低的。还有一种就是服务提供者将自己的信息暴露在一个第三方的位置,消费者则去这个第三方的位置获取可用服务端列表。这个第三方的位置就是注册中心。
消费者要想调用服务提供者,首先服务提供者要进行服务注册,然后消费者再进行服务发现。
Dubbo中三种角色对应的类:
提供者:
ServiceConfig
public class ServiceConfig<T> extends AbstractServiceConfig
ServiceBean
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware, ApplicationEventPublisherAware
消费者:
ReferenceConfig
public class ReferenceConfig<T> extends AbstractReferenceConfig
ReferenceBean
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean
注册中心:
RegistryConfig
public class RegistryConfig extends AbstractConfig
█ 服务注册
担任远程服务提供者的服务,在启动的时候,会将自身提供的接口服务列表注册到注册中心上。在Dubbo中,接口服务列表就是以接口和实现类方法以URL的形式对外暴露和注册。
使用:
Dubbo提供了三种方式可以像注册中心注册服务,分别是XML配置、注解、以及API。不管哪种方式,服务注册的起点都是export方法。
(1)XML配置
结合Spring框架使用,依靠Spring的功能,DubboNamespaceHandler、DubboBeanDefinitionParser完成配置的解析与Bean的创建,此时每一个接口服务会被创建成ServiceBean对象,ServiceBean继承自ServiceConfig,具有ServiceConfig的功能,还会结合Spring功能, 在Spring启动完成后调用export方法对外暴露和注册服务。
<bean id="serviceIdxxx" class="接口实现类的全限定名" />
<dubbo:service interface="接口的全限定名" ref="serviceIdxxx" />
(2)注解
注解和XML配置的功能差不多,都是依托于Spring使用的。在接口实现类标记@Service注解即可,注意此注解是:com.alibaba.dubbo.config.annotation.Service。DubboComponentScanRegistrar与ServiceAnnotationBeanPostProcessor完成注解的扫描与解析,根据注解配置创建ServiceBean对象。
package com.alibaba.dubbo.config.annotation;
......
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface Service {
Class<?> interfaceClass() default void.class;
String interfaceName() default "";
String version() default "";
String group() default "";
String path() default "";
......
}
(3)API
当不结合Spring框架使用的时候,可以使用API方式。
ServiceConfig serviceConfig = new ServiceConfig();
// 接口
serviceConfig.setInterface(xxxx.class);
serviceConfig.setId("serviceIdxxx");
......
serviceConfig.export();
原理:
Dubbo会将服务提供者中的某一个接口实现类作为整体,封装成一个ServiceConfig对象(XML配置<dubbo:service>或注解@Service或手动编写ServiceConfig),然后通过export方法暴露服务和完成向注册中心注册服务。ServiceBean继承了ServiceConfig,与Spring完成契合,实现将ServiceConfig对象注册成Spring容器中的bean。下面从ServiceConfig的export开始,一步步看Dubbo是如何完成服务注册的。(在读源码之前,建议先学习Dubbo中@SPI,@Adaptive,@Activate三个注解的功能以及ExtensionLoader获取三个注解类的方法。尤其是ExtensionLoader.getAdaptiveExtension。Dubbo大量使用了此方法来获取扩展类对象,实现了根据参数动态获取不同的扩展类的功能。有兴趣的同学,请戳这里《Dubbo之@Adaptive》)
(1)export
// 通过此方法开始服务暴露与注册
public synchronized void export() {
......
else {
// 去暴露和注册服务
this.doExport();
}
}
(2)doExport
// 该方法大部分内容就是完成属性的初始化工作,比如协议、注册中心、监控中心等。这些配置也是Spring在启动的时候,根据XML配置
// 或注解配置完成赋值的,比如<dubbo:protocol />
protected synchronized void doExport() {
if (this.unexported) {
throw new IllegalStateException("Already unexported!");
} else if (!this.exported) {
......
// 暴露工作又转移到了这个方法
this.doExportUrls();
......
} else {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
}
}
(3)doExportUrls
private void doExportUrls() {
// 获取所有的注册中心
List<URL> registryURLs = this.loadRegistries(true);
// this.protocols是协议集合
Iterator var2 = this.protocols.iterator();
// 一个服务按照每一种协议都向所有的注册中心注册
while(var2.hasNext()) {
ProtocolConfig protocolConfig = (ProtocolConfig)var2.next();
// 进入此方法
this.doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
(4)doExportUrlsFor1Protocol
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
......
if (!"none".toString().equalsIgnoreCase(scope)) {
// 本地暴露服务
if (!"remote".toString().equalsIgnoreCase(scope)) {
this.exportLocal(url);
}
// 向注册中心注册服务
if (!"local".toString().equalsIgnoreCase(scope)) {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + this.interfaceClass.getName() + " to url " + url);
}
// 根据配置获取到的注册中心服务列表,依次向注册中心注册服务
if (registryURLs != null && !registryURLs.isEmpty()) {
Iterator var25 = registryURLs.iterator();
while(var25.hasNext()) {
......
// proxy的值决定了proxyFactory对象,该值要么在配置的时候指定,否则默认为javassist
proxy = url.getParameter("proxy");
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter("proxy", proxy);
}
// 获取Invoker,下面进入proxyFactory.getInvoker看看
Invoker<?> invoker = proxyFactory.getInvoker(this.ref, this.interfaceClass, registryURL.addParameterAndEncoded("export", url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 协议对外暴露
Exporter<?> exporter = protocol.export(wrapperInvoker);
this.exporters.add(exporter);
}
}
......
}
}
this.urls.add(url);
}
Invoker<?> invoker = proxyFactory.getInvoker(this.ref, this.interfaceClass, registryURL.addParameterAndEncoded("export", url.toFullString()));其中proxyFactory是private static final ProxyFactory proxyFactory = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(),学习过《Dubbo之Adaptive》的同学应该知道这里的功能?看看ProxyFacoty,对于getInvoker等三个方法,会根据参数Invoker或URL中的proxy值来决定具体的ProxyFactory对象。即若proxy=javassist,则会调用JavassistProxyFactory中的getProxy方法。则上面这句代码,会根据registryURL中proxy的值来决定使用哪个ProxyFactory对象,proxy要么在XML或注解中指定,默认为JavassistProxyFactory:
@SPI("javassist")
public interface ProxyFactory {
@Adapti