系列文章目录
【收藏向】从用法到源码,一篇文章让你精通Dubbo的SPI机制
面试Dubbo ,却问我和Springcloud有什么区别?
超简单,手把手教你搭建Dubbo工程(内附源码)
文章目录
- 系列文章目录
- 前言
- 一、服务暴露的分类
- 二、本地暴露
- 1. 配置
- 2. 实现原理
- 三、远程暴露
- 1. 配置
- 2. 实现原理
- 四、服务的注解配置
- 五、服务暴露的触发
- 1. dubbo 的启动
- 2. 服务识别——后置处理器
- 3. 服务暴露的时机
- 4. 与Spring结合的流程图
前言
今天开始,将正式的进入Dubbo 核心功能的掌握与学习,Dubbo的最核心功能是什么?自然是其RPC功能,而该功能其实分为三个方面:
1.服务暴露
2.服务引用
3.服务调用
三者分别专注于服务提供者、服务消费者、服务调用过程。我们今天就先关注服务暴露,我们至少有这么几个疑问待解决:
- 服务暴露有几种,分别是怎么实现的
- 我们如何进行并配置服务暴露
- Dubbo和Spirng框架是怎么结合的
📕作者简介:战斧,多年开发及管理经验,爱好广泛,致力于创作更多高质量内容
📗本文收录于 Dubbo专栏,有需要者,可直接订阅专栏实时获取更新
📘高质量专栏 RabbitMQ、Spring全家桶 等仍在更新,欢迎指导
📙Zookeeper Redis kafka docker netty等诸多框架,以及架构与分布式专题即将上线,敬请期待
一、服务暴露的分类
看过我们之前的文章 超简单,手把手教你搭建Dubbo工程(内附源码) 的读者,应该有所了解了。即Dubbo 实际上有两种服务暴露的类型,即远程暴露和本地暴露
- 远程暴露
远程暴露是指将服务发布到远程注册中心,供消费者调用。Dubbo支持多种远程通信协议,例如dubbo、rmi、hessian、http等。远程暴露的实现原理大致如下:
服务提供者在启动时,将服务实现类通过协议进行网络传输,发布到注册中心上。
服务消费者在启动时,从注册中心上获取到服务提供者的地址,然后通过协议进行网络通信,调用服务提供者的方法。
- 本地暴露
本地暴露是指将服务发布到本地JVM上,供同一JVM中的其他服务调用。Dubbo的本地通信使用了JVM内部的IPC或者共享内存等方式,从而避免了网络传输的开销。本地暴露的实现原理大致如下:
服务提供者在启动时,将服务实现类通过代理方式,注入到本地JVM的缓存中。
服务消费者在启动时,直接从本地JVM缓存中获取到服务提供者的代理对象,然后调用服务提供者的方法。
PS:需要注意的是,本地暴露仅限于同一JVM内的服务调用,如果需要跨进程或跨机器进行通信,仍需要使用远程通信方式
二、本地暴露
有的人会问,Dubbo提供本地调用的服务是什么意思?因为大部分人都是把Dubbo当作RPC框架使用的,为什么其还会提供本地暴露这种用法?
应该说:Dubbo的本地暴露实际上是比较少用到的,但对于一些需要高性能、低延迟、本地调用的应用场景,使用本地暴露可以提高服务的性能和可用性。从实际项目上来讲,笔者接触过一些项目,其功能全部实现了对外暴露,也就是说有很多功能,既允许外部远程调用,也能被本地其他方法调用。这个时候,本地的调用如果还走RPC的路子,不仅带来延时,还无谓地浪费了带宽,支持本地调用就很有必要了
1. 配置
在Dubbo 2.2 0版本开始,Dubbo默认在本地以JVM的方式暴露服务,当然,对于老版本而言,还需要进行一些配置。这样的话,在第一步需要在整体配置文件里开启
#开启服务暴露 dubbo.provider.export= true
其次就是在单个接口上,我们需要配置引用的源scope = local
2. 实现原理
我们来看进行服务暴露(或者说导出)的基础方法 ServiceConfig 下私有方法 exportUrl
private void exportUrl(URL url, List registryURLs) { 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)) { // 远程导出省略..... } } // 不难看出,`scope` 字段采取的判断逻辑非常宽容,试想一下,如果填入 scope = "abc" 会发生什么? this.urls.add(url); }
通过上述方法,我们知道了除非指定某接口就是远程暴露,否则都会进行本地暴露。那具体本地暴露是怎么回事呢?我们继续来看
private Protocol protocolSPI = this.getExtensionLoader(Protocol.class).getAdaptiveExtension(); String LOCAL_PROTOCOL = "injvm"; String LOCALHOST_VALUE = "127.0.0.1"; private void exportLocal(URL url) { URL local = URLBuilder.from(url) .setProtocol(LOCAL_PROTOCOL) .setHost(LOCALHOST_VALUE) .setPort(0) .build(); local = local.setScopeModel(getScopeModel()) .setServiceModel(providerModel); doExportUrl(local, false); logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local); } private void doExportUrl(URL url, boolean withMetaData) { Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); if (withMetaData) { invoker = new DelegateProviderMetaDataInvoker(invoker, this); } Exporter exporter = protocolSPI.export(invoker); exporters.add(exporter); }
不难发现,本地暴露并没有采用取巧的方式去做特殊处理,而是老老实实构建了个URL,并且像模像样的进行导出,只是这个URL的设置了协议类型为injvm,主机更是直接127.0.0.1。当然最重要的是 protocolSPI.export(invoker) 这个代码,这里的 protocolSPI 是利用的Dubbo-SPI的自适应机制,所以导致最终执行导出的实现类为 InjvmProtocol (关于Dubbo-SPI的内容可见 一篇文章让你精通Dubbo的SPI机制),我们来看 InjvmProtocol 的 export 方法
// AbstractProtocol.class protected final Map> exporterMap; InjvmExporter(Invoker invoker, String key, Map
- 远程暴露