Dubbo之服务暴露
前言
本文 dubbo 使用版本2.7.5
dubbo 通过使用dubbo:service
配置或@service
在解析完配置后进行服务暴露,供服务消费者消费。
dubbo 的服务暴露有两种:
- 远程暴露
- 本地暴露
可以通过scope
显式指定暴露方式:
- none 不暴露
- remote 远程暴露
- local 本地暴露
服务暴露流程
下面是一个服务暴露的流程图:
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
根据不同协议进行暴露。
通过获取所有注册中心实例(registryurls)后,进行依次暴露,暴露操作在doexporturlsfor1protocol
中。
private void doexporturlsfor1protocol(protocolconfig protocolconfig, list<url> registryurls) { map<string, string> map = new hashmap<string, string>(); // 配置信息存入 map ..... // 获取服务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
的逻辑,比如。
目前 dubbo 中有以下几种:
本地暴露
同一个应用中,可能既要提供服务远程暴露给其他应用引用,也要给自身提供引用。如果只提供远程暴露的话,当自身应用需要引用自身的服务时,需要通过远程通信访问,那么这大大浪费网络资源。这是就需要用 injvm 协议暴露,就是我们所说的本地暴露,无需跨网络远程通信,可以更好的节省资源。
通过上面代码中,我们知道本地暴露调用的是serviceconfig#exportlocal
方法。
本地暴露会指定 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 的服务暴露流程进行了分析,核心点就是开篇图中的得到 invoker 后转化到 export。其中更多详细的地方,由于展开后篇幅太大,不能一一写到,会在今后相关的 dubbo 文章再进行讲解。
个人博客:
关注公众号 【ytao】,更多原创好文