引言

上一篇我们分析了服务发布的原理,可以看到默认是创建了一个Netty server,并通过Invoker调用服务,同样,在客户端也会创建一个Inovker对象,下面就一起来看看这个引用创建过程。

正文

服务订阅

服务端的dubbo:service配置对应的类为ServiceBean,同样的,dubbo:reference对应有一个ReferenceBean,该类中的getObject方法,就是获取客户端代理类以及订阅服务的开端(默认情况下,Dubbo使用懒加载方式,在ReferenceBean对应的服务被引用或注入到其它类的时候调用getObject方法;否则,在bean初始化完成后就会调用afterPropertiesSet方法,而该方法也会调用getObject方法),所以我们就从这个方法开始。

public Object getObject() throws Exception {
return get();
} public synchronized T get() {
if (destroyed){
throw new IllegalStateException("Already destroyed!");
}
if (ref == null) {
init();
}
return ref;
}

这两个方法不用多说,判断接口代理类是否已经存在,不存在调用init方法初始化,而该方法中大部分是检查配置,关键点是createProxy方法:

private T createProxy(Map<String, String> map) {
.......
// 本地JVM调用
if (isJvmRefer) {
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
invoker = refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
// 配置了url属性,可能是点对点调用,也可能是写的注册中心的url
if (url != null && url.length() > 0) {
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
url = url.setPath(interfaceName);
}
// 如果是registry协议,说明是想连接注册中心,就设置refer参数到url
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else {
// 加载注册中心url,并将zookeeper协议转为registry协议
List<URL> us = loadRegistries(false);
if (us != null && us.size() > 0) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
} if (urls.size() == 1) {
// 只有一个注册中心或者服务直连
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
// 多个注册中心或者多个服务提供者直连,或者两者混合
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // 用了最后一个registry url
}
}
if (registryURL != null) { // 有 注册中心协议的URL
// 对有注册中心的Cluster 只用 AvailableCluster
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // 不是 注册中心的URL
invoker = cluster.join(new StaticDirectory(invokers));
}
}
} // 创建服务代理
return (T) proxyFactory.getProxy(invoker);
}

代码很长,我截取了关键的部分,首先判断是否为本地injvm调用,若不是则加载服务直连的url或注册中心的url,接着根据url数量判断生成相应的invoker(url数量为1说明是只有一个注册中心或者服务直连,则直接调用protocol.refer获得相应的invoker;大于1代表是多个注册中心或者多个服务提供者直连,或者两者混合,则会生成多个invoker,通过cluster.join合并),最后调用proxyFactory.getProxy(invoker)生成相应代理类。所以,这里主要逻辑就在invoker和代理类的生成。

Invoker的创建

在上一篇讲过,Invoker在服务端是服务提供类的代理类,通过proxyFactory.getInvoker创建;而在客户端,则用于执行远程调用,通过protocol.refer调用。但是Protocol的实现类有很多,这里主要分析单注册中心和dubbo协议直连的情况。

单注册中心的Invoker创建

如果是Zookeeper的单注册中心方式,invoker就是通过invoker = refprotocol.refer(interfaceClass, urls.get(0))这句代码创建的,这里我们能够很快的定位到RegistryProtocol.refer方法中:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 将registry协议转为zookeeper协议
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
// 这个代码上一篇文章也分析过了,首先这里是通过依赖注入注入的RegistryFactory$Adpative
// 对象,最终会创建一个Zookeeper连接并返回ZookeeperRegistry对象
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
} // 分组服务走这里
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0 ) {
if ( ( Constants.COMMA_SPLIT_PATTERN.split( group ) ).length > 1
|| "*".equals( group ) ) {
return doRefer( getMergeableCluster(), registry, type, url );
}
}
// 非分组服务走这里,注意这里的cluster也是依赖注入进来的
return doRefer(cluster, registry, type, url);
}

需要注意cluster对象,它是通过依赖注入进来的,猜猜它是个什么对象?是FailoverCluster吗?其实并不是的,实际应该为MockClusterWrapper(FailoverCluster)对象(在服务发布一节中已有相应的分析),有什么用,我们后面再分析,这里暂时不讨论。

在refer中主要是创建zookeeper连接,并获取Registry对象,然后通过doRefer订阅和调用服务。

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// 服务目录,暂时不详细分析它,知道它包含了所有的服务url就行了
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
// 注册consumer服务
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
// 监听providers、configurators、routers、category节点的变化
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
// 一个注册中心可能存在多个相同的服务提供者,需要将其合并为一个Invoker
return cluster.join(directory);
}

这个方法首先创建了一个服务目录,该目录会监听除consumer节点以外(本身就是consumer,当然没必要再监听consumer的变化了)的其它节点状态,最后通过cluster合并服务目录并返回invoker(Directory和Cluster等待后文来分析):

// MockClusterWrapper.join
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 这里的cluster是FailoverCluster
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
} // FailoverCluster.join
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<T>(directory);
}

从上面可以看到,这里返回的是MockClusterInvoker对象,并持有FailoverClusterInvoker的引用,这个在分析服务调用过程时会用到。

Dubbo直连的Invoker创建

如果是通过Dubbo协议直连服务的话,也是通过invoker = refprotocol.refer(interfaceClass, urls.get(0))创建Invoker,只不过会进入到DubboProtocol.refer方法中:

public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}

这个方法主要通过getClients获取客户端ExchangeClient的实例,并实例化DubboInvoker返回。但是实际通信的肯定不是ExchangeClient,因为服务端默认使用的是Netty,那么客户端也应该是,我们可以点进去看看如何创建的。

private ExchangeClient[] getClients(URL url){
// 是否共享连接
boolean service_share_connect = false;
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
//如果connections不配置,则共享连接,否则每服务每次新建连接
if (connections == 0){
service_share_connect = true;
connections = 1;
} ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (service_share_connect){
// 获取共享连接
clients[i] = getSharedClient(url);
} else {
// 创建配置的连接数
clients[i] = initClient(url);
}
}
return clients;
}

dubbo:reference可以通过connections配置连接数,调用initClient方法创建连接,不配置默认使用共享连接,调用getSharedClient获取,首先来看看getSharedClient方法:

private ExchangeClient getSharedClient(URL url){
// 从缓存中获取客户端,该客户端具有计数功能
String key = url.getAddress();
ReferenceCountExchangeClient client = referenceClientMap.get(key);
if ( client != null ){
if ( !client.isClosed()){
// 每被引用一次,计数就+1
client.incrementAndGetCount();
return client;
} else {
referenceClientMap.remove(key);
}
}
// 创建客户端实例
ExchangeClient exchagneclient = initClient(url);
// 使用ReferenceCountExchangeClient装饰,使其具有引用计数功能
client = new ReferenceCountExchangeClient(exchagneclient, ghostClientMap);
referenceClientMap.put(key, client);
ghostClientMap.remove(key);
return client;
}

该方法中也调用了initClient方法创建客户端,并使用ReferenceCountExchangeClient装饰,使其具有引用计数的功能,每被使用一次计数就+1。再看看客户端是如何被创建的:

private ExchangeClient initClient(URL url) {

    // 获取客户端类型,默认使用netty
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT)); String version = url.getParameter(Constants.DUBBO_VERSION_KEY);
boolean compatible = (version != null && version.startsWith("1.0."));
url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() && compatible ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
//默认开启heartbeat
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT)); // BIO存在严重性能问题,暂时不允许使用
if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported client type: " + str + "," +
" supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
} ExchangeClient client ;
try {
// 创建客户端时是否立即创建连接,lazy表示不立即创建
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)){
client = new LazyConnectExchangeClient(url ,requestHandler);
} else {
client = Exchangers.connect(url ,requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url
+ "): " + e.getMessage(), e);
}
return client;
}

默认创建一个netty客户端,并根据配置判断是否立即创建连接,若使用懒加载则会在请求服务时才创建连接。但不管是否是懒加载,都是通过Exchangers.connect方法创建的连接:

public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
return getExchanger(url).connect(url, handler);
}

这里的getExchanger获取到的和服务端一样,也是HeaderExchanger

public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

是不是感觉似曾相识,还记得服务端是怎么创建连接的吧,只不过当时是调用的Transporters.bind,而这里是Transporters.connect:

public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
ChannelHandler handler;
if (handlers == null || handlers.length == 0) {
handler = new ChannelHandlerAdapter();
} else if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().connect(url, handler);
}

getTransporter方法也不用分析了,最终会进入到NettyTransporter.connect方法中:

public Client connect(URL url, ChannelHandler listener) throws RemotingException {
return new NettyClient(url, listener);
}

就是去创建一个NettyClient客户端连接Server进行通信(Netty的源码分析不是本篇的重点,就先不分析了),这样DubboInvoker中就持有了NettyClient的引用了。至此,Invoker创建流程分析完成,下面一起来看看代理类的创建。

创建代理类

上面用大量的篇幅分析了Invoker的创建,拿到Invoker对象之后通过proxyFactory.getProxy创建代理类对象,这个proxyFactory很熟悉了,也是通过自适应扩展机制获取到的对象,所以应该会调用JavassistProxyFactory的getProxy方法,但是该类中没有与之匹配的方法(方法名相同,但是参数列表不同),所以首先应该进入其父类AbstractProxyFactory的getProxy方法中:

public <T> T getProxy(Invoker<T> invoker) throws RpcException {
Class<?>[] interfaces = null;
// 从url中获取接口
String config = invoker.getUrl().getParameter("interfaces");
if (config != null && config.length() > 0) {
String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
if (types != null && types.length > 0) {
interfaces = new Class<?>[types.length + 2];
interfaces[0] = invoker.getInterface();
interfaces[1] = EchoService.class;
for (int i = 0; i < types.length; i ++) {
interfaces[i + 1] = ReflectUtils.forName(types[i]);
}
}
}
// 若url中未配置,则直接从invoker中获取
if (interfaces == null) {
interfaces = new Class<?>[] {invoker.getInterface(), EchoService.class};
}
// 调用Javaassist的getProxy方法
return getProxy(invoker, interfaces);
} // JavaassistProxyFactory
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
// 生成 Proxy 子类Proxy0
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

这里会通过模板方法调用JavassistProxyFactory.getProxy,然后通过Proxy类生成一个接口代理类proxy0和一个Proxy的子类Proxy0。Proxy0主要是实现父类的抽象方法newInstance(InvocationHandler handler)

public Object newInstance(java.lang.reflect.InvocationHandler h) {
return new com.alibaba.dubbo.common.bytecode.proxy0($1);
}

可以看到该方法就是传入一个InvocationHandler并初始化proxy0对象,刚刚说了proxy0是服务接口的代理类,因此它是实现了我们定义的服务接口及方法:

private java.lang.reflect.InvocationHandler handler;

public java.lang.String sayHello(java.lang.String arg0) {
Object[] args = new Object[1];
args[0] = ($w)$1;
Object ret = handler.invoke(this, methods[0], args);
return (java.lang.String)ret;
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}

最终就是去调用Invoker的invoker方法,所以这里会进入到相应的MockClusterInvoker.invoke方法(非服务直连创建的Invoker)以及AbstractInvoker.invoke方法(Dubbo直连时,由于DubboInvoker中没有invoker方法,所以调用其父类AbstractInvoker的)中,详细的调用过程下一篇再分析。

Dubbo——服务引用的更多相关文章

  1. dubbo服务引用与集群容错

    服务引用无非就是做了两件事 将spring的schemas标签信息转换bean,然后通过这个bean的信息,连接.订阅zookeeper节点信息创建一个invoker 将invoker的信息创建一个动 ...

  2. Dubbo服务引用源码解析③

    ​ 上一章分析了服务暴露的源码,这一章继续分析服务引用的源码.在Dubbo中有两种引用方式:第一种是服务直连,第二种是基于注册中心进行引用.服务直连一般用在测试的场景下,线上更多的是基于注册中心的方式 ...

  3. Dubbo 源码分析 - 服务引用

    1. 简介 在上一篇文章中,我详细的分析了服务导出的原理.本篇文章我们趁热打铁,继续分析服务引用的原理.在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直联的方式引用服务,第二种 ...

  4. dubbo源码分析02:服务引用

    一.何时创建服务引用 引用官方文档的原话,如果将Dubbo托管在Spring-IOC容器下,Dubbo服务引用的时机有两个,第一个是在Spring容器调用ReferenceBean的afterProp ...

  5. Dubbo源码(四) - 服务引用(消费者)

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 上一篇文章,讲了Dubbo的服务导出: Dubbo源码(三) - 服务导出(生产者) 本文,咱们 ...

  6. Dubbo 服务引入-Version2.7.5

    1.服务引用原理 Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 Referen ...

  7. Dubbo服务调用过程源码解析④

    目录 0.服务的调用 1.发送请求 2.请求编码 3.请求的解码 4.调用具体服务 5.返回调用结果 6.接收调用结果 Dubbo SPI源码解析① Dubbo服务暴露源码解析② Dubbo服务引用源 ...

  8. dubbo refrence bean(服务引用)

    在xml上写一个dubbo标签就可以把远程的服务引用到本地使用: <dubbo:reference id="buyFoodService" interface="c ...

  9. Dubbo原理和源码解析之服务引用

    一.框架设计 在官方<Dubbo 开发指南>框架设计部分,给出了引用服务时序图: 另外,在官方<Dubbo 用户指南>集群容错部分,给出了服务引用的各功能组件关系图: 本文将根 ...

随机推荐

  1. Unity 游戏框架搭建 2019 (四十八/四十九) MonoBehaviourSimplify 中的消息策略完善&关于发送事件的简单封装

    MonoBehaviourSimplify 中的消息策略完善 在上一篇,笔者说,MonoBehaviourSimplify 中的消息策略还有一些小问题.我们在这篇试着解决一下. 先贴出来代码: usi ...

  2. DFS与DP算法

    名词解释: DFS(Dynamic Plan):动态规划 DFS(Depth First Search):深度优先搜索 DFS与DP的关系 很多情况下,dfs和dp两种解题方法的思路都是很相似的,这两 ...

  3. C/C++代码优化之求两个整型的平均值

    在 C/C++ 中, 直接利用 (x + y) >> 1 来计算 \(\left\lfloor {\left( {x + y} \right)/2} \right\rfloor\) (两个 ...

  4. parrot os的一些坑

    burpsuite 破解版的运行环境需要jdk8,jdk11无法运行 选择jdk环境 update-alternatives --config java 截图工具 sudo apt install f ...

  5. Rocket - interrupts - NullIntSource

    https://mp.weixin.qq.com/s/Fn3u2OSLAzPDrlZTiLfikg 简单介绍NullIntSource的实现. 1. 简单介绍 NullIntSource实现一个不会发 ...

  6. 【HBase】知识小结+HMaster选举、故障恢复、读写流程

    1:什么是HBase HBase是一个高可靠性,高性能,面向列,可伸缩的分布式数据库,提供海量数据存储功能,一个结构化的分布式存储系统,不同于一般的关系型数据库,它适合半结构化和非结构化数据存储. 2 ...

  7. Java实现 LeetCode 836 矩形重叠(暴力)

    836. 矩形重叠 矩形以列表 [x1, y1, x2, y2] 的形式表示,其中 (x1, y1) 为左下角的坐标,(x2, y2) 是右上角的坐标. 如果相交的面积为正,则称两矩形重叠.需要明确的 ...

  8. (Java实现) 拦截导弹

    1260:[例9.4]拦截导弹(Noip1999) 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 4063 通过数: 1477 [题目描述] 某国为了防御敌国的导弹袭击,发展出一 ...

  9. Java实现第八届蓝桥杯图形排版

    标题:图形排版 小明需要在一篇文档中加入 N 张图片,其中第 i 张图片的宽度是 Wi,高度是 Hi. 假设纸张的宽度是 M,小明使用的文档编辑工具会用以下方式对图片进行自动排版: 1. 该工具会按照 ...

  10. Java实现 蓝桥杯VIP 算法提高 企业奖金发放

    算法提高 企业奖金发放 时间限制:1.0s 内存限制:512.0MB 企业发放的奖金根据利润提成.利润低于或等于10万元时,奖金可提10%:利润高于10万元,低于20万元时,低于10万元的部分按10% ...