[dubbo 源码之 ]2. 服务消费方如何启动服务
启动流程
消费者在启动之后,会通过ReferenceConfig#get()
来生成远程调用代理类。在get
方法中,会启动一系列调用函数,我们来一个个解析。
配置同样包含2种:
- XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
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="first-consumer-xml"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181/"/>
<dubbo:reference proxy="javassist" scope="remote"
id="demoService"
generic="false"
check="false"
async="false"
group="dubbo-sxzhongf-group"
version="1.0.0"
interface="com.sxzhongf.deep.in.dubbo.api.service.IGreetingService">
<dubbo:method name="sayHello" retries="3" timeout="5000" mock="false" />
<dubbo:method name="testGeneric" retries="3" timeout="5000" mock="false" />
</dubbo:reference>
</beans>
- Java API
public class ApiConsumerApplication {
public static void main(String[] args) {
// 1. 创建服务引用对象实例
ReferenceConfig<IGreetingService> referenceConfig = new ReferenceConfig<IGreetingService>();
// 2. 设置应用程序信息
referenceConfig.setApplication(new ApplicationConfig("deep-in-dubbo-first-consumer"));
// 3. 设置注册中心
referenceConfig.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181/"));
// 4. 设置服务接口和超时时间
referenceConfig.setInterface(IGreetingService.class);
// 默认重试3次
referenceConfig.setTimeout(5000);
// 5 设置服务分组和版本
referenceConfig.setGroup("dubbo-sxzhongf-group");
referenceConfig.setVersion("1.0.0");
// 6. 引用服务
IGreetingService greetingService = referenceConfig.get();
// 7. 设置隐式参数
RpcContext.getContext().setAttachment("company", "sxzhongf");
// 获取provider端传递的隐式参数(FIXME: 需要后续追踪)
// System.out.println("年龄是:" + RpcContext.getContext().getAttachment("age"));
//8. 调用服务
System.out.println(greetingService.sayHello("pan"));
}
}
1. new ReferenceConfig
在此阶段,会初始化org.apache.dubbo.config.AbstractConfig
& org.apache.dubbo.config.ReferenceConfig
的静态变量以及静态代码块。
2. ReferenceConfig#get
ReferenceConfig#init
- 通过
DubboBootstrap
启动dubbo。 - 继而初始化服务的元数据信息,
URL.buildKey(interfaceName, group, version)
这段用来生成唯一服务的key,所以我们之前说dubbo的唯一标识是通过全路径
和group以及version来决定的。 - 接下来通过
org.apache.dubbo.config.utils.ConfigValidationUtils#checkMock
来检查我们mock是否设置正确。 - 设置一系列要用的参数(系统运行参数、是否为consumer、是否为泛型调用等等),检查
dubbo
的注册地址,默认为当前主机IP
- 通过
ReferenceConfig#createProxy
创建调用代理开始
ReferenceConfig#shouldJvmRefer
首先判断是否为Injvm
调用如果不为
injvm
,判断是否为peer to peer
端对端设置,如果为p2p,那么就直连url检查注册中心是否存在(注册中心有可能有多个)
循环检查注册中心是否有效
配置转换
URL
registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=deep-in-dubbo-first-consumer&dubbo=2.0.2&pid=9780&refer=application%3Ddeep-in-dubbo-first-consumer%26dubbo%3D2.0.2%26group%3Ddubbo-sxzhongf-group%26interface%3Dcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%26methods%3DsayHello%2CtestGeneric%26pid%3D9780%26register.ip%3D192.168.14.99%26release%3D2.7.5%26revision%3D1.0.0%26side%3Dconsumer%26sticky%3Dfalse%26timeout%3D5000%26timestamp%3D1582959441066%26version%3D1.0.0®istry=zookeeper&release=2.7.5×tamp=1582961922459
如果只有一个注册中心,执行
REF_PROTOCOL.refer(interfaceClass, urls.get(0));
来将URL
转为Invoker
对象,因为private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
是扩展是Adaptive
,因此在这里会执行Protocol$Adaptive#refer
方法,又由于protocol
参数值为registry
,因此会只是RegistryProtocol#refer
,又由于被Wrapper
类装配,因此会先执行三个Wrapper类,最后才能执行到RegistryProtocol#refer -> RegistryProtocol#doRefer
,在doRefer
方法中会订阅服务提供者地址,最后返回Invoker
对象。!
那么究竟是如何生成的Invoker
对象呢?我们来看下具体代码:
java private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) { // 1.可以查寻RegistryDirectory & StaticDirectory 区别 RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url); directory.setRegistry(registry); directory.setProtocol(protocol); // all attributes of REFER_KEY Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters()); //2. 封装订阅所用URL URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters); if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) { directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url)); registry.register(directory.getRegisteredConsumerUrl()); } //3.build路由规则链 directory.buildRouterChain(subscribeUrl); //4.订阅服务提供者地址 directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY, PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY)); //5.封装集群策略到虚拟invoker Invoker invoker = cluster.join(directory); return invoker; }
上述代码中,步骤1根据URL生成了一个RegistryDirectory
(关于Directory接口的作用,可以自行查询一些,直白一些就是将一堆Invoker对象封装成一个List,只有2种实现RegistryDirectory & StaticDirectory
,从命名可看出一个是动态可变,一个不可变),代码2 封装了订阅所要使用的参数信息,代码3则是封装绑定路由规则链,代码4订阅。代码5调用 Cluster$Adaptive#join
方法生成Invoker
对象。
在代码2种从zk获取服务提供者信息:
一旦zk返回服务提供者列表之后,就会调用RegistryDirectory#notify
,如下:
在org.apache.dubbo.common.utils.UrlUtils#isMatch
中对provider和consumer的api进行匹配校验。继续跟踪:RegistryDirectory#notify -> RegistryDirectory#refreshOverrideAndInvoker -> RegistryDirectory#refreshInvoker -> RegistryDirectory#toInvokers
在toInvokers
正式将URL转换为Invoker
,通过invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
在这里protocol#refer
同样执行顺序如:
(dubbo 2.7.5) protocol#refer -> protocol$Adaptive#refer -> QosProtocolWrapper#refer -> ProtocolListenerWrapper#refer -> ProtocolFilterWrapper#refer ->AbstractProtocol#refer->DubboProtocol#protocolBindingRefer
,调用代码如下:
```java
@Override
public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
```
关注getClients
,其中执行了DubboProtocol#getSharedClient -> DubboProtocol#initClient
创建netty client进行连接。
因为这里使用的是明确的DubboInvoker
,在回调的时候,Wrapper会对该Invoker进行包装,执行效果如下:
因此,会执行到ProtocolFilterWrapper#buildInvokerChain
,该函数会对服务进行调用链跟踪:
```java
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
// 获取所有在MATA-INF文件中配置的激活的责任链接口
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public URL getUrl() {
return invoker.getUrl();
}
@Override
public boolean isAvailable() {
return invoker.isAvailable();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult;
try {
asyncResult = filter.invoke(next, invocation);
} catch (Exception e) {
if (filter instanceof ListenableFilter) {// Deprecated!
Filter.Listener listener = ((ListenableFilter) filter).listener();
if (listener != null) {
listener.onError(e, invoker, invocation);
}
} else if (filter instanceof Filter.Listener) {
Filter.Listener listener = (Filter.Listener) filter;
listener.onError(e, invoker, invocation);
}
throw e;
} finally {
}
return asyncResult.whenCompleteWithContext((r, t) -> {
if (filter instanceof ListenableFilter) {// Deprecated!
Filter.Listener listener = ((ListenableFilter) filter).listener();
if (listener != null) {
if (t == null) {
listener.onMessage(r, invoker, invocation);
} else {
listener.onError(t, invoker, invocation);
}
}
} else if (filter instanceof Filter.Listener) {
Filter.Listener listener = (Filter.Listener) filter;
if (t == null) {
listener.onMessage(r, invoker, invocation);
} else {
listener.onError(t, invoker, invocation);
}
} else {// Deprecated!
filter.onResponse(r, invoker, invocation);
}
});
}
@Override
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
```
所有的负载均衡、容错策略等都是在这里绑定的。
7.如果有多个注册中心,将会循环执行步骤6,将URL转换为Invoker
对象,然后添加到一个List,分别进行注册之后,然后判断最后一个注册中心url
是否有效,针对多订阅的场景,URL中添加cluster
参数,默认使用org.apache.dubbo.rpc.cluster.support.registry.ZoneAwareCluster
策略,使用org.apache.dubbo.rpc.cluster.Cluster#join
将多个Invoker
对象封装一个虚拟的Invoker
中,否则如果最后一个注册中心是无效的,直接封装Invoker
对象。
8.创建服务代理ProxyFactory#getProxy(org.apache.dubbo.rpc.Invoker<T>)
,因为ProxyFactory
是一个适配类。那么同样这里会调用ProxyFactory$Adaptive#getProxy
,这里最终就是返回一个代理服务的Invoker对象。
至此,我们的消费端的绑定远程zk的服务就已经结束了。
那么,我们在调用服务器方法的时候服务器端和客户端都是如何处理的呢?下节我们将继续分析。
[dubbo 源码之 ]2. 服务消费方如何启动服务的更多相关文章
- dubbo源码分析02:服务引用
一.何时创建服务引用 引用官方文档的原话,如果将Dubbo托管在Spring-IOC容器下,Dubbo服务引用的时机有两个,第一个是在Spring容器调用ReferenceBean的afterProp ...
- Dubbo学习笔记10:Dubbo服务消费方启动流程源码分析
同理我们看下服务消费端启动流程时序图: 在<Dubbo整体架构分析>一文中,我们提到服务消费方需要使用ReferenceConfig API来消费服务,具体是调用代码(1)get()方法来 ...
- Dubbo 源码分析 - 服务调用过程
注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...
- Dubbo 源码分析 - 服务引用
1. 简介 在上一篇文章中,我详细的分析了服务导出的原理.本篇文章我们趁热打铁,继续分析服务引用的原理.在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直联的方式引用服务,第二种 ...
- Dubbo 源码分析 - 服务导出
1.服务导出过程 本篇文章,我们来研究一下 Dubbo 导出服务的过程.Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可 ...
- Dubbo(三):深入理解Dubbo源码之如何将服务发布到注册中心
一.前言 前面有说到Dubbo的服务发现机制,也就是SPI,那既然Dubbo内部实现了更加强大的服务发现机制,现在我们就来一起看看Dubbo在发现服务后需要做什么才能将服务注册到注册中心中. 二.Du ...
- Dubbo源码(五) - 服务目录
前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 今天,来聊聊Dubbo的服务目录(Directory).下面是官方文档对服务目录的定义: 服务目 ...
- Dubbo源码(九) - 服务调用过程
1. 前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 源码分析均基于官方Demo,路径:dubbo/dubbo-demo 如果没有看过之前Dub ...
- dubbo源码之四——服务发布二
dubbo版本:2.5.4 2. 服务提供者暴露一个服务的详细过程 上图是服务提供者暴露服务的主过程: 首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl ...
随机推荐
- ElasticSearch 本机可以访问,外网无法访问----问题解决
问题:本机可以访问,外网无法访问 config/elasticsearch.yml network.host: 0.0.0.0 使用普通用户zuoys,重启es,报错如下: [1]: max file ...
- 静态、动态cell区别
静态cell:cell数目固定不变,图片/文字固定不变(如qq设置列表可使用静态cell加载) 动态cell:cell数目较多,且图片/文字可能会发生变化(如应网络请求,淘宝列表中某个物品名称或者图片 ...
- 文件操作符|-e|-M|-s|-A|_|-r -w $filename|stat|localtime|&|>>|<<
TTY:终端是一种字符型设备,它有多种类型,通常使用tty 来简称各种类型的终端设备 #!/usr/bin/perl use strict; use warnings; print "exi ...
- python中的变量引用小结
python的变量都可以看成是内存中某个对象的引用.(变量指向该内存地址存储的值) 1.python中的可更改对象和不可更改对象 python中的对象可以分为可更改(mutable)对象与不可更改(i ...
- 一张图看懂三维GIS
- 实战_4:UI开发工具-WindowBuilder
介绍: WindowBuilder是谷歌开发的,开发java图形界面的工具,是一个eclipse插件. WindowBuilder支持开发多种形式的图形界面:SWT/JFace.Swing.GWT 安 ...
- 工具类BitMap 把网络URL图片转换成BitMap
代码不复杂,直接把完整代码贴上. 这次是用到很旧的HttpURLConnection,那为什麽会用这个,因为我本来想转回okhttp的,可实在没时间转,项目就已经做下去了,结果转不回来. packag ...
- 算法笔记4.3递归 问题 A: 吃糖果
问题 A: 吃糖果 题目描述 名名的妈妈从外地出差回来,带了一盒好吃又精美的巧克力给名名(盒内共有 N 块巧克力,20 > N >0). 妈妈告诉名名每天可以吃一块或者两块巧克力. 假设名 ...
- Spring Boot中@Async的作用
在Spring中,@Async这个注解用于标记的异步的方法.方法上一旦标记了这个方法,当其它线程调用这个方法时,就会开启一个新的线程去异步处理业务逻辑. 此注解的使用说明: 1.此注解可以用在方法上, ...
- rest framework-解析器和渲染器-长期维护
############### 解析器 ############### # 解析器----数据解析器, # # 前端发送了json数据,在request的body里面, # 我们需要把json ...