Dubbo作为一个Rpc框架,服务端必须得将自己暴露出去,以便客户端的调用,所以我们来看一下dubbo是如何将服务进行暴露的。


首先我们知道,启动dubbo得进行一些配置,如下图所示的一些dubbo标签(关于spring为什么能识别dubbo标签,可以搜索一下spring的schema机制,这里不做阐述,因为不是重点)

然后我们可以在下图的文件中找到两个命名空间处理器(因为dubbo是由阿里巴巴捐赠给apache),点进apache的文件里面

进去之后可以看到就是将配置文件中的信息解析到bean中存放于ioc,那因为我们要暴露service,所以我们看下ServiceBean

public class DubboNamespaceHandler extends NamespaceHandlerSupport {static {Version.checkDuplicate(DubboNamespaceHandler.class);}@Overridepublic void init() {/*** 解析配置文件中<dubbo:xxx></dubbo:xxx> 相关的配置,并向容器中注册bean信息*/registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());}}

在ServiceBean这个类中就可以看到服务暴露的方法,我们一路向里点

/*** dubbo服务导出入口,* 当容器初始化完成之后,需要处理一些操作,比如一些数据的加载、初始化缓存、特定任务的注册等等。* 这个时候我们就可以使用Spring提供的ApplicationListener来进行操作* @param event*/@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {if (!isExported() && !isUnexported()) {if (logger.isInfoEnabled()) {logger.info("The service ready on spring started. service: " + getInterface());}/*** 导出服务*/export();}}

一直看到ServiceConfig中的doExportUrl(),上卖弄这个list就是加载我们配置文件中需要注册到的注册中心

/*** dubbo服务导出核心*/private void doExportUrls() {//加载配置文件中的所有注册中心配置,并且封装为dubbo内部的URL对象列表List<URL> registryURLs = loadRegistries(true);//循环所有协议配置,根据不同的协议,向注册中心中发起注册 dubbo provider可能提供多种协议服务,默认dubbo协议,还有其他的比如Redis,Thrift等for (ProtocolConfig protocolConfig : protocols) {String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);ApplicationModel.initProviderModel(pathKey, providerModel);//核心doExportUrlsFor1Protocol(protocolConfig, registryURLs);}}

在方法doExportUrlsFor1Protocol()中可以看到将信息组装成Url的形式进行传输,这url其实就是我们注册中心的信息以及服务信息

// 组装 URLURL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

接下来就是为该服务生成一个代理,在进行调用时,先经过代理在调用服务

首先是PROXY_FACTORY进行的一个自适应,可以看到getInvoker()上有个@Adaptive,然后默认选择的是javassist,也就是生成的一个代理

// 为服务提供类(ref)生成 Invoker/*** Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,* 它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现** 在服务提供方,Invoker用于调用服务提供类。在服务消费方,Invoker用于执行远程调用** Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory*/Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));/*** A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its* default implementation*/private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();@SPI("javassist")
public interface ProxyFactory {/*** create proxy.** @param invoker* @return proxy*/@Adaptive({PROXY_KEY})<T> T getProxy(Invoker<T> invoker) throws RpcException;/*** create proxy.** @param invoker* @return proxy*/@Adaptive({PROXY_KEY})<T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;/*** create invoker.** @param <T>* @param proxy* @param type* @param url* @return invoker*/@Adaptive({PROXY_KEY})<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;}

于是我们选择JavassistProxyFactory中的getInvoker(),可以看到最终执行实例的调用就是用invokeMethod()

    @Overridepublic <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'// 为目标类创建 Wrapper/*** Wrapper 是一个抽象类,其中 invokeMethod 是一个抽象方法。Dubbo 会在运行时通过 Javassist 框架为 Wrapper 生成实现类,* 并实现 invokeMethod 方法,该方法最终会根据调用信息调用具体的服务。以 DemoServiceImpl 为例,Javassist 为其生成的代理类如下。*  Wrapper0 是在运行时生成的,大家可使用 Arthas 进行反编译*public class Wrapper0 extends Wrapper implements ClassGenerator.DC {*public static String[] pns;*public static Map pts;*public static String[] mns;*public static String[] dmns;*public static Class[] mts0;*public Object invokeMethod(Object object, String string, Class[] arrclass, Object[] arrobject) throws InvocationTargetException {*DemoService demoService;*try {*             // 类型转换*      demoService = (DemoService) object;*}*         catch(Throwable throwable){*throw new IllegalArgumentException(throwable);*}*try {*             // 根据方法名调用指定的方法*if ("sayHello".equals(string) && arrclass.length == 1) {*return demoService.sayHello((String) arrobject[0]);*}*}*         catch(Throwable throwable){*throw new InvocationTargetException(throwable);*}throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.alibaba.dubbo.demo.DemoService.").toString());*}*}**/final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);// 创建匿名 Invoker 类对象,并实现 doInvoke 方法。return new AbstractProxyInvoker<T>(proxy, type, url) {@Overrideprotected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable {// 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);}};}

然后我们继续回到ServiceConfig中,可以看到用协议进行了导出,其实导出分两个步骤,一个是启动本地服务,然后是像注册中心中去注册该服务,那这里的代码就是根据协议会选择RegistryProtocol中的export

/*** 导出服务,并生成 Exporter 与导出服务到本地相比,导出服务到远程的过程要复杂不少,其包含了服务导出与服务注册两个过程** debug此处查看应该走哪个protocol的export* 走的是 RegistryProtocol*/Exporter<?> exporter = protocol.export(wrapperInvoker);

接着就能看到核心的代码,一个是启动服务,一个是注册,首先先是将url进行了解析然后配置了监听,随后执行了服务的导出,我们点进去看

接着可以看到根据url的信息找到DubboProtocol,并返回一个Exporter

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {// dubbo://192.168.200.10:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.200.10&bind.port=20880&deprecated=false&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=15804&qos.port=22222&register=true&release=&side=provider&timestamp=1622539267498String key = getCacheKey(originInvoker);//访问缓存return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {//创建 Invoker 委托类对象 DelegateInvoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);//----****重点跟 protocol.export(invokerDelegate) 方法,此处protocol根据SPI机制,根据URL中的参数找 DubboProtocol 实现 即根据协议导出return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);});}

随后我们就可以在其中到看到先是拿到url,然后创建了相对应的exporter并存放于缓存之中,随后就是根据url开启了一个服务,可以看到默认采取的是netty启动的

接着就是一个dcl,判断了一下是否服务,是否启动,最后创建并能放入缓存之中

private void openServer(URL url) {// find server. key=192.168.200.10:20880String key = url.getAddress();//client can export a service which's only for server to invokeboolean isServer = url.getParameter(IS_SERVER_KEY, true);if (isServer) {ExchangeServer server = serverMap.get(key); // serverMap根据ip:port缓存Server对象      因为服务端可能在本机不同端口暴露if (server == null) {synchronized (this) {server = serverMap.get(key);if (server == null) {serverMap.put(key, createServer(url)); // createServer是核心----****重点去关注******}}} else {// server supports reset, use together with overrideserver.reset(url);}}}

随后就是在url中添加一系列的参数,如果心跳检测的、编解码参数等,接着就是启动服务

能看到的是获取了一个exchanger并绑定了我们的url以及处理器,exchanger这里也是一个拓展点,所以,默认采取的是HeaderExchanger

public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {if (url == null) {throw new IllegalArgumentException("url == null");}if (handler == null) {throw new IllegalArgumentException("handler == null");}//向url中添加codec参数 dubbo://192.168.200.10:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.200.10&bind.port=20880&channel.readonly.sent=true&codec=dubbo&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&heartbeat=60000&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=6052&qos.port=22222&register=true&release=&side=provider&timestamp=1622539986773url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");//getExchanger(url)根据SPI返回的是 HeaderExchangerreturn getExchanger(url).bind(url, handler);}

在这个bin的过程中

  1. 创建了HeaderExchangeHandler用于负载处理请求协会结果
  2. 对请求和响应结果做了编解码操作
  3. 最后就是绑定了地址
public class HeaderExchanger implements Exchanger {public static final String NAME = "header";@Overridepublic ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {/*** 这里包含了多个调用,分别如下:* 1. 创建 HeaderExchangeHandler 对象 参数handler= DubboProtocol.requestHandler  很重要,涉及到发送请求时的处理* 2. 创建 DecodeHandler 对象* 3. 通过 Transporters 构建 Client 实例* 4. 创建 HeaderExchangeClient 对象** 重点看:Transporters.connect*/return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);}@Overridepublic ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {// 创建 HeaderExchangeServer 实例,该方法包含了多个逻辑,分别如下://   1. new HeaderExchangeHandler(handler)                                                负载处理请求回写结果//   2. new DecodeHandler(new HeaderExchangeHandler(handler))                             对请求数据和响应结果进行解码操作,如何交由后续流程继续处理//   3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))     重点看这个bind方法,return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));}}

来到netty的传输层中,看到创建了netty的服务,我们能接着往下点

最后在doOpen()方法中看到熟悉的netty的代码,跟我们写的几乎一样,在这里呢就是服务的暴露流程

接着我们回到之前,服务暴露之后我们需要将服务注册到注册中心去

在这里呢,就是将将服务进行注册,registryFactory不用多说,典型的拓展点,会根据spi机制,先找到FailbackRegistry,在其中的doRegister()找到我们配置的实现--zookeeper的实现类

public void register(URL registryUrl, URL registeredProviderUrl) {// zookeeper://192.168.200.129:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.200.10%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bean.name%3Dorg.apache.dubbo.demo.DemoService%26bind.ip%3D192.168.200.10%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D12416%26qos.port%3D22222%26register%3Dtrue%26release%3D%26side%3Dprovider%26timestamp%3D1622540622168&pid=12416&qos.port=22222&timestamp=1622540622168/***  @Adaptive({"protocol"})*  Registry getRegistry(URL url);*  根据SPI机制找到  ZookeeperRegistry extends FailbackRegistry  extends AbstractRegistry*  看这里:register方法在 FailbackRegistry 中,真正执行注册是在ZookeeperRegistry中的doRegister方法里*/Registry registry = registryFactory.getRegistry(registryUrl);registry.register(registeredProviderUrl);}

那这里呢,就是做了一些节点的创建,将我们的服务注册到注册中心去


以上呢,就是dubbo的服务端暴露服务的内容。

Dubbo服务暴露(导出)流程相关推荐

  1. Dubbo服务暴露的流程

    在Dubbo服务暴露中,需要被暴露的服务的接口类首先会通过proxyFactory代理工厂获得代理的对象invoker,而暴露之后的服务被调用,也将都会通过这个invoker来调用相关的类. 在dub ...

  2. dubbo服务暴露流程总结

    这篇文章主要总结一下dubbo服务端启动的时候服务暴露过程,虽然官方网站和各种博客上已经有很多介绍服务暴露的帖子,但还是想把自己跟源码过程中遇到的问题和心得记录下来,算是个总结,并且本篇文章是基于du ...

  3. 面试官问我:解释一下Dubbo服务暴露

    今天我们要分析的就是Dubbo的服务暴露过程,这个过程属于Dubbo的核心过程之一了,因为Dubbo的大体流程就是服务暴露->服务引用->服务消费这几个主流程,当然还会涉及到注册发现.负载 ...

  4. Dubbo——服务暴露过程分析

    这篇文章来叙述对dubbo的服务暴露前的准备工作: 使用Spring配置文件,通过main方法来启动spring容器,来观察dubbo服务的启动过程. dubbo配置文件 <context:co ...

  5. dubbo服务暴露原理解析

    配置解析 dubbo 的各个配置项,详细的可以参考官网 只有 group,interface,version 是服务的匹配条件,三者决定是不是同一个服务,其它配置项均为调优和治理参数 所有的配置最终都 ...

  6. Dubbo服务暴露原理

    服务暴露原理 配置文件 IOC容器启动,加载配置文件的时候 Dubbo标签处理器,解析每一个标签 封装成对应的组件 service 解析service标签 将service标签信息,封装成Servic ...

  7. 深入解析 Dubbo 3.0 服务端暴露全流程

    简介:随着云原生时代的到来,Dubbo 3.0 的一个很重要的目标就是全面拥抱云原生.正因如此,Dubbo 3.0 为了能够更好的适配云原生,将原来的接口级服务发现机制演进为应用级服务发现机制. 作者 ...

  8. Dubbo服务端暴露全流程

    本文来说下Dubbo服务端暴露全流程 文章目录 概述 什么是应用级服务发现 服务端暴露全流程 暴露injvm协议的服务 注册service-discovery-registry协议 暴露Triple协 ...

  9. Dubbo/Dubbox的服务暴露(一)

    前言 源码入手 平时我要了解一个框架,基本会去从他的Listener入手,如果web.xml中没有配置listener可能还会有 filter,这是spring给我们的启示,可是当要去了解dubbo的 ...

最新文章

  1. php怎样验证验证码对错,PHP生成中文验证码并检测对错实例
  2. CentOS8下安装docker
  3. transformer中attention计算方式_Transformer在推荐模型中的应用总结
  4. 苹果mac图像后期处理软件:Lightroom Classic
  5. 收获,不止oracle
  6. mysql的engine不同,导致事物回滚失败的问题
  7. Garmin报警点完善计划
  8. 机器学习深度学习入门学习资料大全(一)
  9. 人脸数据集——亚洲人脸数据集
  10. 【MIT 6.S081】实验四:traps (实验暂停)
  11. matlab firl,matlab 利用matlab工具箱函数fir1 联合开发网 - pudn.com
  12. 解决Svn图标不显示或者显示异常(亲测有效)
  13. web 打印实用控件 lodop
  14. android 清空剪贴板,清空剪贴板app
  15. 分布式任务调度组件 Uncode-Schedule
  16. 无损内嵌字幕到mkv文件
  17. android listview阻尼效果,自定义阻尼效果listview
  18. 从零部署Linux服务器完全指南2022版(CentOS 8+Nginx+PHP)
  19. gamit错误提示:PCN-code missing for receiver type TRMR12 in rcvant.dat
  20. mb_detect_encoding php,php mb_detect_encoding检测字符串编码有误的问题

热门文章

  1. python基本代码教程-Python基础教程(第3版)
  2. 《语音识别技术》.ppt
  3. 程序员接私活的几个平台
  4. 产品开发管理方法工具流程 pdf_HR必备薪酬和绩效管理方法论、工具、案例
  5. mouseover和mouseenter的区别
  6. 蓝桥杯笔记:带分数(dfs排列问题)
  7. 滑动窗口:字符串的变位词(排列)leetcode567,leetcode438
  8. react 动态添加组件属性_React的组件动态参数使用Underscore和Context来传递
  9. java jquery easyui_java中用jquery-easyui插件做可编辑datagird列表
  10. 计算机人员简历英语,计算机专业英文个人简历范文