Dubbo服务暴露~远程暴露
远程暴露有两种情况:
- 配置了注册中心的,需要将服务注册到注册中心
- 未配置注册中心的,仅仅将服务按照远程协议暴露出来
1、未配置注册中心(用于服务消费者直连服务提供者)
具体步骤如下:
- 创建Invoker的步骤和Injvm的方式相同;
- 创建DelegateProviderMetaDataInvoker对象,持有Invoker和ServiceConfig对象;
- 创建Exporter对象,步骤和Injvm相同。
和本地暴露的区别:
- Invoker对象是: DelegateProviderMetaDataInvoker
- Exporter中的Protocol扩展类不同(根据服务暴露配置或者默认)
2、配置了注册中心
包含两个步骤:服务导出和服务注册
// NOTE1: 创建Invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
// NOTE: DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// NOTE2: 导出服务,并生成 Exporter
Exporter<?> exporter = protocol.export(wrapperInvoker);
2.1、创建Invoker
- 与无注册中心服务暴露创建Invoker的过程基本相同路径:ProxyFactory$Adaptive => StubProxyFactoryWrapper => JavassistProxyFactory => Wrapper => AbstractProxyInvoker => DelegateProviderMetaDataInvoker。
- 内部的URL配置协议头为“registry”
registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&......&pid=10421&qos.port=22222®istry=zookeeper×tamp=1561639865358
2.2、创建Export并暴露
- 启动通信服务器,绑定服务端口,提供远程调用
- 向注册中心注册服务提供者,提供服务消费者从注册中心发现服务。
2.2.1、调用主路径
路径:由两个小链路构成一个大链路
- 链路1: Protocol$Adaptive => ProtocolFilterWrapper => ProtocolListenerWrapper => RegistryProtocol【RegistryFactory$Adaptive => ProxyFactory$Adaptive => Protocol$Adaptive => Cluster$Adaptive】=> Protocol$Adaptive
- 链路2: Protocol$Adaptive => ProtocolFilterWrapper => ProtocolListenerWrapper => DubboProtocol => DubboExporter => 服务注册
- 通信服务器启动:DubboProtocol => ExchangeServer => Transporters => NettyServer
- 创建RegistryProtocol扩展类时的内部属性关系图 (大佬的图)
- 注册中心服务暴露流程图
2.2.2、调用步骤描述
- 首先,传入的是注册中心的 URL(registryURL) ,通过 Protocol$Adaptive 获取到的是 RegistryProtocol 对象。
- 其次,RegistryProtocol 会在其 #export(…) 方法中,使用服务提供者的 URL ( 即注册中心的 URL 的 export 参数值),再次调用 Protocol$Adaptive 获取到的是 DubboProtocol 对象,创建DubboExporter。
- 在DubboProtocol中启动通信服务器(默认为NettyServer)。
- 最后,向注册中心注册服务提供者的URL。
2.3、服务导出和通信服务器启动(走代码)
1、RegistryProtocol
- 调用 doLocalExport 导出服务(对应NOTE 1)
- 向注册中心注册服务(对应NOTE 2)
向注册中心进行订阅 override 数据- 创建并返回 DestroyableExporter(对应NOTE 3)
//☆☆--RegistryProtocol
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// NOTE: 获取注册中心 URL
URL registryUrl = getRegistryUrl(originInvoker);
//NOTE: 服务暴露URL
URL providerUrl = getProviderUrl(originInvoker);
//NOTE: 获取订阅 URL(provider开头)
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
//NOTE: 使用 OverrideListener 对象,订阅配置规则
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
//NOTE 1: 创建Exporter,暴露服务
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// NOTE: 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry(这里不是SPI 机制)
final Registry registry = getRegistry(originInvoker);
// NOTE: 获取已注册的服务提供者 URL
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
//NOTE: 向本地注册表,注册服务提供者
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);
//to judge if we need to delay publish
boolean register = registeredProviderUrl.getParameter("register", true);
if (register) {
// NOTE 2: 向注册中心注册服务
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
// NOTE 3: 创建并返回 DestroyableExporter
return new DestroyableExporter<>(exporter);
}
2、RegistryProtocol.doLocalExport
/**
* 服务导出
*/
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
//NOTE:缓存
String key = getCacheKey(originInvoker);
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
//NOTE:创建 Invoker 为委托类对象,将providerUrl和originInvoker封装
final Invoker<?> invokerDelegete = new InvokerDelegate<T>(originInvoker, providerUrl);
//NOTE: DubboProtocol export创建Exporter,进行服务暴露
/**
* 使用【创建的 Exporter 对象】+【originInvoker】,创建 ExporterChangeableWrapper 对象。这样,originInvoker 就和 Exporter 对象,形成了绑定的关系
*/
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return exporter;
}
3、DubboProtocol.export(invokerDelegete)
- 创建DubboExporter(对应NOTE 1)
- 创建并启动通信服务器
- 优化序列化
//☆☆--DubboProtocol
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
//对应的是providerUrl
URL url = invoker.getUrl();
// NOTE: 获取服务标识,由服务组名,服务名,服务版本号以及端口组成
String key = serviceKey(url);
//NOTE 1: 创建DubboExporter对象
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
// NOTE: 将 <key, exporter> 键值对放入缓存中
exporterMap.put(key, exporter);
......
// NOTE 2 : 启动服务器(netty 通讯)
openServer(url);
// NOTE 3: 优化序列化
optimizeSerialization(url);
return exporter;
}
- 创建并启动通信服务器
private void openServer(URL url) {
// NOTE: 取 host:port,并将其作为服务器实例的 key,用于标识当前的服务器实例。在同一台机器上,同一个端口上仅允许启动一个服务器实例。
String key = url.getAddress();
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {
synchronized (this) {
server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
}
}
} else {
server.reset(url);
}
}
}
/**
* NOTE: 创建通信服务器(服务层)
* 1、检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常
* 2、创建ExchangerServer服务器实例
* 3、检测是否支持 client 参数所表示的 Transporter 拓展
*/
private ExchangeServer createServer(URL url) {
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
//NOTE: 添加心跳检测配置到 url 中,默认60秒
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// NOTE: 获取 server 参数,默认为 netty
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
// NOTE: 通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常
......代码省略
// NOTE: 添加编码解码器参数(默认为'dubbo')
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
ExchangeServer server;
try {
//NOTE: 创建ExchangerServer服务器
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
//是否支持 client 参数所表示的 Transporter 拓展
......省略client的校验代码
return server;
}
- Exchangers.bind(url, requestHandler)
//☆☆--Exchangers
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
//获得Exchanger的扩展实现类:HeaderExchanger
Exchanger exchanger = ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
return exchanger.bind(url, handler);
}
//☆☆--HeaderExchanger
public class HeaderExchanger implements Exchanger {
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
// NOTE: 这里包含了多个调用,分别如下:
// 1. 创建 HeaderExchangeHandler 对象
// 2. 创建 DecodeHandler 对象
// 3. 通过 Transporters 构建 Server 实例(默认NettyServer,NettyTransporter)
// 4. 创建 HeaderExchangeServer 对象
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
}
//☆☆--Transporters
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (handlers.length == 1) {
handler = handlers[0];
} else {//如果 handlers 元素数量大于1,则创建 ChannelHandler 分发器
handler = new ChannelHandlerDispatcher(handlers);
}
// NOTE: 获取自适应 Transporter 实例,并调用实例方法(默认为NettyTransporter)
return getTransporter().bind(url, handler);
}
//☆☆--NettyTransporter
public class NettyTransporter implements Transporter {
@Override
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
// NOTE: 创建Netty Server 并调用内部的doOpen()方法启动服务
return new NettyServer(url, listener);
}
}
2.4、服务注册(走代码)
调用DubboProtocol创建Exporter后,会根据
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
- 获得注册中心实例
- 向注册中心注册服务提供者URL
2.4.1、获得注册中心实例(工厂)
根据registryUrl获取对应的Registry扩展类(以ZookeeperRegistry为例)
//RegistryProtocol
Registry registry = registryFactory.getRegistry(registryUrl);
//根据registryUrl获得对应的Registry:ZookeeperRegistry、DubboRegistry等
private Registry getRegistry(final Invoker<?> originInvoker) {
URL registryUrl = getRegistryUrl(originInvoker);
return registryFactory.getRegistry(registryUrl);
}
//☆☆--AbstractRegistryFactory
public Registry getRegistry(URL url) {
url = url.setPath(RegistryService.class.getName())
.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
String key = url.toServiceStringWithoutResolving();
// 创建Registry时,需要加锁
LOCK.lock();
try {
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
//创建Registry
registry = createRegistry(url);
if (registry == null) {
throw new IllegalStateException("Can not create registry " + url);
}
REGISTRIES.put(key, registry);
return registry;
} finally {
// Release the lock
LOCK.unlock();
}
}
- 创建注册中心:ZookeeperRegistry
public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
private ZookeeperTransporter zookeeperTransporter;
public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
this.zookeeperTransporter = zookeeperTransporter;
}
@Override
public Registry createRegistry(URL url) {
//创建ZookeeperRegistry(内部默认的zkClient为:CuratorZookeeperClient)
return new ZookeeperRegistry(url, zookeeperTransporter);
}
}
2.4.2、向注册中心注册服务
注册中心注册服务其实就是将服务提供者的信息,封装到URL中,在注册中心(zk)上创建一个个节点。
public void register(URL registryUrl, URL registeredProviderUrl) {
Registry registry = registryFactory.getRegistry(registryUrl);
// 注册服务
registry.register(registeredProviderUrl);
}
- 主路径 :RegistryProtocol => FailbackRegistry => ZookeeperRegistry => CuratorZookeeperClient
//☆☆---ZookeeperRegistry
public void doRegister(URL url) {
try {
// NOTE: 通过 Zookeeper 客户端创建节点,节点路径由 toUrlPath 方法生成,路径格式如下:
// ${group}/${serviceInterface}/providers/${url}
// NOTE: 比如: /dubbo/org.apache.dubbo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1....
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
- zkClient中创建节点
//AbstractZookeeperClient
public void create(String path, boolean ephemeral) {
if (!ephemeral) {
if (checkExists(path)) {
return;
}
}
//NOTE: 层层解析,创建zk节点
int i = path.lastIndexOf('/');
if (i > 0) {
create(path.substring(0, i), false);
}
//NOTE: 是否创建临时节点
if (ephemeral) {
createEphemeral(path);
} else {
createPersistent(path);
}
}
//CuratorZookeeperClient
public void createEphemeral(String path) {
try {
// 通过 Curator 框架创建节点
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (NodeExistsException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
3、总结
服务远程暴露,创建Invoker简单,但后续的服务导出流程比较长,主要在创建Exporter和服务注册过程很复杂。而且在RegistryProtocol和DubboProtocol执行逻辑中,都会用类似ProtocolFilterWrapper或者ProtocolListenerWrapper这种封装类来实现AoP增加一些功能。在服务注册过中,路径特别深,有一些我还没有深入,比如ExchangerServer的创建、Transporter的创建、Registry的创建细节。理解服务远程暴露主要包含:
- 将服务封装成一个Exoprter进行服务导出;
- 将服务提供者关联的URL配置信息,在注册中心按照目录结构创建一个个节点。(节点的信息是按照GIV的形式来构建的)
4、附件
RegistryProtocol内部注入的属性:RegistryFactory、Cluster
4.1、RegistryFactory$Adaptive
package org.apache.dubbo.registry;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class RegistryFactory$Adaptive implements org.apache.dubbo.registry.RegistryFactory {
public org.apache.dubbo.registry.Registry getRegistry(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) {
throw new IllegalArgumentException("url == null");
}
org.apache.dubbo.common.URL url = arg0;
String extName = ((url.getProtocol() == null) ? "dubbo" : url.getProtocol());
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(org.apache.dubbo.registry.RegistryFactory) name from url(" +
url.toString() + ") use keys([protocol])");
}
org.apache.dubbo.registry.RegistryFactory extension = (org.apache.dubbo.registry.RegistryFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.registry.RegistryFactory.class).getExtension(extName);
return extension.getRegistry(arg0);
}
}
4.2、Cluster$Adaptive
package org.apache.dubbo.rpc.cluster;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Cluster$Adaptive implements org.apache.dubbo.rpc.cluster.Cluster {
public org.apache.dubbo.rpc.Invoker join(org.apache.dubbo.rpc.cluster.Directory arg0)
throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) {
throw new IllegalArgumentException(
"org.apache.dubbo.rpc.cluster.Directory argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException(
"org.apache.dubbo.rpc.cluster.Directory argument getUrl() == null");
}
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("cluster", "failover");
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(org.apache.dubbo.rpc.cluster.Cluster) name from url(" +
url.toString() + ") use keys([cluster])");
}
org.apache.dubbo.rpc.cluster.Cluster extension = (org.apache.dubbo.rpc.cluster.Cluster) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
return extension.join(arg0);
}
}