dubbo的服务发布

本篇了解一下dubbo的服务发布流程
先来个总得流程图
dubbo服务发布流程图

一. 从配置文件走起

在这里插入图片描述
这个xml文件是dubbo提供的一个demo,用过dubbo的同学应该都了解这就是暴露一个接口demoService,那么这个接口如何暴露出去的呢?
既然是配置了这个xml就能暴露出去,那么肯定是从xml文件的配置开始,上图圈出来的红框中,很明显看出来是用的schema定义的xml文件,不了解的同学可以百度一下:schema自定义配置这个关键词,了解下schema自定义配置相关知识,这里就不再讲了,提供一个参考链接:https://www.cnblogs.com/jifeng/archive/2011/09/14/2176599.html
那么了解了schema自定义配置之后,就可以找到关于

<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>

这行代码具体怎么开始了
在这里插入图片描述
在这里插入图片描述
上面这两个图表示了service标签的定义
在这里插入图片描述
org.apache.dubbo.config.spring.schema.DubboNamespaceHandler#init中可以看到对service标签的解析器:new DubboBeanDefinitionParser(ServiceBean.class, true),所以我们进入org.apache.dubbo.config.spring.ServiceBean里面查看源码

二. ServiceBean

查看ServiceBean源码
在这里插入图片描述
上面这些实现的接口都是spring的接口,其实主要作用就是保存了spring容器的引用,并且监听了spring容器的启动事件,再看看ServiceBean的关系图
在这里插入图片描述
ServiceBean中有一个方法
在这里插入图片描述
他会发出一个Export事件
在这里插入图片描述
在这里插入图片描述
从注释可以发现是调用了org.apache.dubbo.config.spring.ServiceBean#exported,这个方法在上面截图有了,里面调用了父类的org.apache.dubbo.config.ServiceConfig#exported在这里插入图片描述
可以看到依旧是发布了一个事件,这个事件点进去看:
在这里插入图片描述
实际是走到了org.apache.dubbo.config.ServiceConfig#export
在这里插入图片描述
上面这里和dubbo老版本的区别很大,走了事件发布和监听,不用管太多,只要找到org.apache.dubbo.config.ServiceConfig#export这里,其实是真正开始发布服务,我们重点关注里面的方法:org.apache.dubbo.config.ServiceConfig#doExport

三. doExport

在这里插入图片描述
doExportUrls方法中,首先是加载配置文件中dubbo.registry.address的注册中心信息并组装到registryURLs,然后下面是一个for循环,这个循环的protocols是代表通信协议,for循环表示一个服务可以有多个通信协议,比如tcp、http,默认是dubbo的tcp协议
然后进入:org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol这个方法,这个代码很多,前面都是各种校验和组装信息
在这里插入图片描述
重点在这里,可以看到注释写的是暴露本地服务和暴露远程服务,本地服务指的就是本地服务JVM里面的服务,不用通过zookeeper进行远程网络通信调用;那么远程服务就是通过zookeeper网络通信调用,暴露给可以给其他服务调用,这个应该不难理解
那么我们来看暴露本地服务的流程:

3.1 本地服务暴露

在这里插入图片描述
首先PROTOCOLPROXY_FACTORY分别都是从SPI中获取的,getAdaptiveExtension说明是获取的是个自适应的扩展类,那么分别看org.apache.dubbo.rpc.Protocolorg.apache.dubbo.rpc.ProxyFactory,这两个接口的@Adaptive注解都是标注在接口上,说明是动态生成的扩展类(这块是SPI的知识,不了解的可以看我之前的文章:dubbo的SPI)
在这里插入图片描述
其中ProxyFactory的作用就是为了获取一个接口的代理类,例如获取一个远程接口的代理。
ProxyFactory有2个方法,

  1. getInvoker:针对server端,将服务对象,如DemoServiceImpl包装成一个Invoker对象。
  2. getProxy :针对client端,创建接口的代理对象,例如DemoService的接口。

然后Protocol的默认实现是@SPI("dubbo")-> org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol,但是实际上这个Protocol在这里是一个动态生成的类Protoclo$Adpative
ProxyFactory的默认实现是@SPI("javassist")->org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory,同样的这个ProxyFactory在这里也是一个动态生成的类ProxyFactory$Adpative
回到org.apache.dubbo.config.ServiceConfig#exportLocal方法中,看方法调用PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local),其实调用的就是org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker
在这里插入图片描述
进入这个方法,首先看到是获取一个Wrapper,这个wrapper是从org.apache.dubbo.common.bytecode.Wrapper#makeWrapper里面获取来的,那么这个Wrapper的作用其实是类似springBeanWrapper的,spring 的BeanWrapper作用是:
BeanWrapper是对Bean的包装,其接口中所定义的功能很简单包括设置获取被包装的对象,获取被包装bean的属性描述器,由于BeanWrapper接口是PropertyAccessor的子接口,因此其也可以设置以及访问被包装对象的属性值。BeanWrapper大部分情况下是在spring ioc内部进行使用,通过BeanWrapper,spring ioc容器可以用统一的方式来访问bean的属性
那么dubbo的Wrapper其实和他差不多,在dubbo里面它的作用也是:包装一个接口或者类,可以通过Wrapper对接口或者类的实例进行取值、赋值、或者制定方法的调用
回到代码中,拿到这个Wrapper包装类之后,new AbstractProxyInvoker<T>(proxy, type, url)创建了一个Invoker这个Invoker它是一个可执行的对象,能够根据方法的名称、参数得到相应的执行结果,它里面有一个很重要的方法:org.apache.dubbo.rpc.Invoker#invoke(Invocation invocation),这个方法的入参Invocation代表了要执行的方法和参数等重要信息,Invoker在dubbo里面相当的重要,这里先说一下概念:
Invoker有3种类型的Invoker

  1. 本地执行类的Invoker
    server端:要执行 demoService.sayHello,就通过InjvmExporter来进行反射执行demoService.sayHello就可以了。

  2. 远程通信类的Invoker
    client端:要执行 demoService.sayHello,它封装了DubboInvoker进行远程通信,发送要执行的接口给server端。
    server端:采用了AbstractProxyInvoker执行了DemoServiceImpl.sayHello,然后将执行结果返回发送给client.

  3. 多个远程通信执行类的Invoker聚合成集群版的Invoker
    client端:要执行 demoService.sayHello,就要通过AbstractClusterInvoker来进行负载均衡,DubboInvoker进行远程通信,发送要执行的接口给server端。
    server端:采用了AbstractProxyInvoker执行了DemoServiceImpl.sayHello,然后将执行结果返回发送给client.

那么继续回到org.apache.dubbo.config.ServiceConfig#exportLocal方法,到我们分析这里,只是分析了PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local)获取了一个Invoker对象,然后我们再看:PROTOCOL.export(Invoker<T> invoker)这一步,这个调用呢其实会走到org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol#export,为啥会走到InjvmProtocol里面呢,因为这里的PROTOCOL是自动生成的代理类:

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    // 根据规则,Protocol 接口的destroy()方法上没有@Adaptive注解,所以生成的代码方法体为抛出一个异常
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
    // 根据规则,Protocol 接口的getDefaultPort()方法上没有@Adaptive注解,所以生成的代码方法体为抛出一个异常
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null)
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        //这里需要注意extName,这个extName是从url.getProtocol()里面获取的,那么在暴露本地服务时,这个url.getProtocol()的值其实是:"injvm"  当暴露的是远程服务时,这个值是:"registry"
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

上面这个代码是自动生成的代理类源代码,可以看到在生成的代码中有行SPI获取实现类的代码:ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName),这里就决定了org.apache.dubbo.rpc.Protocol#export到底走的哪个实现类,这个extName的获取是通过:String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
在这里插入图片描述
在本地服务暴露中我们可以看到URLprotocol被set为了org.apache.dubbo.rpc.Constants#LOCAL_PROTOCOL,而这个值其实就是<injvm>,那么自动生成的代理类中ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName)获取到的值肯定就是org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol#export
同时我们也看到了暴露本地服务的方法注释,而且暴露本地服务本就应该是把服务暴露在自己的JVM中,所以这里拿到这个Invoker对象自然就走到了org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol#export
在这里插入图片描述
在本地服务暴露中,创建了一个InjvmExporter,那么这个InjvmExporter作用就是用来维护Invoker的生命周期,然后把这个put到了exporterMap在这里插入图片描述
这里的key是"包路径+接口名",比如:com.alibaba.dubbo.demo.DemoService
然后创建完成后本该出去到org.apache.dubbo.config.ServiceConfig#exportLocal,但是实际上会走到AOP的调用org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper#export
为什么回来到这里呢,正常应该执行就返回了,其实这里是dubbo的AOP导致的,在我的dubbo的SPI这篇文章里面提到过这一点
在这里插入图片描述
大家不理解的可以回到那篇文章里面去看看,在dubbo的SPI加载实现的时候
在这里插入图片描述
在这里插入图片描述

所以会经过3个Wrapper:org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper#exportorg.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#exportorg.apache.dubbo.qos.protocol.QosProtocolWrapper#export,那么这几个Wrapper的作用分别是:

  • ProtocolFilterWrapper:过滤器调用链包装器
  • ProtocolListenerWrapper:协议监听器包装器
  • QosProtocolWrapper:在线运维服务包装器

到这里呢,org.apache.dubbo.config.ServiceConfig#exportLocal里面本地服务暴露就执行玩了,最后把返回的Exporter添加到了org.apache.dubbo.config.ServiceConfig#exporters,然后就打印了日志
在这里插入图片描述

3.2 远程服务暴露

在讲暴露远程服务之前,我们先讲一下dubbo中的org.apache.dubbo.rpc.Protocol接口的概念

在这里插入图片描述

  • org.apache.dubbo.rpc.Protocol#export暴露远程服务(用于服务端),就是将proxyFactory.getInvoker创建的代理类 invoker对象,通过协议暴露给外部
  • org.apache.dubbo.rpc.Protocol#refer引用远程服务(用于客户端), 通过proxyFactory.getProxy来创建远程的动态代理类,例如DemoService的远程动态接口

接着继续看代码,远程服务暴露的部分代码和本地服务暴露是差不多的,比如
在这里插入图片描述
都是先根据URL等信息获取到一个Invoker对象,然后这里又生成一个包装类DelegateProviderMetaDataInvoker,然后重点还是在PROTOCOL.export(wrapperInvoker);这一个调用上,那么这个PROTOCOL依然是和上面分析本地服务暴露时一样的动态生成的类,但是传入的参数不同,所以org.apache.dubbo.rpc.Protocol#export中实际调用到的不是本地服务暴露时的org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol#export,而是:org.apache.dubbo.registry.integration.RegistryProtocol#export,当然在调用到RegistryProtocol#export之前还是会先进入上面讲过的几个Wrapper过滤、监听里面,之后才进入实际的RegistryProtocol#export
在这里插入图片描述
这个方法的代码我们直接看org.apache.dubbo.registry.integration.RegistryProtocol#doLocalExport
在这里插入图片描述
可以看到这里又是一个protocol.export,这里的这个protocol依旧是动态生成的代理类,根据传入的invokerDelegate获取对应的protocol,这里就直接说了实际执行到的是:org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export,在执行之前依旧是过几个Wrapper,这里就不再说了。直接看DubboProtocol#export在这里插入图片描述
这个方法进来获取了url,然后serviceKey(url)获取了一个key,这个key就是"包路径+接口:端口号",比如:com.alibaba.dubbo.demo.DemoService:20880,这就是远程暴露服务和本地暴露服务的key的区别,后面多了一个端口号。
然后创建了一个DubboExporter 那么这个DubboExporter作用同样和本地服务暴露一样用来维护Invoker的生命周期,同时存入exporterMap
然后到了openServer(url);optimizeSerialization(url);
其中openServer(url);的作用是:

  1. 获取服务器实例的 key,格式为host:port
  2. 通过key,从缓存中获取对象
  3. 如果没有获取到,就调用createServer新建一个
  4. 如果获取到了就调用reset重置服务

进入openServer(url);会发现最终会执行到这里
在这里插入图片描述
点进去这个Exchangers.bind(url, requestHandler),这里先说一下这个Exchangers,他的作用是:信息交换层,封装请求响应模式,同步转异步
继续跟进代码
在这里插入图片描述
在这里插入图片描述
这里ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);的type是:header,所以返回的是org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger,那么继续往下跟进就到了org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#bind
接着进入:org.apache.dubbo.remoting.Transporters#bind(org.apache.dubbo.common.URL, org.apache.dubbo.remoting.ChannelHandler...),这里说下一这个Transporters他是网络传输层,用来抽象netty和mina的统一接口
在这里插入图片描述
org.apache.dubbo.remoting.Transporters#getTransporter是通过SPI的ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();获取的,所以这里获取的是个动态代理类Transporters$Adpative,然后通过动态代理类最终调用到了:
在这里插入图片描述
这里需要说明一下,netty4是2.5.6引入的,2.5.6之前的netty用的是netty3
在这里插入图片描述
org.apache.dubbo.remoting.transport.netty4.NettyServer#doOpen
在这里插入图片描述
org.apache.dubbo.remoting.transport.netty.NettyServer#doOpen
在这里插入图片描述
这两个netty的启动这里就不详细分析了,就是启动了netty服务,大家可以百度下netty的知识,反正这里大家只需要知道启动了netty服务就行,后面有机会再写下netty相关的文章

然后我们继续回到:org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#bind中,到这里Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))这里就算走完了,回到new HeaderExchangeServer,也就是org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeServer#HeaderExchangeServer
在这里插入图片描述
在这里插入图片描述
这里主要是做了一个心跳定时器
然后就回到了org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#createServer里面,这里Exchangers.bind(url, requestHandler)完成之后就继续回到org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export
再回到:org.apache.dubbo.registry.integration.RegistryProtocol#exportdoLocalExport(originInvoker, providerUrl)调用
到这里 远程服务暴露基本就完成了,总结一下远程服务暴露需要以下4个步骤:

  1. org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export开始暴露服务
  2. org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#bind 信息组装交换
  3. org.apache.dubbo.remoting.transport.netty4.NettyTransporter#bind网络传输层Transporter信息构建
  4. org.apache.dubbo.remoting.transport.netty4.NettyServer#doOpen打开netty暴露服务

到这里之后,就开始zookeeper链接相关的了 在下一篇文章里面在分析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值