【2020-03-28】Dubbo源码杂谈
前言
本周空闲时间利用了百分之六七十的样子。主要将Dubbo官网文档和本地代码debug结合起来学习,基本看完了服务导出、服务引入以及服务调用的过程,暂未涉及路由、字典等功能。下面对这一周的收获进行一下总结梳理。
一、基于事件驱动的服务导出
提起服务导出,不要被它的名字误导了,通俗点说就是服务的暴露和注册。服务的暴露是指将服务端的端口开放,等待消费端来连接。服务的注册即将服务信息注册到注册中心。针对服务暴露和注册的具体流程,可参见博主之前的一篇文章 https://www.cnblogs.com/zzq6032010/p/11275478.html ,讲述的比较详细,暂不赘述。
注重提一下的是Dubbo启动服务暴露和注册的时机,是采用的事件驱动来触发的,跟SpringBoot有点神似。这种通过事件驱动来触发特定逻辑的方式,在实际开发工作中也可以灵活使用。
二、服务引入及SPI
对于Dubbo的SPI自适应扩展,可参见博主之前的一篇文章 https://www.cnblogs.com/zzq6032010/p/11219611.html,但此篇文章当时写的比较浅显,还未悟得全部。
下面以Protocol类为例,看一下在ServiceConfig类中的成员变量 Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension() 是什么样子。
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol { public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
} public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
} public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
} public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
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);
} public java.util.List getServers() {
throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
}
这就是getAdaptiveExtension()之后得到的代理类,可见在初始化ServiceConfig时先获取的protocol只是一个代理Protocol类,程序运行时再通过传入的Url来判断具体使用哪个Protocol实现类。这才是SPI自适应扩展的精髓所在。
除此之外,在通过getExtension方法获取最终实现类时,还要经过wrapper类的包装。详见ExtensionLoader类中的如下方法:
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
如果接口存在包装类,则在第16行进行wrapper类的处理,将当前instance封装进包装类中,再返回包装类的实例,即通过这一行代码实现了扩展类的装饰器模式改造。
此处同样以Protocol类为例,Url中的协议是registry,那么我最终执行到RegistryProtocol的export方法时栈调用路径是这样的:

即中间经过了三层Wrapper的封装,每层都有自己特定的功能,且各层之间互不影响。Dubbo在很多自适应扩展接口处加了类似这样的装饰扩展,程序的可扩展设计还可以这样玩,Interesting!
服务引入的流程大体是这样的:消费端从注册中心获取服务端信息,封装成Invoker,再封装成代理类注入消费端Spring容器。流程比较简单,可自行根据上一节的内容debug调试。
三、服务调用的疑问
之前未看Dubbo源码时一直有一个疑问:dubbo的消费端代理类调用服务端接口进行消费时,是通过netty将消息发送过去的,服务端在接收到消息后,是如何调用的服务端目标类中的方法?反射吗?反射可以调用到方法,但是没法解决依赖的问题,而且正常情况服务端调用应该也是Spring容器中已经实例化好的的服务对象,那是如何通过netty的消息找到Spring中的对象的?
实际dubbo处理的很简单,只要在服务暴露的时候将暴露的服务自己存起来就好了,等消费端传过来消息的时候,直接去map里面取,取到的就是Spring中封装的那个服务对象,very easy。

服务调用过程草图
如上图所示,服务调用的流程大体是这样的:调用之后通过client远程连接到server,在server端维护了暴露服务的一个map,服务端接收到请求后去map获取Exporter,exporter中有服务端封装好的Invoker,持有Spring中的服务bean,最终完成调用。中间还涉及很多细节,比如netty的封装与调用,序列化反序列化,负载均衡和容错处理等。
小结
Dubbo作为一个优秀的rpc服务框架,其优势不止在于它的rpc过程,还在于更多细节模块的实现以及可扩展的设计,比如序列化处理、负载均衡、容错、netty的线程调度、路由、字典... 内容挺多的,后面打算针对dubbo的四大负载均衡算法做一下研究,浅尝辄止,不求甚解!
【2020-03-28】Dubbo源码杂谈的更多相关文章
- dubbo面试题,会这些说明你真正看懂了dubbo源码
整理了一些dubbo可能会被面试的面试题,感觉非常不错.如果你基本能回答说明你看懂了dubbo源码,对dubbo了解的足够全面.你可以尝试看能不能回答下.我们一起看下有哪些问题吧? 1.dubbo中& ...
- dubbo源码分析6-telnet方式的管理实现
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码分析1-reference bean创建
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码分析2-reference bean发起服务方法调用
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码分析3-service bean的创建与发布
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码分析4-基于netty的dubbo协议的server
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码分析5-dubbo的扩展点机制
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码之四——服务发布二
dubbo版本:2.5.4 2. 服务提供者暴露一个服务的详细过程 上图是服务提供者暴露服务的主过程: 首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl ...
- dubbo源码之二——dubbo入口
dubbo源码版本:2.5.4 dubbo-contaner-api com.alibaba.dubbo.container dubbo-demo-consumer com.alibaba.dubbo ...
随机推荐
- 美团新零售招聘-高级测试开发(20k-50k/月)
内推邮箱:liuxinguang@meituan.com 地点:北京 职位级别:p2-2以上级别 15.5薪
- c#版阿里云oss上传——基于layui、pupload的oss 后端web端直传的vue组件
废话前言 去年,做项目用到oss上传,度娘上逛了一圈写了个前后端代码结合的c#版oss上传. 今年又要用到oss上传时发现很难复用,复用改动的范围太大,显然这个轮子不合格.于是想了下,花了一天的时间( ...
- JavaScript学习总结之数组常用的方法和属性
先点赞后关注,防止会迷路寄语:没有一个冬天不会过去,没有一个春天不会到来. 前言数组常用的属性和方法常用属性返回数组的大小常用方法栈方法队列方法重排序方法操作方法转换方法迭代方法归并方法总结结尾 前言 ...
- 达拉草201771010105《面向对象程序设计(java)》第十六周学习总结
达拉草201771010105<面向对象程序设计(java)>第十六周学习总结 第一部分:理论知识 1.程序与进程的概念: (1)程序是一段静态的代码,它是应用程序执行的蓝 本. (2)进 ...
- KEMET推出新的多层陶瓷电容器,用于市电供电的应用
前言:2019年12月2日,全球领先的电子组件供应商KEMET公司 (“ KEMET”或“公司”)推出了一系列新的表面安装设备(SMD)安全认证的多层陶瓷电容器(MLCC),用于市电供电的应用.与现有 ...
- vmware workstaion之不能连接外网
Vmwareworkstation15系列桥接模式不能上网解决办法 最近在vmwareworkstation15中创建一个双网卡虚拟机,由于一直连不了外网,一直困扰着我.各种姿势在百度谷歌查找,最终还 ...
- 这么香的Chrome插件,你都安装了吗?
工欲善其事必先利其器,今天长话短说,介绍13个敏捷.高效的Chrome插件 根据使用方式,本人将其划分为三大类: 开发者工具 日常效率工具类 浏览器管理类 开发者工具 1. Web Developer ...
- 这些Zepto中实用的方法集
前言 时间过得可真快,转眼间2017年已去大半有余,你就说吓不吓人,这一年你成长了多少,是否荒度了很多时光,亦或者天天向上,收获满满.今天主要写一些看Zepto基础模块时,比较实用的部分内部方法,在我 ...
- 课题:html5图像羽化(不规则区域羽化,feather,html5羽化)
下午搜索了一堆相关文章,没有找到符合要求的. 对一张图片应用不规则区域的羽化,该怎么做呢? 首先去查了下 羽化的原理,然而没有什么用, 然后就开始从表现层去研究怎么模拟? idea 1: blur滤镜 ...
- Python正则表达式语法
单个字符匹配 字符 匹配 . 匹配任意字符(除了\n) [...] 匹配字符集 \d/\D 匹配数字/非数字 \s/\S 匹配空白/非空白字符 \w/\W 匹配单词字符[a-zA-A0-9] In [ ...