Dubbo是如何进行远程服务调用的?(源码流程跟踪)

首先会分析Dubbo是如何进行远程服务调用的,如果不了解dubbo的服务暴露和服务调用,请去看前两篇dubbo的文章,然后后面我还会说一下dubbo的SPI机制

当我们在使用@reference 注解的时候,来调用我们的提供者的Service对象的时候,Dubbo中的服务调用是怎么实现的

Dubbo的远程服务调用

(1)首选Dubbo是通过Poxy对象来生成一个代理对象的

  1. 具体实现是在ReferenceConfig对象中调用的private T createProxy(Map<String, String> map)方法的,这个方法中有三种生成Invoker对象的方式,第一种是通过本地JVM,第二种是通过URL对象是不是为空判断进行判断,然后如果为空就从注册中心获取这个Invoker对象,否则就是从ReferenceConfig中的URL中拿到
  2. 上面那个方法中还会通过获取到的Invoker这里的【生成Invoker的过程后面补充】的对象去通过ProxyFactory生成Poxy对象,代码为:return proxyFactory.getProxy(this.invoker);,这里proxyFactory其实就是
	//ProxyFactory接口的javassist扩展类JavassistProxyFactory的getProxy方法实现public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {return Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));}
  1. 然后通过第2步的getPoxy()方法去动态代理生成代理Poxy对象
    public static Proxy getProxy(Class<?>... ics) {return getProxy(ClassHelper.getClassLoader(Proxy.class), ics);}/*** Get proxy.** @param cl  class loader.* @param ics interface class array. 可以实现多个接口* @return Proxy instance.*/public static Proxy getProxy(ClassLoader cl, Class<?>... ics) {if (ics.length > 65535)throw new IllegalArgumentException("interface limit exceeded");StringBuilder sb = new StringBuilder();for (int i = 0; i < ics.length; i++) {String itf = ics[i].getName();if (!ics[i].isInterface())throw new RuntimeException(itf + " is not a interface.");Class<?> tmp = null;try {tmp = Class.forName(itf, false, cl);} catch (ClassNotFoundException e) {}if (tmp != ics[i])throw new IllegalArgumentException(ics[i] + " is not visible from class loader");sb.append(itf).append(';');}// use interface class name list as key.// 用接口类名做key,多个接口以分号分开。String key = sb.toString();// get cache by class loader.// 缓存Map<String, Object> cache;synchronized (ProxyCacheMap) {cache = ProxyCacheMap.get(cl);if (cache == null) {cache = new HashMap<String, Object>();ProxyCacheMap.put(cl, cache);}}Proxy proxy = null;synchronized (cache) {do {Object value = cache.get(key);if (value instanceof Reference<?>) {//如果有存在引用对象,返回缓存对象。proxy = (Proxy) ((Reference<?>) value).get();if (proxy != null)return proxy;}//对象正在生成,线程挂起,等待if (value == PendingGenerationMarker) {try {cache.wait();} catch (InterruptedException e) {}} else {//放入正在生成标识cache.put(key, PendingGenerationMarker);break;}}while (true);}//类名称后自动加序列号 0,1,2,3...long id = PROXY_CLASS_COUNTER.getAndIncrement();String pkg = null;//ClassGenerator dubbo用javassist实现的工具类ClassGenerator ccp = null, ccm = null;try {ccp = ClassGenerator.newInstance(cl);Set<String> worked = new HashSet<String>();List<Method> methods = new ArrayList<Method>();for (int i = 0; i < ics.length; i++) {//检查包名称及不同包的修饰符if (!Modifier.isPublic(ics[i].getModifiers())) {String npkg = ics[i].getPackage().getName();if (pkg == null) {pkg = npkg;} else {if (!pkg.equals(npkg))throw new IllegalArgumentException("non-public interfaces from different packages");}}//代理类添加要实现的接口Class对象ccp.addInterface(ics[i]);for (Method method : ics[i].getMethods()) {//获取方法描述符,不同接口,同样的方法,只能被实现一次。String desc = ReflectUtils.getDesc(method);if (worked.contains(desc))continue;worked.add(desc);int ix = methods.size();//方法返回类型Class<?> rt = method.getReturnType();//方法参数类型列表Class<?>[] pts = method.getParameterTypes();//生成接口的实现代码,每个方法都一样StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");for (int j = 0; j < pts.length; j++)code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");if (!Void.TYPE.equals(rt))code.append(" return ").append(asArgument(rt, "ret")).append(";");methods.add(method);ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());}}if (pkg == null)pkg = PACKAGE_NAME;// create ProxyInstance class.// 具体代理类名称,这里是类全名String pcn = pkg + ".proxy" + id;ccp.setClassName(pcn);ccp.addField("public static java.lang.reflect.Method[] methods;");ccp.addField("private " + InvocationHandler.class.getName() + " handler;");//创建构造函数ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;");ccp.addDefaultConstructor();Class<?> clazz = ccp.toClass();//通过反射,把method数组放入,静态变量methods中,clazz.getField("methods").set(null, methods.toArray(new Method[0]));// create Proxy class.String fcn = Proxy.class.getName() + id;ccm = ClassGenerator.newInstance(cl);ccm.setClassName(fcn);ccm.addDefaultConstructor();//设置父类为抽象类,Proxy类子类,ccm.setSuperClass(Proxy.class);//生成实现它的抽象方法newInstance代码//new 的实例对象,是上面生成的代理类 pcnccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");Class<?> pc = ccm.toClass();proxy = (Proxy) pc.newInstance();} catch (RuntimeException e) {throw e;} catch (Exception e) {throw new RuntimeException(e.getMessage(), e);} finally {// release ClassGeneratorif (ccp != null)ccp.release();if (ccm != null)ccm.release();synchronized (cache) {if (proxy == null)cache.remove(key);else//放入缓存,key:实现的接口名,value 代理对象,这个用弱引用,//当jvm gc时,会打断对实例对象的引用,对象接下来就等待被回收。cache.put(key, new WeakReference<Proxy>(proxy));cache.notifyAll();}}return proxy;}

(2)这里是进行补充上面的那个【Invoker的怎么生成的步骤】,看一下Invoker中都包含了什么信息这么重要,这里需要强调一下这个Invoker生成的过程和Dubbo服务的暴露和导出生成的Invoker不太一样

  1. invoker对象是通过 InvokerInvocationHandler构造方法传入,而InvokerInvocationHandler对象是由JavassistProxyFactory类getProxy(Invoker invoker, Class<?>[] interfaces)方法创建。
    这还要回到调用proxyFactory.getProxy(invoker);方法的地方,即ReferenceConfig类的createProxy(Map<String, String> map)方法中
  2. 所以这个Invoker其实是通过ReferenceConfig 中的createProxy(Map<String, String> map)方法来生成的Invoker对象,这个就是下面中使用到的对象refprotocol ,private static final Protocol refprotocol = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
 if (urls.size() == 1) {//只有一个直连地址或一个注册中心配置地址//这里的urls.get(0)协议,可能是直连地址(默认dubbo协议),也可能是regiter注册地址(zookeeper协议)//我们这里走的是注册中心,所以invoker = refprotocol.refer(interfaceClass, urls.get(0));//本例通过配置一个注册中心的形式(***看这里***)} else {//多个直连地址或者多个注册中心地址,甚至是两者的组合。List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();URL registryURL = null;for (URL url : urls) {//创建invoker放入invokersinvokers.add(refprotocol.refer(interfaceClass, url));if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {registryURL = url; // 多个注册中心,用最后一个registry url}}if (registryURL != null) { //有注册中心协议的URL,//对多个url,其中存在有注册中心的,写死用AvailableCluster集群策略//这其中包括直连和注册中心混合或者都是注册中心两种情况URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);invoker = cluster.join(new StaticDirectory(u, invokers));} else { // 多个直连的urlinvoker = cluster.join(new StaticDirectory(invokers));}}
  1. 上面的代码可以看出生成Invoker的有三种方式,
    • 第一种是refprotocol.refer(this.interfaceClass, (URL),这里的接口用的是SPI机制的dubbo对应下面的那个@SPI注解=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol这一句,所以这一种主要是使用的是Dubbo直连的协议都会走这一层
    • 第二种是 cluster.join(new StaticDirectory(u, invokers));,这个是加了路由的负载均衡相关的,这一部分主要都是路由层的东西
    • 第三种是 this.invoker = cluster.join(new StaticDirectory(invokers));也是路由层的东西, 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
@SPI("dubbo")
public interface Protocol {int getDefaultPort();@Adaptive<T> Exporter<T> export(Invoker<T> var1) throws RpcException;@Adaptive<T> Invoker<T> refer(Class<T> var1, URL var2) throws RpcException;void destroy();
}

因为Dubbo中在实现远程调用的时候其实是通过Poxy对象生成的Invoker对象,那么就先看一下Invoker的怎么生成的,里面都包含了什么信息?

  • 这里的invoker对象,通过InvokerInvocationHandler构造方法传入,而InvokerInvocationHandler对象是由JavassistProxyFactory类
    getProxy(Invoker invoker, Class<?>[] interfaces)方法创建

Dubbo中的SPI机制的使用和分析

在这里插入图片描述

参考文章:https://cloud.tencent.com/developer/article/1109459

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/555521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis五种数据类型及应用场景

Redis五种数据类型及应用场景 MySqlMemcached架构的问题 实际MySQL是适合进行海量数据存储的&#xff0c;通过Memcached将热点数据加载到cache&#xff0c;加速访问&#xff0c;很多公司都曾经使用过这样的架构&#xff0c;但随着业务数据量的不断增加&#xff0c;和访问量的…

WebSocket介绍和Socket的区别

WebSocket介绍与原理 WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP)请求完成。 ——百度百科 目的&#xff1a;即时通讯&#xff0c;替代轮询 应用场景&#xff1a;网站上的即时通讯是很常见的&#xf…

WebSocket协议入门介绍

文章目录WebSocket协议是什么WebSocket是应用层协议WebSocket与Http的区别为什么要使用WebSocket如何使用WebSocket客户端API在客户端使用WebSocket在服务端使用WebSocket反向代理对WebSocket的支持WebSocket协议是什么 WebSocket是应用层协议 WebSocket是基于TCP的应用层协议…

远程ykvm 插件移值java_Centos7 命令行下kvm安装windows,linux

查看是否支持egrep "svm|vmx" /proc/cpuinfo |uniq安装软件yum install libvirt -yyum -y install qemu-kvmsystemctl enable libvirtd && systemctl start libvirtd# 启动libvitd服务查看 ip add #查看是否有 virbr0网卡 yum install virt-installcentos7 例…

php7.0扩展yac,php扩展之yac安装

git克隆$ git clone https://github.com/laruence/yac.git解压安装注意这里的php-config可能不一样查看php-config位置$whereis php编译安装$ cd yac$ phpize$ ./configure --with-php-config/usr/bin/php-config$ make$ make install修改php.ini,载入扩展,重启php-fpmextensio…

springboot+websocket实现服务端、客户端

一、引言 小编最近一直在使用springboot框架开发项目&#xff0c;毕竟现在很多公司都在采用此框架&#xff0c;之后小编也会陆续写关于springboot开发常用功能的文章。 什么场景下会要使用到websocket的呢&#xff1f; websocket主要功能就是实现网络通讯&#xff0c;比如说…

输出有样式的php,PHP导出带样式的Excel

工作中做导出的时候&#xff0c;需要导出自定义的表格或嫌弃导出的Excel格式太难看了。需要设置颜色、字号大小、加粗、合并单元格等等。Paste_Image.pngPHP代码&#xff1a;php/**导出文件return string*/public function export(){$file_name "成绩单-".date(&quo…

idea创建多模块Springboot项目、导入多模块、删除多模块

前言 在eclipse中有Workspace&#xff08;工作空间&#xff09;和 Project&#xff08;工程&#xff09;的概念在 IDEA中只有 Project&#xff08;工程&#xff09;和 Module&#xff08;模块&#xff09;的概念。这个地方刚开始用的时候会很容易理不清它们之间的关系。在eclip…

包含内部类的.java文件编译后生成几个.class文件

如果一个类有内部类&#xff0c;编译将生成几个字节码文件&#xff0c;规则是怎样呢&#xff1f; 写在前&#xff0c;自己动手丰衣足食&#xff0c;结论只有个人实验支持&#xff0c;没有官方数据支持&#xff0c;欢迎自行查阅文档然后来指正&#xff0c;轻喷&#xff0c;谢谢…

Intellij IDEA远程debug教程实战和要点总结

远程调试&#xff0c;特别是当你在本地开发的时候&#xff0c;你需要调试服务器上的程序时&#xff0c;远程调试就显得非常有用。 JAVA支持调试功能&#xff0c;本身提供了一个简单的调试工具JDB&#xff0c;支持设置断点及线程级的调试同时&#xff0c;不同的JVM通过接口的协…

Idea进行远程Debug

Idea远程调试 当把一个本地项目部署到远程服务器后有可能出现意想不到错误&#xff0c;这个时候通过远程调试能够更清楚的找到bug所在位置。本篇主要讲解如何使用Idea开发工具 进行调试1.远程调试服务器上面的SpringBoot 项目2.远程调试服务器上运行在tomcat中的项目&#xff…

matlab表示DFT和DTFT,【 MATLAB 】离散傅里叶级数(DFS)与DFT、DTFT及 z变换之间的关系...

上篇博文我们简单的讨论了离散傅里叶级数DFS和离散傅里叶变换DFT之间的关系&#xff0c;简单地说&#xff0c;DFT就是DFS在一个周期内的表现。为了后面讨论方便&#xff0c;这里给出DFS的系数公式(分析公式)&#xff1a;(1) 其中&#xff1a;综合公式&#xff1a;(2) 为了对比&…

php分列显示,【杂谈】PHP数组怎样按键名完成降序分列

PHP关联数组按键名完成降序分列&#xff0c;我们能够直接经由过程PHP中的krsor()函数来完成。krsort函数示意对数组根据键名逆向排序。那末在前面的文章中&#xff0c;已给人人引见过PHP关联数组按键名完成升序分列的要领。下面我们继承连系简朴的示例&#xff0c;给人人引见PH…

spike 序列matlab,SPKtool1.0.1 神经信号spike 分类及处理 工具包 matlab 238万源代码下载- www.pudn.com...

文件名称: SPKtool1.0.1下载收藏√ [5 4 3 2 1 ]开发工具: matlab文件大小: 937 KB上传时间: 2013-03-05下载次数: 3提 供 者: 无语详细说明&#xff1a;神经信号spike 分类及处理 工具包-A toolbox for neurophysiological data processing文件列表(点击判断是否您需要的…

cuda加速求解龙格库塔四阶五步积分

一般代码使用cuda加速的方法&#xff1a; 使用PyTorch进行加速&#xff1a; 首先&#xff0c;你需要将你的ODE系统定义为PyTorch模型&#xff0c;这样可以利用PyTorch的自动微分功能和GPU加速。然后&#xff0c;你需要将数据和参数转换为PyTorch张量&#xff0c;并将它们移动到…

Idea怎么实现画类图

1.【file】-【Settings】-【Tools】-【Diagrams】-勾选Java Class Diagram的选项 2.右击类&#xff0c;【Diagrams】-【Show Diagram】

如何在linux下创建一个可运行shell脚本?

linux系统下&#xff0c;经常会用到自启动脚本&#xff0c;那么如何新建一个自启动脚本&#xff1f; 工具/原料 linux系统 方法/步骤 1 新建一个.sh文件&#xff0c;touch test.sh 2 编辑test.sh文件&#xff0c;vi test.sh 然后键入i&#xff0c;输入内容&#xff0c; #!…

TCP 协议(包含三次握手,四次挥手)

文章目录1.确认应答机制 (ACK)2.超时重传3.1建立连接 - 三次握手 ▲3.2.断开连接 - 四次挥手 ▲1.确认应答机制 (ACK) 确认应答是可靠传输的最核心机制 接收方反馈一个应答报文(ACK)&#xff0c;表示已收到 假设现在 A 想去 B 家里玩游戏&#xff0c;于是 A 给 B 发消息&…

Redis使用单线程却快到飞起的原因

文章目录Redis为什么用单线程&#xff1f;多线程的开销Redis使用单线程为什么还这么快&#xff1f;网络与IO操作的潜在阻塞点基于多路复用的高性能IO模型回调机制Redis的性能瓶颈点其他Redis相关的有趣问题1. 为什么要用Redis&#xff0c;直接访问内存不好吗&#xff1f;2. 数据…

线程池参数到底要怎么配?

文章目录1 线程池快速回顾2 现有设置参数的方法及不足3 如何设置核心线程数&#xff08;corePoolSize&#xff09;4 如何设置最大线程数&#xff08;maxPoolSize&#xff09;5 如何改变等待队列长度想必大家对Java里面线程池&#xff08; 类&#xff09;一定不陌生吧&#xff0…