这里debug的代码用的是github上dubbo项目的dubbo-demo里的dubbo-demo-xml下的代码。这里以默认的dubbo通信协议为debug的代码,由于这一篇都是dubbo框架内部实现,所以建议先看下dubbo官网上的一篇讲dubbo的设计原则的文章,有助于理解代码,这里先贴出dubbo官网上的架构图,然后我们在看代码时,比对架构图一起分析:
下面是架构图中的各层说明:
- config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
- proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
- registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
- cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
- monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
- protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
- exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
- transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
- serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
demo里的consumer端的xml配置:
<beans
// xmlns:xsi是xsi标签命名空间
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
// xmlns:dubbo是dubbo标签的命名空间
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
// 当前那xml文件默认的命名空间
xmlns="http://www.springframework.org/schema/beans"
// xsi:schemaLocation 配置了每个命名空间对应里配置规范,用来做格式校验
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="demo-consumer"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181" timeout="6000"/>
<!-- 协议配置-->
<dubbo:protocol name="dubbo"/>
<!-- consumer配置-->
<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService" timeout="6000" />
</beans>
1、将dubbo:reference
配置解析成BeanDefinition
Dubbo的consumer配置是<dubbo:reference>
,在《spring解析dubbo标签配置过程》中可以看到这个配置被解析成BeanDefinition
类的过程,时序图如下:
2、通过BeanDefinition实例化ReferenceConfig
对象
BeanDefinition
的BeanClass属性值为org.apache.dubbo.config.spring.ReferenceConfig.class
类,该类实现了FactoryBean
接口(FactoryBean
接口可以说就是为了这种通过动态代理技术生成bean的场景设计的,Spring在注入时,会注入该对象的getObject()
方法返回真正的实例)。getBean()
详细逻辑见这里,debug截图如下:
3、依赖注入时,触发ReferenceConfig
这个FactoryBean
的getObject()
调用,返回代理类
Spring在依赖注入时也会调用getBean(String beanName)
方法来获取已经实例化的bean,通过beanName会获取第二步中实例化的ReferenceConfig
对象,而该对象是BeanFactory
,所以最终会调用getObject()
方法,debug截图如下:
getObject()
截图如下:
getObject()
代码如下,我这里省略了部分分支代码:
public Object getObject() {
return this.get();
}
public synchronized T get() {
...
this.init();
...
return this.ref;
}
public synchronized void init() {
// 初始化dubbo启动类
if (this.bootstrap == null) {
this.bootstrap = DubboBootstrap.getInstance();
this.bootstrap.init();
}
...
// map保存所有创建consumer代理类的相关配置信息
Map<String, String> map = new HashMap();
map.put("side", "consumer");
// 往map中插入dubbo版本号、时间戳、进程pid运行时信息
ReferenceConfigBase.appendRuntimeParameters(map);
...
// 传入map,创建代理类
this.ref = this.createProxy(map);
...
}
private T createProxy(Map<String, String> map) {
...
// 检查注册中心配置,并根据注册中心配置生成注册中心的URL
this.checkRegistry();
List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
if (CollectionUtils.isNotEmpty(us)) {
for(Iterator var3 = us.iterator(); var3.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) {
u = (URL)var3.next();
monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
if (monitorUrl != null) {
map.put("monitor", URL.encode(monitorUrl.toFullString()));
}
}
}
...
// 构建invoker链
this.invoker = REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0));
...
// 通过ProxyFactory创建代理
return PROXY_FACTORY.getProxy(this.invoker);
}
下面是结合dubbo架构图画的主要步骤的时序图:
3.1 初始化Dubbo启动类DubboBootstrap
这个DubboBootstrap
是dubbo的启动类,类似于springboot
的SpringApplication
类,存储了整个dubbo环节的各种配置信息,负责provider
的服务暴露及consumer
的服务订阅、维护dubbo容器的生命周期,这里先跳过,继续研究我们的consumer
实例化过程
3.2 构建注册中心对象
代码在createProxy(Map<String, String> map)
方法里,核心代码如下
this.checkRegistry();
List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
if (CollectionUtils.isNotEmpty(us)) {
// 获取注册中心的URL,并将当前consumer配置的元数据信息放到URL的Parameter属性里
for(Iterator var3 = us.iterator(); var3.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) {
...
}
}
<dubbo:registry>
配置中心对应的bean是RegistryConfig.class
,解析过程和<dubbo:reference>
一样,被解析出来的配置都会放到一个全局的ConfigManager
中,这里会从这个ConfigManager
中读取注册中心配置。dubbo里的常见的配置都会缓存起来,在《spring的xml文件里dubbo标签解析过程》里可以看到负责解析dubbo自定义标签的是DubboNamespaceHandler
,每种配置对应的实体类如下:
这些类都继承了AbstractConfig
类,这个抽象类有个被@PostConstruct
注解标注的addIntoConfigManager()
方法(该注解会告诉Spring容器在实例化该对象后,会执行注解标注的方法),会将当前对象缓存到一个全局的ConfigManager
对象中:
@PostConstruct
public void addIntoConfigManager() {
ApplicationModel.getConfigManager().addConfig(this);
}
Url
可以说是dubbo主链路里分量非常重的一个参数封装类,在主链路里封装参数一路传递,这里就是讲注册中心的元数据与consumer的元数据封装到一个URL中,传递到下游的方法里,主要存储一些元数据信息,这里就封装了注册中心地址、consumer的接口、方法等信息,debug截图如下:
private static final Protocol REF_PROTOCOL = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
this.invoker = REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0));
这里要先讲下SPI扩展技术:
dubbo通过SPI技术来扩展相关组件,上面代码中的REF_PROTOCOL
对象就是通过SPI来扩展的,原理就是找到META-INF/dubbo/internal
下配置的扩展类,比如我下面的截图:
从这里可以看出Protocol.class
协议扩展有很多种,那么最终调用哪一个实现类来执行refer方法呢?关键是在getAdaptiveExtension()
,这个方法返回的是一个适配器类,这个适配器的refer方法会根据url里的protol协议来调用getExtension(extName)
,比如以registry://
开头的url需要用注册中心的协议实现类来执行,我贴出这个类的代码:
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
...
// 获取URL中的协议,默认dubbo
org.apache.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
...
// 根据协议来获取对应的插件
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
getExtension(extName)
方法会返回一个包装类,截图如下:
可以看出dubbo通过SPI做扩展时,配置文件里的类会分为三种:
- 适配器类:主要是通过url里的信息来适配出合适的包装类
- 包装类:会将真正的实现类包装起来,形成一个调用链,这个包装类可以理解成一个代理类,里面可以加上过滤、监听等逻辑,比如截图里的ProtocolListenerWrapper和ProtocolFilterWrapper两个包装类
- 真正的实现类
而调用链就是 通过适配器类找到对应包装类,然后通过包装类形成一个调用链,最后调用真正的实现类,下面的loadClass
方法负责加载META-INF/dubbo/internal
下配置的扩展类,可以看出区分三种类的逻辑,截图如下:
REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0))
方法,就是调用最外层的包装类ProtocolListenerWrapper
的refer方法:
// ProtocolListenerWrapper类的refer方法
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return (Invoker)(UrlUtils.isRegistry(url) ? this.protocol.refer(type, url) : new ListenerInvokerWrapper(this.protocol.refer(type, url), Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(InvokerListener.class).getActivateExtension(url, "invoker.listener"))));
}
// ProtocolFilterWrapper类的refer方法
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return UrlUtils.isRegistry(url) ? this.protocol.refer(type, url) : buildInvokerChain(this.protocol.refer(type, url), "reference.filter", "consumer");
}
这里由于是Registry协议,所以直接调用的是RegistryProtocol
类的refer方法,主干代码如下:
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = this.getRegistryUrl(url);
// 获取注册中心对象
Registry registry = this.registryFactory.getRegistry(url);
...
return this.doRefer(this.cluster, registry, type, url);
}
这里的registryFactory也是通过SPI来扩展的,由于注册中心用的是zookeeper,所以这里的registry
对象是ZookeeperRegistry
,堆栈截图如下:
3.3 构建服务发现对象
每一个配置的Consumer都会对应一个服务发现对象,服务发现的主要功能是监听注册中心的该接口下的节点变化,包括服务提供方、路由配置等信息在节点发生变化时,通知注册中心更新本地的配置信息。主干代码如下:
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// 构建服务发现对象
RegistryDirectory<T> directory = new RegistryDirectory(type, url);
directory.setRegistry(registry);
directory.setProtocol(this.protocol);
...
// 通过SPI扩展机制 设置服务发现的默认配置的路由链
directory.buildRouterChain(subscribeUrl);
...
}
通过SPI扩展配置构建路由链,并保存到服务发现对象中:
public void buildRouterChain(URL url) {
this.setRouterChain(RouterChain.buildChain(url));
}
private RouterChain(URL url) {
List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class).getActivateExtension(url, "router");
List<Router> routers = (List)extensionFactories.stream().map((factory) -> {
return factory.getRouter(url);
}).collect(Collectors.toList());
this.initWithRouters(routers);
}
3.4 将Consumer信息发布到注册中心
还是在doRefer
方法,删除次要代码后如下:
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
...
if (!"*".equals(url.getServiceInterface()) && url.getParameter("register", true)) {
directory.setRegisteredConsumerUrl(this.getRegisteredConsumerUrl(subscribeUrl, url));
// 将Consumer信息发布到注册中心
registry.register(directory.getRegisteredConsumerUrl());
}
...
}
registry.register(directory.getRegisteredConsumerUrl());
会调用zookeeper的客户端框架Curator
将consumser信息发布到zookeeper上,在zookeeper的 /dubbo/org.apache.dubbo.demo.DemoService/consumers目录下写入信息:
3.5 通过服务发现对象订阅并获取注册中心里的 服务提供方、配置、路由信息,更新服务发现对象的本地配置,构建invoker链
还是在doRefer
方法,删除次要代码后如下:
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
RegistryDirectory<T> directory = new RegistryDirectory(type, url);
directory.setRegistry(registry);
directory.setProtocol(this.protocol);
...
// 订阅节点信息,并根据注册中心的节点下的配置信息更新 服务发现directory对象里的配置
directory.subscribe(subscribeUrl.addParameter("category", "providers,configurators,routers"));
...
}
public void subscribe(URL url) {
this.setConsumerUrl(url);
CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
this.serviceConfigurationListener = new RegistryDirectory.ReferenceConfigurationListener(this, url);
// 将当前registryDirectory对象作为Listener监听节点变化
this.registry.subscribe(url, this);
}
3.5.1 订阅注册中心的providers,configurators,routers
节点
这个是服务发现的核心流程,通过zookeeper的客户端框架Curator
来watch zookeeper上providers,configurators,routers
的节点信息,并在zookeeper节点信息发生变化时,会通知到监听器,RegistryDirectory
类实现了NotifyListener
类,会作为监听器监听服务提供方节点的变化,当注册中心的providers,configurators,routers
这三个节点发生变化时,会回调这个监听器的NotifyListener
类的notify(List urls)
方法。
3.5.2 获取节点配置,更新本地缓存的provider列表,与provider建立长连接
服务提供发生变化时,zookeeper的providers
节点内容就会发生变化,会调用ZookeeperRegistry
的notify(List urls)
方法来更新privider列表,由于这里是初次订阅注册中心,所以也会主动触发一次notify(List)
方法,主干代码如下:
public synchronized void notify(List<URL> urls) {
...
// 通过回调中的urls获取服务提供方信息
List<URL> providerURLs = (List)urls.getOrDefault("providers", Collections.emptyList());
// 更新本地服务提供方列表
this.refreshOverrideAndInvoker(providerURLs);
}
url信息里包含了服务提供方的元数据信息,debug截图如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8GSaWVyR-1617207225262)(quiver-image-url/763A6034B1F510D4BFC30456997D0C74.jpg =1862x631)] 这里主要有两步:
- 将URL信息解析成一个被层层包装的Invoker链,最里层是DubboInvoker.
每一层包装都是为了添加新特性,其中重要的有增加了异步支持的AsyncToSyncInvoker,consumer就是在AsyncToSyncInvoker的invoke方法里阻塞等待provider返回结果,在这一步里形成的Invoker包装链如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AkF52hWn-1617207225279)(quiver-image-url/261433A85D0D9CA4D4B9408679A5AC82.jpg =1000x262)]
- 创建NettyClient,与provider建立长连接,通过
DubboInvoker
构造方法可以看出需要封装一个ExchangeClient
,该对象负责网络通信,默认使用Netty框架。
构造方法代码如下:
DubboInvoker<T> invoker = new DubboInvoker(serviceType, url, this.getClients(url), this.invokers);
时序图如下:
注册中心回调完整时序图如下:
3.6、通过Cluster对象对Invoker进行再次包装,添加集群特性,包括故障处理、负载均衡、路由逻辑
Invoker invoker = cluster.join(directory);
这里的cluster也是通过SPI来扩展的,我们看下dubbo里对cluster的分类就能明白抽象出这一层的意义:
从名字上可以看出有failover(故障转移)、failfast(快速失败)等,所以解Cluster
可以理是抽象了对于失败情况的不同处理机制。默认是failover机制,最终调用的是FailoverCluster
,debug截图如下:
在3.6里的invoker链的基础上经过这一层包装后,invoker链图如下:
3.7 将invoker链包装成代理类,返回给getObject()
方法
private static final ProxyFactory PROXY_FACTORY = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
PROXY_FACTORY.getProxy(this.invoker);
从代码可以看出ProxyFactory
也是通过SPI来扩展,默认使用JavassistProxyFactory
,JavassistProxyFactory.getProxy
代码如下:
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
这里将上面形成的invoker链包装成InvokerInvocationHandler.class
,我们先看下生成的代理类被反编译后的源码:
public class proxy0
implements ClassGenerator.DC,
Destroyable,
EchoService,
DemoService {
public static Method[] methods;
private InvocationHandler handler;
@Override
public Object $echo(Object object) {
Object[] objectArray = new Object[]{object};
Object object2 = this.handler.invoke(this, methods[0], objectArray);
return object2;
}
public CompletableFuture sayHelloAsync(String string) {
Object[] objectArray = new Object[]{string};
Object object = this.handler.invoke(this, methods[1], objectArray);
return (CompletableFuture)object;
}
public String sayHello(String string) {
Object[] objectArray = new Object[]{string};
Object object = this.handler.invoke(this, methods[2], objectArray);
return (String)object;
}
@Override
public void $destroy() {
Object[] objectArray = new Object[]{};
Object object = this.handler.invoke(this, methods[3], objectArray);
}
public proxy0() {
}
public proxy0(InvocationHandler invocationHandler) {
this.handler = invocationHandler;
}
}
可以看出我们在调用demoService.sayHello(String string)
方法时,实际就是调用InvokerInvocationHandler
的invoke方法,而该类的invoke
方法如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this.invoker, args);
} else {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 0) {
if ("toString".equals(methodName)) {
return this.invoker.toString();
}
if ("$destroy".equals(methodName)) {
this.invoker.destroy();
return null;
}
if ("hashCode".equals(methodName)) {
return this.invoker.hashCode();
}
} else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
return this.invoker.equals(args[0]);
}
// 构造RpcInvocation信息,分装了接口、方法、参数等元数据信息
RpcInvocation rpcInvocation = new RpcInvocation(method, this.invoker.getInterface().getName(), args);
rpcInvocation.setTargetServiceUniqueName(this.invoker.getUrl().getServiceKey());
// 调用invoker链
return this.invoker.invoke(rpcInvocation).recreate();
}
}
可以看出主要逻辑就是将被调用的类、方法、参数这些元数据信息封装成RpcInvocation
对象,调用之前生成的invoker
链的invoke
方法,这样就串起来整个调用。
到这里代理类就已经创建完毕,最终返回给getObject()
方法的就是一个代理类。