Dubbo服务导出如何实现的(我们从中能够学习到什么?)

Dubbo通过注解或者xml形式标识Dubbo服务,在Spring 容器发布刷新事件,会立即执行服务导出逻辑,示例如下:

import com.alibaba.dubbo.config.annotation.Service; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; import java.util.stream.Collectors; /** * 价格服务 **/ @Service @Component public class PriceFacade implements IPriceFacade { }

Dubbo服务导出分为本地导出和远程导出,本地服务导出就是本地方法的调用,远程导出就是通过设置的通信方式进行远程服务调用,服务导出后,Dubbo还支持向不同的注册中心注册导出的服务。以下重点分析多协议多注册中心服务导出的过程,其大致流程如下:
Dubbo服务导出如何实现的(我们从中能够学习到什么?)
文章图片

简单总结服务导出的流程,就是Dubbo根据设置的协议导出服务,如果是远程服务导出,则根据设置的协议,例如TCP/IP或者HTTP协议进行远程通信。如果设置了服务注册中心,则会在服务导出后,向注册中心注册服务。
在Dubbo中,对于协议Protocol,通信Transporter,以及服务注册Registry的选择都是基于Dubbo SPI自适应机制实现的。如果,配置了服务导出的过滤器或者监听器,也会根据Dubbo SPI自动激活的机制加载对应的集合对象。Dubbo SPI的机制详解可以参考Dubbo SPI是什么?Dubbo SPI如何实现?我们从中能够学习到什么?。
多协议多注册中心 在Spring 容器发布刷新事件,会调用Dubbo配置ServiceConfig的export方法进行服务导出。在doExportUrls方法中执行多协议多注册中心的服务导出逻辑。在doExportUrlsFor1Protocol方法中,根据到协议导出服务,根据配置执行本地服务导出或远程服务导出,并且根据是否有注册中心的配置执行舒服注册逻辑,其部分源码如下:
// ServiceConfig.class // 服务导出 public synchronized void export() { if (!shouldExport()) { return; }if (bootstrap == null) { bootstrap = DubboBootstrap.getInstance(); bootstrap.init(); }//------- 省略 ---------//if (shouldDelay()) { DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); } else { doExport(); }exported(); }// ServiceConfig.class // 多协议服务导出,多注册中心注册 protected synchronized void doExport() { if (unexported) { throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!"); } if (exported) { return; } exported = true; if (StringUtils.isEmpty(path)) { path = interfaceName; } doExportUrls(); }// ServiceConfig.class // 服务导出 private void doExportUrls() { ServiceRepository repository = ApplicationModel.getServiceRepository(); ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass()); repository.registerProvider( getUniqueServiceName(), ref, serviceDescriptor, this, serviceMetadata ); // 注册中心URL List registryURLs = ConfigValidationUtils.loadRegistries(this, true); // 多协议 for (ProtocolConfig protocolConfig : protocols) { String pathKey = URL.buildKey(getContextPath(protocolConfig) .map(p -> p + "/" + path) .orElse(path), group, version); repository.registerService(pathKey, interfaceClass); serviceMetadata.setServiceKey(pathKey); // 单个协议的服务导出 doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }// ServiceConfig.class // 单个协议的服务导出 private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) { String name = protocolConfig.getName(); if (StringUtils.isEmpty(name)) { name = DUBBO; }//------- 省略参数设置部分 --------// // 服务导出 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 (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { continue; } url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY)); // 监视器配置 URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL); if (monitorUrl != null) { url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString()); } String proxy = url.getParameter(PROXY_KEY); if (StringUtils.isNotEmpty(proxy)) { registryURL = registryURL.addParameter(PROXY_KEY, proxy); } // 动态代理生成Invoker Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) // Invoker包装类 interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); // 协议导出服务 Exporter exporter = PROTOCOL.export(wrapperInvoker); exporters.add(exporter); } } // 没有注册中心直接导出服务 else { // 动态代理生成Invoker Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); // 协议导出服务 Exporter exporter = PROTOCOL.export(wrapperInvoker); exporters.add(exporter); }WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE)); if (metadataService != null) { metadataService.publishServiceDefinition(url); } } } this.urls.add(url); }

动态代理Invoker
在服务导出过程中,使用动态代理的技术,把所有dubbo服务都转换为Invoker对象,通过调用invoke方法,Invoker统一了所有服务的调用方式,Invoker接口定义如下:
public interface Invoker extends Node {/** * 调用服务Class对象 */ Class getInterface(); /** * 代理方法,invocation封装了服务调用参数 */ Result invoke(Invocation invocation) throws RpcException; }

在Dubbo中,使用Javassist和JDK动态代理两种方式,实现了服务代理类生成,Dubbo默认使用Javassist方式。具体的动态代理定义可以参考代理(Proxy)是什么?为什么要使用代理模式?Spring与Dubbo如何运用代理模式的?。Javassist和JDK动态代理两种生成Invoker的源码如下:
/** * javassist */ public class JavassistProxyFactory extends AbstractProxyFactory {@Override @SuppressWarnings("unchecked") public T getProxy(Invoker invoker, Class[] interfaces) { return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); }@Override public Invoker getInvoker(T proxy, Class type, URL url) { // TODO Wrapper cannot handle this scenario correctly: the classname contains '$' final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); return new AbstractProxyInvoker(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class[] parameterTypes, Object[] arguments) throws Throwable { return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; }}/** * Jdk动态代理 */ public class JdkProxyFactory extends AbstractProxyFactory {@Override @SuppressWarnings("unchecked") public T getProxy(Invoker invoker, Class[] interfaces) { return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker)); }@Override public Invoker getInvoker(T proxy, Class type, URL url) { return new AbstractProxyInvoker(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class[] parameterTypes, Object[] arguments) throws Throwable { Method method = proxy.getClass().getMethod(methodName, parameterTypes); return method.invoke(proxy, arguments); } }; }}

服务导出协议
在Dubbo中,支持不同协议的服务导出,主要的包括本地服务导出,基于TCP/IP,HTTP,RMI,THRIFT等等。定义了Protocol接口统一了对不同协议的封装,接口其服务导出,引用的定义如下:
public interface Protocol {/** * 导出服务 */ @Adaptive Exporter export(Invoker invoker) throws RpcException; /** * 引用服务 */ @Adaptive Invoker refer(Class type, URL url) throws RpcException; }

在Dubbo中,分别对不同基础协议定义了不同的实现类,如下:
  • InjvmProtocol本地导出
  • DubboProtocol默认协议设置,使用TCP/IP通信协议导出服务
  • HttpProtocol,使用HTTP协议导出服务
  • RestProtocol,基于Rest 风格导出服务
  • HessianProtocol,RmiProtocol,ThriftProtocol,WebServiceProtocol,XmlRpcProtocol
其类结构图如下:
Dubbo服务导出如何实现的(我们从中能够学习到什么?)
文章图片

使用装饰模式(具体如何使用可以参考装饰器模式-Mybatis教你如何玩),对Protocol进行装饰,其实现的装饰类如下:
  • ProtocolFilterWrapper,过滤器装饰类,对Protocol的export和refer方法,增加链式的过滤处理。例如,日志打印,并发访问控制,类加载转换等等操作。
  • ProtocolListenerWrapper,事件监听装饰类,定义ExporterListener监听服务导出成功或者失败的事件。
其类结构图如下:
Dubbo服务导出如何实现的(我们从中能够学习到什么?)
文章图片

通信协议
不同的协议使用不同的通信方式进行服务的远程调用,主要包括TCP/IP,HTTP通信。DubboProtocol采用的TCP/IP协议通信,HttpProtocol采用HTTP协议进行通信。Dubbo定义Transporter接口,监听服务端口以及链接客户端,其接口定义如下:
public interface Transporter {/** * 启动服务端,绑定监听端口 */ @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException; /** * 链接服务端,创建客户端 */ @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) Client connect(URL url, ChannelHandler handler) throws RemotingException; }

采用mina和netty两种框架进行实现,其实现类分别为MinaTransporter,基于netty3的NettyTransporter 和基于netty4的NettyTransporter,其类结构图如下:
Dubbo服务导出如何实现的(我们从中能够学习到什么?)
文章图片

定义HttpBinder接口,定义基于HTTP通信协议,其接口定义如下:
public interface HttpBinder {/** * 绑定HTTP服务器 */ @Adaptive({Constants.SERVER_KEY}) HttpServer bind(URL url, HttpHandler handler); }

其实现类分别为JettyHttpBinder,ServletHttpBinder,TomcatHttpBinder,其类结构图如下:
【Dubbo服务导出如何实现的(我们从中能够学习到什么?)】Dubbo服务导出如何实现的(我们从中能够学习到什么?)
文章图片

注册中心
如果导出服务时,配置了注册中心,则Dubbo会根据SPI机制,动态的选择RegistryProtocol。RegistryProtocol使用了装饰模式,具体的服务导出逻辑,交由其设置的Protocol。在export中,定义向注册中心的逻辑。其部分源码如下:
// 实现Protocol接口 public class RegistryProtocol implements Protocol {// 被装饰的Protocol private Protocol protocol; @Override public Exporter export(final Invoker originInvoker) throws RpcException {// 注册中心配置 URL registryUrl = getRegistryUrl(originInvoker); URL providerUrl = getProviderUrl(originInvoker); final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl); final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); // 使用protocol导出服务 final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl); final Registry registry = getRegistry(originInvoker); final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { // 向设置的注册中心注册服务 register(registryUrl, registeredProviderUrl); } registerStatedUrl(registryUrl, registeredProviderUrl, register); registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); exporter.setRegisterUrl(registeredProviderUrl); exporter.setSubscribeUrl(overrideSubscribeUrl); notifyExport(exporter); return new DestroyableExporter<>(exporter); }private ExporterChangeableWrapper doLocalExport(final Invoker originInvoker, URL providerUrl) { String key = getCacheKey(originInvoker); return (ExporterChangeableWrapper) bounds.computeIfAbsent(key, s -> { Invoker invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); // 使用被装饰的protocol导出服务 return new ExporterChangeableWrapper<>((Exporter) protocol.export(invokerDelegate), originInvoker); }); }/** * 使用Registry向注册中心注册服务 */ private void register(URL registryUrl, URL registeredProviderUrl) { Registry registry = registryFactory.getRegistry(registryUrl); registry.register(registeredProviderUrl); } }

在Dubbo中,定义Registry接口,向注册中心注册服务,其接口定义如下:
public interface Registry extends Node, RegistryService { default void reExportRegister(URL url) { register(url); }default void reExportUnregister(URL url) { unregister(url); } }

其具体的实现类包括,NacosRegistry,ZookeeperRegistry,SofaRegistry和RedisRegistry,其类结构图如下:

Dubbo服务导出如何实现的(我们从中能够学习到什么?)
文章图片

我们从中能够学习到什么设计模式的使用
  • 装饰模式,Protocol协议的装饰,具体实现可以参考装饰器模式-Mybatis教你如何玩。
  • 责任链模式,ProtocolFilterWrapper,对Protocol进行过滤装饰时,采用链表的形式实现了责任链的过滤。具体的责任链设计可以参考责任链如何设计?Tomcat和Netty分别是如何实现过滤器链和事件链?
  • 事件监听模式,ProtocolFilterWrapper,对Protocol成功导出服务和导出服务失败进行事件的监听。具体的事件监听设计可以参考事件驱动模式-Tomcat和zookeeper教你如何玩
动态代理的使用
使用Javassit或者JDK动态代理技术生成Invoker实例,统一了对所有服务方法的执行,并且方便对Invoker进行其他逻辑的装饰。具体的动态代理理解可以参考代理(Proxy)是什么?为什么要使用代理模式?Spring与Dubbo如何运用代理模式的?
通信协议的使用
在远程服务绑定服务时,使用了mina与netty框架,实现了对TCP/IP协议,基于NIO进行远程服务通信,NIO的详细使用可以参考Netty如何实现常见的两种线程模式?dubbo基于Netty发布服务时如何选择线程模式?

    推荐阅读