在上一篇文章我们讲解了一下 dubbo 服务暴露过程中的本地暴露。它只是一个开胃小菜,主要是为我们后面讲解远程暴露开个头。下面就来分析一下 dubbo 在远程暴露里面发生了哪些事。因为 dubbo 远程暴露里面的过程还是比较复杂的,所以我就分为三个文章来讲解 dubbo 的远程暴露:
- dubbo 远程暴露 – Netty 暴露服务
- dubbo 远程暴露 – Zookeeper 连接
- dubbo 远程暴露 – Zookeeper 注册 & 订阅
这就篇就是分析 dubbo 服务暴露中通过 Netty 来暴露服务(当然 dubbo 还可以通过 Mina、Grizzly 来暴露服务,默认使用 Netty)。
1、ServiceConfig#doExportUrls
首先通过方法loadRegistries(true)
来加载注册中心。在方法checkRegistry()
方法中判断如果 xml 里面没有配置注解中心,从 dubbo 的 properties 文件中获取(默认是dubbo.properties
)。然后会返回List<URL>
作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。URL的格式如下:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0 ...
因为 dubbo 支持多种协议,遍历所有协议分别根据不同的协议把服务export到不同的注册中心上去。
-
把配置的信息通过
appendParameters
提取到 map 中 -
判断是否支持泛化调用
-
通过协议名称、host、port、contextPath 和第一步提取出来的 map 构造协议的统一数据模型 URL (如:
dubbo://169.254.69.197:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider ...
) -
循环遍注册中心,把服务暴露在不同的注册中心当中
a) 如果配置了 monitor,就返回监控统一模型数据 URL,并给以monitor
为 key 添加到生成的 URL中,URL格式如下:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?export=dubbo%3A%2F%2F169.254.69.197%3A20880%2Fcom.alibaba.dubbo.demo.DemoService& ...
b) 把协议统一模型 URL 以export
为 key,添加到注册中心的统一模型 URL中
c) 根据服务的具体实现、实现的接口以及注册中心统一模型 URL从代理工厂ProxyFactory
(SPI 默认获取到 JavassistProxyFactory)获取Invoker
对象。
d) 通过Protocol#export(invoker)
暴露服务,因为注册的协议是registry
所以生成的 Protocol 对象如下图所示。因为ProtocolFilterWrapper
和ProtocolFilterWrapper
是过滤registry
协议的,所以最终通过RegistryProtocol
来处理暴露过程。
2、RegistryProtocol#export
根据这个类名我们就可以推测出这个类具有的功能,具有 Registry
( 注册 )与 Protocol
( 协议–服务暴露 )在这个方法里面就包括上面提到的三个逻辑:
- dubbo 远程暴露 – Netty 暴露服务,通过配置的协议根据 SPI 获取到对应的
Protocol
对象,这里是 DubboProtocol,对象。 - dubbo 远程暴露 – Zookeeper 连接 服务注册,通过
RegistryFactory
根据 SPI 获取对应的Registry
对象(ZookeeperRegistry
),然后注册到注册中心上面去,供consumer
调用 - dubbo 远程暴露 – Zookeeper 注册 & 订阅,它会把创建2个节点:一个是
/dubbo/服务全类名/provider/...
节点提供给服务消费方查看节点信息;二是/dubbo/服务全类名/configurators/...
节点提供给服务方watch
(监控) dubbo-admin 对于服务的修改。比如:服务权重。
上面粗略的讲了一下服务远程暴露主要干了哪些事,主要是想让大家有一个全局的意识。下面我们就来讲一下 dubbo 服务是如何通过 Netty 来暴露服务。
-
getCacheKey(originInvoker),通过 Invoker 对象获取到缓存 key,还记得我们在
ServiceConfig#doExportUrls
的 4-b 步骤里面吗?它就是把保存在 注册统一模型里面的export
key 获取到协议的统一模型dubbo://169.254.69.197:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider ...
,然后再删除dynamic
与enabled
参数 -
从
Map<String, ExporterChangeableWrapper<?>> bounds
缓存中根据上面获取的 key 获取 Exporter 对象,如果获取到直接返回;否则进行服务暴露 -
通过
originInvoker
获取里面的 URL 获取到协议的统一模型以及originInvoker
本身创建InvokerDelegete
。 -
根据
InvokerDelegete
暴露服务,因为 URL 协议是dubbo
,所以获取到的实例是DubboProtocol
,而这个对象因为协议不是registry
,所以生成ProtocolListenerWrapper
会根据 SPI 机制检测 dubbo 里面配置的 InvokerListener 扩展;而ProtocolFilterWrapper
会根据 SPI机制检测 dubbo里面配置的 Filter 扩展。所以最终通过DubboProtocol
来处理暴露过程。 -
暴露生成的 Exporter 和 传入的
originInvoker
会创建ExporterChangeableWrapper
对象会以步骤 1 生成的 key 缓存在Map<String, ExporterChangeableWrapper<?>> bounds
当中,并返回结果。
3、DubboProtocol#export
整个DubboProtocol#export
的代码如下:
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispaching event
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
openServer(url);
return exporter;
}
这断代码主要的操作是:
- 根据传入的
Invoker
中的 URL 通过serviceKey(url)
获取到 serviceKey,它的格式为:com.alibaba.dubbo.demo.DemoService:20880
. - 以传的
Invoker
、第 1 步生成的 key 和Map<String, Exporter<?>> exporterMap
生成DubboExporter
,并以第 1 步生成的 key 为索引,把生成的DubboExporter
添加到Map<String, Exporter<?>> exporterMap
中 - 根据 URL 判断是不是服务端,如果是服务端并且从
Map<String, ExchangeServer> serverMap
获取到的 ExchangeServer 为空,就通过DubboProtocol#createServer
创建服务,达到服务暴露的目的。返回DubboExporter
对象
4、DubboProtocol#createServer
dubbo 远程服务( Provider )暴露最终其实就是创建一个 Netty Serve 服务,然后在 dubbo 在服务引用的时候创建一个 Netty Client 服务。其实 dubbo 远程通信的原理其实就是基于 Socket 的远程通信。下面我们来看一下 dubbo 是如何创建一个 Netty 服务的,下面就是它创建的序列图:
它通过传入 URL 与 requestHandler
来创建一个 ExchangeServer,通过Netty 基于 NIO的形式通过自定义Channel来接收服务引用方传递过来的信息,以及发送调用远程服务的本地方法后的数据给服务调用者。URL 里面主要包含 IP 地址 与 端口信息用于创建 Socket 连接,而 requestHandler
是一个 ExchangeHandler 通过自定义协议来处理 dubbo 的远程通信。