概述
在 本地暴露 中已经对服务暴露的整个流程进行了介绍,并深入分析了本地服务暴露,本篇文章将接着本地服务暴露,继续分析远程服务暴露。远程服务暴露相比本地服务暴露,区别点如下:
- 暴露并启动服务,本地暴露是无需启动服务的
- 服务注册,本地暴露无需注册到注册中心
- 配置订阅,订阅配置信息,当配置发生变化尝试重新暴露
远程暴露
由于服务暴露的主干流程在本地暴露中已经详细说明,这里对远程暴露的细节进行说明。下面我们继续从服务 URL 入手分析。经过服务暴露准备操作之后,得到的服务 URL 如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| dubbo://10.1.1.202:20880/com.alibaba.dubbo.demo.DemoService? anyhost=true &application=demo-provider &bean.name=com.alibaba.dubbo.demo.DemoService &bind.ip=10.1.17.202 &bind.port=20880 &dubbo=2.0.2 &generic=false &group=abc &interface=com.alibaba.dubbo.demo.DemoService &methods=sayHello &owner=gentryhuang &pid=1665&qos.port=22222 &server=netty4& side=provider ×tamp=1638155815325
|
协议暴露服务
完成服务 URL 的组装后,服务暴露的准备工作也就完成了,接下来就可以执行服务暴露工作了。宏观层面上服务暴露逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| +--- ServiceConfig
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).hasExtension(url.getProtocol())) { url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getExtension(url.getProtocol()).getConfigurator(url).configure(url); }
String scope = url.getParameter(Constants.SCOPE_KEY);
if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {
if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) { exportLocal(url); }
if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) { if (logger.isInfoEnabled()) { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); }
if (registryURLs != null && !registryURLs.isEmpty()) { for (URL registryURL : registryURLs) { url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) { url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString()); }
if (logger.isInfoEnabled()) { logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL); }
String proxy = url.getParameter(Constants.PROXY_KEY); if (StringUtils.isNotEmpty(proxy)) { registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy); } Invoker<?> invoker = proxyFactory.getInvoker( ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()) );
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter); }
} else { Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } } } this.urls.add(url); }
|
在服务暴露的流程中可知,会尝试向每个注册中心发布服务。如果不存在或无效注册中心,那么仅暴露服务,不会将服务信息发布到注册中心。Consumer没法在注册中心找到该服务的信息,但是可以直连。其中,无效的原因可能是服务只订阅不注册,这样的话就不会向注册中心发布。
在服务暴露的流程中,会先将服务对象 ref 通过 ProxyFactory 包装成 AbstractProxyInvoker 对象,完成包装后才会根据 Dubbo SPI 使用对应的 Protocol 实现暴露服务。由于可能有注册中心,也可能没有注册中心,因此暴露流程分为两个分支:
- 有注册中心:会遍历全部 RegistryURL,并根据 RegistryURL 选择对应的 Protocol 扩展实现进行发布。因为 RegistryURL 是 registry:// 协议,所以这里使用的是 RegistryProtocol 实现。这个协议是注册中心、服务提供者以及服务消费者连接的强梁。
- 没有注册中心:直接根据服务提供者 URL 选择对应的 Protocol 扩展实现进行发布。由于服务提供者可能使用不同协议暴露,因此这里可能是 dubbo://、http:// 等协议,针对不同的协议使用对应的 Protocol 实现暴露服务即可。
不基于注册中心发布服务
不基于注册中心发布服务,就是直接使用服务提供者 URL 对应的协议实现发布服务。
针对 dubbo://
协议暴露实现,参考 Dubbo协议-服务暴露
针对 http://
协议暴露实现,参考 Http协议-服务暴露
基于注册中心发布服务
基于注册中心发布服务需要使用到注册中心,Dubbo 通过封装 RegistryProtocol
来完成。这个过程包含以下四个核心步骤:
- 服务暴露
- 服务 Server 启动
- 服务注册
- 服务配置订阅
RegistryProtocol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public class RegistryProtocol implements Protocol {
private final static Logger logger = LoggerFactory.getLogger(RegistryProtocol.class);
private static RegistryProtocol INSTANCE;
private final Map<URL, NotifyListener> overrideListeners = new ConcurrentHashMap<URL, NotifyListener>();
private final Map<String, ExporterChangeableWrapper<?>> bounds = new ConcurrentHashMap<String, ExporterChangeableWrapper<?>>();
private Cluster cluster;
private Protocol protocol;
private RegistryFactory registryFactory;
private ProxyFactory proxyFactory;
public RegistryProtocol() { INSTANCE = this; }
public static RegistryProtocol getRegistryProtocol() { if (INSTANCE == null) { ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(Constants.REGISTRY_PROTOCOL); } return INSTANCE; } }
|
暴露并启动服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| +--- RegistryProtocol
@SuppressWarnings("unchecked") private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
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) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter); } } }
return exporter; }
|
以上方法做了以下两件事:
- 通过具体的 Protocol 协议实现,如 DubboProtocol 暴露服务。其中,在通过具体 Protocol 协议暴露服务的过程中会启动 Server。
- 缓存暴露的 Export,key 是服务提供方的 URL 剪枝后的 URL 串。
具体协议暴露服务和启动 Server 的细节可参考:
服务注册与订阅
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| +--- RegistryProtocol @Override public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
URL registryUrl = getRegistryUrl(originInvoker);
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
boolean register = registeredProviderUrl.getParameter("register", true);
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
if (register) {
register(registryUrl, registeredProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true); }
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl); }
|
小结
本篇文章介绍了 Dubbo 远程暴露的实现。远程暴露需要实现 暴露服务
、启动 Server
、服务注册
和 服务配置订阅
。前两个是由具体 Protocol 实现的,如 DubboProtocol 会将传入的 Invoker 包装成 DubboExporter 对象,并以服务键作为缓存 key 缓存起来,接着启动服务,具体来说是 Netty 服务器。后两个是有 RegistryProtocol 逻辑实现,需要根据是否有有效的注册中心,决定是否注册服务和订阅服务配置,否则只暴露和启动Server,这样情况下只能直连。