引言

上一篇我们分析了服务发布的原理,可以看到默认是创建了一个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. Python之日志处理(logging模块一基础)

    转载自:https://www.cnblogs.com/yyds/p/6901864.html 本节内容 日志相关概念 logging模块简介 使用logging提供的模块级别的函数记录日志 logg ...

  2. Istio的流量管理(实操二)(istio 系列四)

    Istio的流量管理(实操二)(istio 系列四) 涵盖官方文档Traffic Management章节中的inrgess部分. 目录 Istio的流量管理(实操二)(istio 系列四) Ingr ...

  3. JAVA-Servlet操纵方法

    此篇自用查询 存储数据的区域对象域对象的通用的方法:ServletContext context=getServletContext();获取ServletContext对象setAtrribute( ...

  4. Maven系列(二) -- 将开源库上传到maven仓库私服

    前言 之前简单说了下Maven的搭建,现在跟大家说一下如何将自己的aar传到我们新搭建的maven仓库里面,接下来我们就从最基本的新建一个library开始讲述整个流程,话不多说,让我们把愉快的开始吧 ...

  5. 【JAVA习题七】输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数。

    package erase; import java.util.Scanner; public class 字符串分类 { public static void main(String[] args) ...

  6. Dubbo源码阅读-服务导出

    Dubbo服务导出过程始于Spring容器发布刷新事件,Dubbo在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装URL.第二部分是导出服 ...

  7. HTTP/2 简介及https原理

    注:以下内容节选自<高性能浏览器网络>(出版社:O'Reilly,作者:Ilya Grigorik). 要了解完整版本和相关内容,请访问 hpbn.co. HTTP/2 可以让我们的应用更 ...

  8. jchdl - RTL Value Propagation

    https://mp.weixin.qq.com/s/2_0yQYdHlSQzPw7vX7NuHA     ​​ 因为建模方式的不同,RTL值的传播不同于GSL值的传播.   jchdl GSL模型的 ...

  9. Chisel3 - Tutorial - Adder4

    https://mp.weixin.qq.com/s/X5EStKor2DU0-vS_wIO-fg   四位加法器.通过FullAdder级联实现.   参考链接: https://github.co ...

  10. Java实现 蓝桥杯 算法提高 矩阵翻转

    问题描述 Ciel有一个N*N的矩阵,每个格子里都有一个整数. N是一个奇数,设X = (N+1)/2.Ciel每次都可以做这样的一次操作:他从矩阵选出一个X*X的子矩阵,并将这个子矩阵中的所有整数都 ...