Dubbo进阶(八):服务暴露原理

在了解Dubbo服务暴露原理之前,我们先简单的了解一下Dubbo配置信息的默认策略和优先级:

  • -D传递给JVM参数优先级最高,比如-Ddubbo.protocol.prot=20880

  • 代码或者XML配置优先级次高,比如Spring中XML文件指定<dubbo:protocol.port="20880"/>

  • 配置文件优先级次最低,比如dubbo.properties文件指定dubbo.protocol.port=20880。一般推荐使用dubbo.properties作为默认值,只有XML没有配置的时候,dubbo.properties才会生效。

Dubbo 的服务暴露方式

  • 远程暴露
  • 本地暴露

可以通过scope显式指定暴露方式:

  • none 不暴露
  • remote 远程暴露
  • local 本地暴露

服务暴露起点

我们会通过XML或注解的方式来指定要暴露的服务。例子如下:

<bean id=“xxxService” class=“com.xxx.XxxServiceImpl” />

<!-- 增加暴露远程服务配置 -->

<dubbo:service interface=“com.xxx.XxxService” ref=“xxxService” />

这时会创建出一个ServiceBean对象

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,

ApplicationContextAware, BeanNameAware,

ApplicationEventPublisherAware

public class ServiceConfig<T> extends ServiceConfigBase<T>

public abstract class ServiceConfigBase<T> extends AbstractServiceConfig {

private static final long serialVersionUID = 3033787999037024738L;

protected String interfaceName;

//要暴露服务类的接口类

protected Class<?> interfaceClass;

//实现类引用

protected T ref;

//服务名

protected String path;

protected ProviderConfig provider;

protected String providerIds;

protected volatile String generic;

protected ServiceMetadata serviceMetadata;

ServiceBean和Spring有关,它继承了InitializingBean和ApplicationEvent。在Bean初始化完成后会调用InitializingBean.afterPropertiesSet方法来执行服务暴露的准备工作。在Spring的context完成初始化后,会触发ApplicationEventListener事件进行服务暴露,会执行onApplicationEvent方法。这时服务服务暴露就开始了。

public void onApplicationEvent(ContextRefreshedEvent event) {

// 是否有延迟导出 && 是否已导出 && 是不是已被取消导出

if (isDelay() && !isExported() && !isUnexported()) {

// 导出服务

export();

}

}

服务提供者暴露一个服务的详细过程

在这里插入图片描述

  • ProxyFactory 是动态代理,用来创建 Invoker 对象,实现代理使用JavassistProxyFactory和JdkProxyFactory。
  • Invoker 是一个服务对象实例,Dubbo 框架的实体域。它可以是一个本地的实现,一个远程的实现或一个集群的实现,可以向它发起 Invoker 调用。
  • Protocol 是服务域,负责 Invoker 的生命周期管理,是 Invoker 暴露和引用的主要功能入口,对应该类的export和refer方法。
  • Exporter 是根据不同协议暴露 Invoker 进行封装的类,它会根据不同的协议头进行识别(比如:registry://和dubbo://),调用对应XXXProtocol的export()方法。

从上图中可以看到,Dubbo 中服务暴露分为两个大步骤:第一步通过代理将服务实例转换成 Invoker,这就是通过我们常用的反射实现。第二步将 Invoker 根据具体的协议转换成 Exporter,这是就是我们要分析的核心。从这里可以看到 Dubbo 服务对象都是围绕 Invoker 进行工作。

远程暴露

服务远程暴露从字面上理解,就是将服务跨网络进行远程通信,并非同一 JVM 中的服务进行调用。

服务最后都是转换成org.apache.dubbo.config.spring.ServiceBean,它的UML类图:

在这里插入图片描述
ServiceBean继承自ServiceConfig,服务在ServiceConfig#doExportUrls根据不同协议进行暴露。

private void doExportUrls() {

ServiceRepository repository = ApplicationModel.getServiceRepository();

ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());

repository.registerProvider(

getUniqueServiceName(),

ref,

serviceDescriptor,

this,

serviceMetadata

);

List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);

for (ProtocolConfig protocolConfig : protocols) {

String pathKey = URL.buildKey(getContextPath(protocolConfig)

.map(p -> p + "/" + path)

.orElse(path), group, version);

// In case user specified path, register service one more time to map it to path.

repository.registerService(pathKey, interfaceClass);

// TODO, uncomment this line once service key is unified

serviceMetadata.setServiceKey(pathKey);

doExportUrlsFor1Protocol(protocolConfig, registryURLs);

}

}

通过获取所有注册中心实例(registryURLs)后,进行依次暴露,暴露操作在doExportUrlsFor1Protocol中。

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {

String name = protocolConfig.getName();

if (StringUtils.isEmpty(name)) {

name = DUBBO;

}

// 配置信息存入 map

Map<String, String> map = new HashMap<String, String>();

map.put(SIDE_KEY, PROVIDER_SIDE);

.....

// 获取服务URL

String host = findConfigedHosts(protocolConfig, registryURLs, map);

Integer port = findConfigedPorts(protocolConfig, name, map);

URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

.....

String scope = url.getParameter(SCOPE_KEY);

// 如果 scope 配置为 none,则服务不进行暴露

if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

// 本地暴露

if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {

exportLocal(url);

}

// 远程暴露

if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {

// 判断是否有注册中心

if (CollectionUtils.isNotEmpty(registryURLs)) {

for (URL registryURL : registryURLs) {

//if protocol is only injvm ,not register

if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {

continue;

}

url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));

// 获取监控URL

URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);

if (monitorUrl != null) {

// 追加监控上报地址,在拦截器上报数据

url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());

}

// 日志打印

if (logger.isInfoEnabled()) {

if (url.getParameter(REGISTER_KEY, true)) {

logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);

} else {

logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);

}

}

// For providers, this is used to enable custom proxy to generate invoker

String proxy = url.getParameter(PROXY_KEY);

if (StringUtils.isNotEmpty(proxy)) {

registryURL = registryURL.addParameter(PROXY_KEY, proxy);

}

// 将服务对象转换成 Invoker

Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));

DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

// 暴露服务,向注册中心注册服务,进入对应的 RegistryProtocol

Exporter<?> exporter = protocol.export(wrapperInvoker);

exporters.add(exporter);

}

} else { // 没有注册中心时

if (logger.isInfoEnabled()) {

logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);

}

Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);

DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

// 直接暴露服务

Exporter<?> exporter = protocol.export(wrapperInvoker);

exporters.add(exporter);

}

/**

* 存储Dubbo服务的元数据,元数据可以存储在远端配置中心和本地,默认是存储在本地

* @since 2.7.0

* ServiceData Store

*/

WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));

if (metadataService != null) {

metadataService.publishServiceDefinition(url);

}

}

}

this.urls.add(url);

}

上面是代码片段为暴露服务的核心,可以看到 scope 由三个值控制是否暴露和远程或本地暴露,默认远程和本地都暴露。

在远程调用中,分为使用注册中心暴露和直接暴露(默认dubbo协议),它们之间的区别在url上:

  • 无注册中心:dubbo://192.168.3.19:20880/xxxx

  • 有注册中心:registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=provider&dubbo=2.0.2&export=dubbo://192.168.3.19:20880/xxxx

无注册中心的直接暴露服务。

有注册中心的先创建注册中心,再得到 export 的服务地址,然后暴露服务,当服务暴露成功后把服务元数据注册到注册中心。

代码中protocol#export会根据服务 url 的请求头进行区分不同XXXProtocol#export的逻辑。

本地暴露

本地服务暴露是暴露在JVM中,不需要远程通信。Dubbo会默认把远程服务用injvm协议再暴露一份。为什么会有本地服务暴露:

在Dubbo中,一个服务可以即是provider,又是Consumer,因此就存在它自己调用自己服务的时候,如果再通过网络去访问,那么就是舍近求远,因此有了本地暴露服务这个设计。

通过上面代码中,我们知道本地暴露调用的是ServiceConfig#exportLocal方法。

private void exportLocal(URL url) {

URL local = URLBuilder.from(url)

.setProtocol(LOCAL_PROTOCOL)

.setHost(LOCALHOST_VALUE)

.setPort(0)

.build();

Exporter<?> exporter = PROTOCOL.export(

PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));

exporters.add(exporter);

logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);

}

本地暴露会指定 injvm 协议,并且 host 指定为本地127.0.0.1和端口号为0。protocol.export 调用 InjvmProtocol#export 实现:

@Override

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {

return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);

}

export 中返回了 InjvmExporter 实例化对象。

class InjvmExporter<T> extends AbstractExporter<T> {

private final String key;

private final Map<String, Exporter<?>> exporterMap;

InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {

super(invoker);

this.key = key;

this.exporterMap = exporterMap;

exporterMap.put(key, this);

}

@Override

public void unexport() {

super.unexport();

exporterMap.remove(key);

}

}

本地暴露就比较简单,将 Invoker 直接保存在 InjvmExporter 的 exporterMap 中。

以上是 Dubbo进阶(八):服务暴露原理 的全部内容, 来源链接: utcz.com/a/25274.html

回到顶部