本篇了解一下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 本地服务暴露
首先PROTOCOL
和PROXY_FACTORY
分别都是从SPI中获取的,getAdaptiveExtension
说明是获取的是个自适应的扩展类,那么分别看org.apache.dubbo.rpc.Protocol
和org.apache.dubbo.rpc.ProxyFactory
,这两个接口的@Adaptive
注解都是标注在接口上,说明是动态生成的扩展类(这块是SPI的知识,不了解的可以看我之前的文章:dubbo的SPI)
其中ProxyFactory
的作用就是为了获取一个接口的代理类,例如获取一个远程接口的代理。
ProxyFactory
有2个方法,
getInvoker
:针对server端,将服务对象,如DemoServiceImpl包装成一个Invoker对象。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
的作用其实是类似spring
的BeanWrapper
的,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
-
本地执行类的Invoker
server端:要执行 demoService.sayHello,就通过InjvmExporter来进行反射执行demoService.sayHello就可以了。 -
远程通信类的Invoker
client端:要执行 demoService.sayHello,它封装了DubboInvoker进行远程通信,发送要执行的接口给server端。
server端:采用了AbstractProxyInvoker执行了DemoServiceImpl.sayHello,然后将执行结果返回发送给client. -
多个远程通信执行类的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() );
在本地服务暴露中我们可以看到URL
的protocol
被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#export
和org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#export
和org.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);
的作用是:
- 获取服务器实例的 key,格式为host:port
- 通过key,从缓存中获取对象
- 如果没有获取到,就调用createServer新建一个
- 如果获取到了就调用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#export
的doLocalExport(originInvoker, providerUrl)
调用
到这里 远程服务暴露基本就完成了,总结一下远程服务暴露需要以下4个步骤:
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export
开始暴露服务org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#bind
信息组装交换org.apache.dubbo.remoting.transport.netty4.NettyTransporter#bind
网络传输层Transporter信息构建org.apache.dubbo.remoting.transport.netty4.NettyServer#doOpen
打开netty暴露服务
到这里之后,就开始zookeeper链接相关的了 在下一篇文章里面在分析